/*
PROGRAM NAME	LD_Select

USED BY			BBC Academy Studio A/CTA installation

Version		Date		Programmer		Comments
=======		====		==========		========
1.0			30-11-2014	Andy Woodhouse	Initial Release
1.1			15-08-2016	Andy Woodhouse	Updates to use instances.xml instead of wn_academy.xml

Overview
========
LD_Select is a version 4.5 automatic panel that implements switching selected 
sources to the defined vision monitor in response to presses on the Lighting
Directors button box.

The automatic reads the matrix driver id and the GPI driver id from file instances.xml
It fetches the crosspoint switching data from the Tally Infodriver slots 61 to 68
(source selectons), and slot 69 (destination monitor).

The processing implements priority switch control such that when two buttons are simultaneously pressed
the one with the lowest GPI ID is the active source selection.

*/

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

#define PNL_MAIN	1

#define TIMER_SETUP	1
#define INIT_POLL 2
#define BUTTONS 8
#define SOURCE1SLOT 61
#define MONITOR_SLOT 69		// Slot in tally infodriver that holds the switched SDI destination.
#define STUDIOA 1
#define CTA_SDI_ROUTER "CTA SDI Router"
#define CTA_TALLYID "CTA Tally Infodriver"
#define CTA_GPI_ID "LD Button 1"

// This nasty little macro is needed to make our class visible to the outside world
EXPORT_BNCS_SCRIPT( LD_Select )

// constructor - equivalent to ApplCore STARTUP
LD_Select::LD_Select( bncs_client_callback * parent, const char * path ) : bncs_script_helper( parent, path )
{
	// Set local variables to known initial states
	sdSDIdriver = 0;
	tallyInfodriver = 0;
	matrixDestination = 0;
	gpiDriverID = 0;
	gpiSlotFirst = 0;
	gpiSlotLast = 0;
	defaultSDIsourceID = 0;
	currentSDIsourceID = 0;
	defaultSourceName = "---";
	currentSourceName = "";

	BtnPressed = false;						// Initialise flags
	sdiRegistered = false;
	gpiRegistered = false;
	tallyRegistered = false;
	awaitGPI = true;						// true intil gets revertives from GPI
	awaitTally = true;						// true until get revertives from Tally Infodriver

	stateHi = "WN_TakeHigh";				// statesheet name for joystick upressed button display
	stateLo = "WNGroupDeselected";			// statesheet name for joystick pressed button display

	// Show panel from file LD_Auto.bncs_ui
	// We'll know it as our panel PNL_MAIN
	panelShow(PNL_MAIN, "LD_Auto.bncs_ui");

	// Read working data values from instances.xml
	getDev(CTA_SDI_ROUTER, &sdSDIdriver);

	getDev(CTA_TALLYID, &tallyInfodriver);
	textPut("text", tallyInfodriver, PNL_MAIN, "ID_ID");		// Display Tally infodriver ID
	textPut("text", bncs_string(SOURCE1SLOT) + " - " + bncs_string(MONITOR_SLOT), PNL_MAIN, "ID_Slots");

	// Get the device and GPI id for the first button
	getDev(CTA_GPI_ID, &gpiDriverID, &gpiSlotFirst);
	textPut("text", gpiDriverID, PNL_MAIN, "GPIDriverID");
	
	gpiSlotLast = gpiSlotFirst + BUTTONS - 1;
	textPut("text", bncs_string(gpiSlotFirst) + " - " + bncs_string(gpiSlotLast), PNL_MAIN, "GPIslots");

	// Set the GPI indicators on mimic panel to known initial state
	for (int i = 1; i <= 8; i++) textPut("statesheet", stateLo, PNL_MAIN, i);

	// Initialise the array of sources and pressed that are associated with the buttons 
	for (int i = 0; i <= 7; i++) sw_src[i] = 0;
	for (int j = 0; j <= 7; j++) pressed[j] = 0;

	// Register for Infodriver responses, and poll for the contents
	timerStart(INIT_POLL, 1000);								// Start the response timer
	pollTimer = true;
	infoRegister(tallyInfodriver, SOURCE1SLOT, MONITOR_SLOT);
	infoPoll(tallyInfodriver, SOURCE1SLOT, MONITOR_SLOT);
	tallyRegistered = true;

	// Register for GPI responses, and poll for initial settings
	gpiRegister(gpiDriverID, gpiSlotFirst, gpiSlotLast);
	gpiPoll(gpiDriverID, gpiSlotFirst, gpiSlotLast);
	gpiRegistered = true;

}

// destructor - equivalent to ApplCore CLOSEDOWN
LD_Select::~LD_Select()
{
	// Remove router and GPI driver registrations
	if (sdiRegistered) routerUnregister(sdSDIdriver);
	if (gpiRegistered) gpiUnregister(gpiDriverID);
	if (tallyRegistered) infoUnregister(tallyInfodriver);
	if (pollTimer) timerStop(INIT_POLL);
}

// all button pushes and notifications come here
void LD_Select::buttonCallback( buttonNotify *b )
{
	// This is an automatic panel that has no active buttons
}

// all revertives come here
int LD_Select::revertiveCallback( revertiveNotify * r )
{

	/////////////////////////////////////////
	// Check for responses from SDI Router //
	/////////////////////////////////////////
	if (r->device() == sdSDIdriver)
	{
		// Copy source id and name to mimic
		textPut("text", r->info(), PNL_MAIN, "CurrentSDI_ID");
		textPut("text", r->sInfo(), PNL_MAIN, "Current_Name");
		currentSDIsourceID = r->info();

		if (!BtnPressed)								// If there are no buttons pressed, update the default source
		{
			defaultSDIsourceID = currentSDIsourceID;
			defaultSourceName = currentSourceName;
		}
	}

	/////////////////////////////////////////
	// Check for responses from GPI driver //
	/////////////////////////////////////////
	if (r->device() == gpiDriverID)
	{
		int offset;

		if (awaitGPI)									// Have we yet seen GPI reply?
		{
			awaitGPI = false;
			controlHide(PNL_MAIN, "AwaitGPI");
		}

		if ((r->index() >= gpiSlotFirst) && (r->index() <= gpiSlotLast))	// Hyper defensive as only registered for this range
		{
			offset = r->index() - gpiSlotFirst;								// Compute the button offset buton 1 == offset 0
			if (r->info() == 0)												// Update button mimic
				textPut("statesheet", stateLo, PNL_MAIN, 1 + offset);
			else
				textPut("statesheet", stateHi, PNL_MAIN, 1 + offset);
		}
		pressed[offset] = r->info();										// Update the array of pressed buttons
		switchDest();														// Call routine that updates the monitor destination
	}	// End of gpiDriver processing


	///////////////////////////////////////////////
	// Check for responses from Tally Infodriver //
	///////////////////////////////////////////////
	if (r->device() == tallyInfodriver)
	{
		bncs_string s_name;
		int offset;
		int sdiID;

		if (awaitTally)					// Handle the first poll actions. Cancel timer when infodriver has responded
		{
			awaitTally = false;
			if (pollTimer)
			{
				pollTimer = false;
				timerStop(INIT_POLL);
				controlHide(PNL_MAIN, "AwaitTallyInfo");
			}
		}

		switch (r->index())
		{
			case SOURCE1SLOT + 0:		// Monitor 1 SDI source
			case SOURCE1SLOT + 1:		// Monitor 2 SDI source
			case SOURCE1SLOT + 2:		// Monitor 3 SDI source
			case SOURCE1SLOT + 3:		// Monitor 4 SDI source
			case SOURCE1SLOT + 4:		// Monitor 5 SDI source
			case SOURCE1SLOT + 5:		// Monitor 6 SDI source
			case SOURCE1SLOT + 6:		// Monitor 7 SDI source
			case SOURCE1SLOT + 7:		// Monitor 8 SDI source
				offset = r->index() - SOURCE1SLOT;						// Compute the offset into button block
				sdiID = r->sInfo().toInt();								// Get the source number
				routerName(sdSDIdriver, 0, sdiID, s_name);				// Get the source name
				textPut("text", sdiID, PNL_MAIN, 11+offset);			// Copy data to source ID mimic displays
				textPut("text", s_name, PNL_MAIN, 21 + offset);			// Copy name to source name mimic displays
				sw_src[offset] = sdiID;									// If sdiID == 0, GPI switch code will ignore
				break;

			case MONITOR_SLOT:			// Destination monitor ID
				matrixDestination = r->sInfo().toInt();					// Get the SDI desination number
				textPut("text", matrixDestination, PNL_MAIN, "50");		// Show selection id on mimic
				routerName(sdSDIdriver, 1, matrixDestination, s_name);	// Look up the destination name
				textPut("text", s_name, PNL_MAIN, "51");				// Show selection on mimic

				// See if the sdi router is currently registered, if so de-register and then register for new destination
				if (sdiRegistered) 
				{
					routerUnregister(sdSDIdriver);
					textPut("text", "", PNL_MAIN, "CurrentSDI_ID");		// Clear any mimic data displays
					sdiRegistered = false;
				}

				// Only register if we have a valid destination
				if (matrixDestination != 0)
				{
					routerRegister(sdSDIdriver, matrixDestination, matrixDestination);	// Register new destination
					sdiRegistered = true;
					routerPoll(sdSDIdriver, matrixDestination, matrixDestination);		// Poll the new destination
				}
				break;

			default:
				break;
		}
	}

	return 0;
}

// all database name changes come back here
void LD_Select::databaseCallback( revertiveNotify * r )
{
	// It is possible that a source name change will be supplied for any of the selectable sources, or for the curent destination.
	// Only interested in name changes from the sd SDI router.
	if (r->device() == sdSDIdriver)
	{
		// Database 0 (Source names) and Database 1 are relevant
		if (r->database() == 0)
		{
			// Sources database. Could match any of the array of source values or the default source
			for (int j = 0; j < 8; j++)
			{
				// scan the array of source selections
				if (r->index() == sw_src[j])
				{
					textPut("text", r->sInfo(), PNL_MAIN, 21 + j);	// Assumes r->sInfo() returns new name
				}
			}	// end for (int j....
			// Other possible name on show is the current source
			if (r->index() == currentSDIsourceID)
			{
				textPut("text", r->sInfo(), PNL_MAIN, "Current_Name");
			}
		}

		if (r->database() == 1)
		{
			// Only interested if the changed index is our destination
			if (r->index() == matrixDestination)
			{
				textPut("text", r->sInfo(), PNL_MAIN, "51");	// Need to prove this code. If does not work must fetch name
			}
		}	// end if (r->database()...
	}	// end if (r->device() == sdSDIdriver())...
}	// End of databse callback

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

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

	}
	else if( p->command() == "instance" && p->value() != m_instance )
	{	// Our instance is being set/changed
		m_instance = p->value();
		//Do something instance-change related here
	}

	else if( p->command() == "myParam" )
	{	// Persisted value or 'Command' being set here
		m_myParam = p->value();
	}

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

	return "";
}

////////////////////////////
// Timer events come here //
////////////////////////////
void LD_Select::timerCallback( int id )
{
	switch( id )
	{
	case TIMER_SETUP:
		timerStop(id);
		break;

	case INIT_POLL:												// Timer event used in initial poll of tally infodriver
		routerPoll(sdSDIdriver, SOURCE1SLOT, MONITOR_SLOT);		// Re-issue the poll request
		break;

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


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

///////////////////////////////////////////////////////////////////////////////////////////////////
// switchDest() is called to see if a button is active, and if so to route the associated source //
///////////////////////////////////////////////////////////////////////////////////////////////////
void LD_Select::switchDest(void)
{
	int sum = 0;

	for (int i = 0; i < 8; i++) sum += pressed[i];	// If any buttons are pressed then sum != 0

	if (sum == 0)
	{
		// Ensure that the default source is routed to the monitor, uless it is already selected
		if (currentSDIsourceID != defaultSDIsourceID)
		{
			routerCrosspoint(sdSDIdriver, defaultSDIsourceID, matrixDestination);
		}
		BtnPressed = false;
	}
	else
	{
		// Compute the leftmost joystick and route associated CCU
		int idx = 0;

		while ((pressed[idx] != 1) && (idx < 8)) idx++;
		BtnPressed = true;
		if ((sw_src[idx] != 0) && (sw_src[idx] != currentSDIsourceID)) routerCrosspoint(sdSDIdriver, sw_src[idx], matrixDestination);
	}
}
