/*
	
PROGRAM NAME	CamJoysticks
		
USED BY			BBC Academy Studio A/CTA installation

Version	Date		Programmer			Comments
=======	====		==========			========
1.0		29-10-14	Andy Woodhouse		Initial Release

	
Overview
========
CamJoysticks is a version 4.5 automatic panel that implements switching camera
sources to the selected vision monitor in response to presses on the camera
OCP joysticks.

At startup, the automatic reads the matrix driver id and the GPI driver id from
file instance.xml. It fetches the destination crosspoint ID from the Tally 
infodriver. Slots 70 has the Camera Preview destination, slot 71 has a flag to
indicate if the Waveform monitor should follow the Camera Preview.

The "normal" vision preview destination is 71 (just to confuse you with tally
infodriver slots!).

The processing implements priority switch control such that when two joysticks
are simultaneously pressed the joystick in the leftmost position is the active
source selection. Whilst it would be rare to have 10 cameras active, this
automatic supports the ability to use all the 10 CCU units fitted in CTA.
*/

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

#define PNL_MAIN	1

// Panel specific definitions
#define TIMER_SETUP	1
#define INIT_POLL 2
#define MONITOR_SLOT 70		// Slot in tally infodriver that holds the switched SDI destination. This is a package ID
#define WAVEFORMFOLLOW 71	// Slot in tally Infodriver that stores a flag. When true (1) causes the waveform monitor to switch to same source as the vision monitor.
#define STUDIOA 1

// Need to make our class visible to outside world.
// The following macro makes us visible.
EXPORT_BNCS_SCRIPT( CamJoysticks )

//++++++++++++++++++++++++++++++++++++++++++++++//
// Constructor - equivalent to ApplCore STARTUP //
//++++++++++++++++++++++++++++++++++++++++++++++//
CamJoysticks::CamJoysticks( bncs_client_callback * parent, const char * path ) : bncs_script_helper( parent, path )
{
	// Set up some safe initial values for the working variables
	sdSDIdriver = 0;					// CTA SDI Router ID
	tallyInfodriver = 0;				// CTA Tally Infodriver ID
	matrixDestination = 0;				// SDI destination switched by joystick actions
	wfmDestination = 0;					// SDI destination that feeds waveform monitor
	gpiDriverID = 0;					// GPI driver ID that passes joystick data to this task
	gpiSlotFirst = 0;					// GPI Slot number of joystick 1
	gpiSlotLast = 0;					// Slot number of the highest joystick recognised
	defaultSDIsourceID = STUDIOA;		// Need a "standard" SDI source - so use Studio A output until we hear from SDI router
	currentSDIsourceID = 0;				// The SDI source currently routed to the controlled crosspoint
	defWfmSource = 0;					// The SDI source routed to the waveform monitor
	defaultSourceName = "Studio A";
	currentSourceName = "";

	for (int i = 0; i <= JOYSTICKS; i++) myCams[i] = 0;		// Clear the camera ccu source array
	for (int i = 0; i < JOYSTICKS; i++) jstick[i] = 0;		// Clear the joystick pressed/open array
	for (int i = 0; i < JOYSTICKS; i++) jsr[i] = false;		// Clear array of revrtive received flags

	stickPressed = false;				// Assume no joystick pressed at start
	sdiRegistered = false;				// Not yet registered for SDI revrtives - used by destructir to unregister
	gpiRegistered = false;				// Not yet registered for GPI  revertives
	tallyRegistered = false;			// Not yet registered for Tally Infodriver revertives
	wfmCopy = false;					// Set true if waveform monuitor must follow the joystick selector
	firstPoll = true;					// Set flag to show we are in pass 1 reading of waveform destination
	awaitGPI = true;
	awaitTally = true;
	opMode = false;						// Not yet ready to do full processing
	tally1 = false;
	tally2 = false;
	sdi1 = false;
	sdi2 = false;
	
	stateHi = "WN_TakeHigh";			// Statesheet name for joystick upressed button display
	stateLo = "WNGroupDeselected";		// Statesheet name for joystick pressed button display

	// Show the mimic panel
	panelShow( PNL_MAIN, "p1.bncs_ui" );

	textPut("text", defaultSDIsourceID, PNL_MAIN, "DefaultSourceID");	// Display default source data
	textPut("text", defaultSourceName, PNL_MAIN, "DefaultSourceName");

	// Read working data values from instances.xml
	getDev("CTA SDI Router", &sdSDIdriver);
	getDev("CTA Tally Infodriver", &tallyInfodriver);
	
	getDev("StA Waveform", &dummy, &wfmDestination);					// Read the waveform monitor SDI destination ID
	textPut("text", wfmDestination, PNL_MAIN, "wfmDest");

	getDev("StA Joystick 1", &gpiDriverID, &gpiSlotFirst);				// Get driver number and slot number
	gpiSlotLast = gpiSlotFirst + JOYSTICKS - 1;
	textPut("text", gpiDriverID, PNL_MAIN, "GPIDriverID");
	textPut("text", bncs_string(gpiSlotFirst) + " - " + bncs_string(gpiSlotLast), PNL_MAIN, "GPIslots");

	// Need to find a better mechanism for doing this ....
	myVals = bncs_config("wn_academy.StAJoystick.CTA_CCU");				// Read the comma delimited list of the CCU SDI source numbers
	ccuList = myVals.attr("value");

	// Initialise the array of ccu source id's
	myCams[0] = 0;														// Array entry 0 is a dummy as we want work 1 to 10
	for (int i = 1; i <= ccuList.count(); i++) 	myCams[i] = ccuList[i-1].toInt();

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

	// This automatic can copy the vision monitor sources to the Waveform Monitor destination. If the copy function is enabled 
	// then subsequently disabled, we need to know the SDI source that should be sent to the waveform monitor. To establish the 
	// initial waveform monitor SDI source, this must be read from the SDI driver before other actions are initiated.
	//
	// Start by registering for revertives from the infodriver, then poll for data we need
	timerStart(INIT_POLL, 1000);			// Set a timer in case we do not get immediate response, and need to re-poll
	infoRegister(tallyInfodriver, MONITOR_SLOT, WAVEFORMFOLLOW);
	tallyRegistered = true;
	infoPoll(tallyInfodriver, MONITOR_SLOT, WAVEFORMFOLLOW);

}	// End of Constructor code


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

//+++++++++++++++++++++++++++++++++++++++++++++++//
// Button Pushes and Notifications               //
// All button pushes and notifications come here //
//+++++++++++++++++++++++++++++++++++++++++++++++//
void CamJoysticks::buttonCallback( buttonNotify *b )
{
	// As this is an automatic panel there are no active buttons on the panel.
}

//++++++++++++++++++++++++++//
// Revertive Processing     //
// All revertives come here //
//++++++++++++++++++++++++++//
int CamJoysticks::revertiveCallback( revertiveNotify * r )
{
	// There can be revertives from the CTA GPI driver, SDI router, and Tally Infodriver.
	// Actions differ during intial polling and normal operation.
	if (opMode)
	{
		// ====================== //
		// Normal operating mode. //
		// ====================== //

		// ==================== //
		// INFODRIVER REVERTIVE //
		// ==================== //
		if (r->device() == tallyInfodriver)		// Change in Tally Infodriver States
		{
			if (r->index() == MONITOR_SLOT)
			{
				// We have a new value for the switched monitor. The value is the SDI destination
				if (sdiRegistered)
				{
					routerUnregister(sdSDIdriver);			// Remove the current registration
					sdiRegistered = false;
					matrixDestination = 0;					// No destination extracted yet
				}

				matrixDestination = r->sInfo().toInt();		// Get the slot content which is a number
				if ((matrixDestination < 0) || (matrixDestination > getRouterSize(sdSDIdriver, 1)))
				{
					// Just being defensive!
					matrixDestination = 0;
				}

				// Only register for matrix data if destination is not zero
				if (matrixDestination != 0)
				{
					textPut("text", matrixDestination, PNL_MAIN, "MtxDest");
					routerRegister(sdSDIdriver, matrixDestination, matrixDestination);	// Register single destination
					sdiRegistered = true;
					routerPoll(sdSDIdriver, matrixDestination, matrixDestination);		// Poll for current source
				}

				// Add registration for the waveform monitor
				routerRegister(sdSDIdriver, wfmDestination, wfmDestination, true);

			} // End if (r->index() == MONITOR_SLOT)
				
			if (r->index() == WAVEFORMFOLLOW)
			{
				// Process the state of the flag that indicates if joystick selection should also control waveform monitor selection
				if (r->sInfo() == "0")
				{
					if (wfmCopy) routerCrosspoint(sdSDIdriver, defWfmSource, wfmDestination);	// Don't make route unless copy flag was previously set
					wfmCopy = false;															// Clear the information flag
					textPut("pixmap", "/images/DisabledTick.png", PNL_MAIN, "wfmCopy");			// Show state on mimic
				}
				else
				{
					wfmCopy = true;			// Set the information flag
					textPut("pixmap", "/images/EnabledTick.png", PNL_MAIN, "wfmCopy");
					routerCrosspoint(sdSDIdriver, currentSDIsourceID, wfmDestination);
				}

			}	// End if (r->index() == WAVEFORMFOLLOW)
		}	// End if (r->device() == tallyInfodriver)	

		// ================= //
		// MATRIX REVERTIVES //
		// ================= //
		if (r->device() == sdSDIdriver)			// Information from the SDI Router
		{
			if (r->index() == matrixDestination)	// Process revertives from vision monitoring destination
			{
				// Note the current router source values, and display on the mimics
				currentSDIsourceID = r->info();
				currentSourceName = r->sInfo();
				textPut("text", currentSDIsourceID, PNL_MAIN, "CurrentSourceID");
				textPut("text", currentSourceName, PNL_MAIN, "CurrentSourceName");

				// If there are no joysticks pressed, then this source is also the default source.
				if (!stickPressed)
				{
					defaultSDIsourceID = currentSDIsourceID;
					defaultSourceName = currentSourceName;
					textPut("text", defaultSDIsourceID, PNL_MAIN, "DefaultSourceID");
					textPut("text", defaultSourceName, PNL_MAIN, "DefaultSourceName");
				}

				if (wfmCopy)							// Is copy to WFM mode active?
				{
					routerCrosspoint(sdSDIdriver, currentSDIsourceID, wfmDestination);
				}
			}	// End if (r->index() == matrixDestination)

			if (r->index() == wfmDestination)			// Process revertives from waveform monitor SDI destination
			{
				if (wfmCopy == false) defWfmSource = r->info();		// Only have to track values when the waveform copy flag is false.

				textPut("text", r->info(), PNL_MAIN, "WFMsdiID");			// Keep the mimic panel data current
				textPut("text", r->sInfo(), PNL_MAIN, "WFMsourceName");		// ditto
			}
		} // End if (r->device() == sdSDIdriver)	

		// ================ //
		// JOYSTICK ACTIONS //
		// ================ //
		if (r->device() == gpiDriverID)			// Joystick has been pressed or released
		{
			if ((r->index() >= gpiSlotFirst) && (r->index() <= gpiSlotLast))	// Being very defensive!!
			{
				// Update the list of gpi states
				jstick[r->index() - gpiSlotFirst] = r->info();

				// Update local mimic buttons
				if (r->info() == 0)
					textPut("statesheet", stateLo, PNL_MAIN, (r->index() - gpiSlotFirst + 1));	// Not Pushed - set blue background to the mimic button
				else
					textPut("statesheet", stateHi, PNL_MAIN, (r->index() - gpiSlotFirst + 1));	// Pushed - set Red background to the mimic button

				ccuRoute();		// Make relevant crosspoint
			}
			else
			{
				// Unexpected revertive, so just ignore and return.
				return 0;
			}
		}	// End if (r->device() == gpiDriverID)	


	}	// End of if (opMode) segment for TRUE

	else
	{
		// ==================== //
		// Initialisation mode. //
		// ==================== //
		if (r->device() == tallyInfodriver)		// Get controlled matix destination id and waveform follow flag from this infodriver.
		{
			if (r->index() == MONITOR_SLOT)
			{
				matrixDestination = r->sInfo().toInt();		// Get the slot content which is a number
				if ((matrixDestination < 0) || (matrixDestination > getRouterSize(sdSDIdriver, 1)))
				{
					matrixDestination = 0;					// Just being defensive!
				}
				tally1 = true;					// We have received monitor slot value
			}

			if (r->index() == WAVEFORMFOLLOW)
			{
				if (r->sInfo() == "0")
				{
					// Do not use joystick selection to drive waveform monitor
					wfmCopy = false;														// Clear the information flag
					textPut("pixmap", "/images/DisabledTick.png", PNL_MAIN, "wfmCopy");		// Show state on mimic
				}
				else
				{
					wfmCopy = true;
					textPut("pixmap", "/images/EnabledTick.png", PNL_MAIN, "wfmCopy");
				}
				tally2 = true;					// We have received WFM Follow flag
			}

			// Add the test flags and SDI register code here
			if (tally1 && tally2)
			{
				controlHide(PNL_MAIN, "Info_2");		// Show we have received data from the tally infodriver
				timerStop(INIT_POLL);
				// Received both items of tally data - so register for SDI revertives and then poll destinations
				textPut("text", matrixDestination, PNL_MAIN, "MtxDest");
				routerRegister(sdSDIdriver, matrixDestination, matrixDestination);	// Register joystick controlled destination
				routerRegister(sdSDIdriver, wfmDestination, wfmDestination, true);	// Register Waveform address
				sdiRegistered = true;
				routerPoll(sdSDIdriver, matrixDestination, matrixDestination);		// Poll for current sources
				routerPoll(sdSDIdriver, wfmDestination, wfmDestination);
			}
		}	// End if (r->device() == tallyInfodriver)

		if (r->device() == sdSDIdriver)
		{
			if (r->index() == matrixDestination)		// Process revertives from vision monitoring destination
			{
				// Set the default destination and current destination from data returned.
				currentSDIsourceID = r->info();
				currentSourceName = r->sInfo();
				textPut("text", currentSDIsourceID, PNL_MAIN, "CurrentSourceID");
				textPut("text", currentSourceName, PNL_MAIN, "CurrentSourceName");
				defaultSDIsourceID = currentSDIsourceID;
				defaultSourceName = currentSourceName;
				textPut("text", defaultSDIsourceID, PNL_MAIN, "DefaultSourceID");
				textPut("text", defaultSourceName, PNL_MAIN, "DefaultSourceName");
				sdi1 = true;
			}	// End if (r->index() == matrixDestination)

			if (r->index() == wfmDestination)
			{
				defWfmSource = r->info();
				textPut("text", r->info(), PNL_MAIN, "WFMsdiID");			// Keep the mimic panel data current
				textPut("text", r->sInfo(), PNL_MAIN, "WFMsourceName");		// ditto
				sdi2 = true;
			}

			if (sdi1 && sdi2)
			{
				controlHide(PNL_MAIN, "Info_1");	// Show we have seen the router

				// Now register for the joystick GPI revertives
				gpiRegister(gpiDriverID, gpiSlotFirst, gpiSlotLast);			// Register for revertives from the joysticks
				gpiRegistered = true;
				gpiPoll(gpiDriverID, gpiSlotFirst, gpiSlotLast);				// Ask for initial settings
			}

		}	// End if (r->device() == sdSDIdriver)

		if (r->device() == gpiDriverID)
		{
			if ((r->index() >= gpiSlotFirst) && (r->index() <= gpiSlotLast))	// Being very defensive!!
			{
				// Update the list of gpi states
				jstick[r->index() - gpiSlotFirst] = r->info();

				// Note we received a revertive from the joystick
				jsr[r->index() - gpiSlotFirst] = true;

				// Update local mimic buttons
				if (r->info() == 0)
					textPut("statesheet", stateLo, PNL_MAIN, (r->index() - gpiSlotFirst + 1));	// Not Pushed - set blue background to the mimic button
				else
					textPut("statesheet", stateHi, PNL_MAIN, (r->index() - gpiSlotFirst + 1));	// Pushed - set Red background to the mimic button
			}
		}	// End of if (r->device() == gpiDriverID)

		// Check if all revertives received...
		int mySum = 0;
		for (int i = 0; i < JOYSTICKS; i++) mySum += jsr[i] ? 1 : 0;		// Count number of true flags
		if (mySum == JOYSTICKS)
		{
			opMode = true;
			controlHide(PNL_MAIN, "Info_3");
		}

	}	// End if (opMode) ELSE clause

	return 0;
}
// ********************************************************************************************************************** //

//++++++++++++++++++++++++++++++++++++++++++//
// Database Changes                         //
// All database name changes come back here //
//++++++++++++++++++++++++++++++++++++++++++//
void CamJoysticks::databaseCallback( revertiveNotify * r )
{
}

// All parent notifications come here ie when this script is just one 
// component of another dialog then our host might want to tell us things
bncs_string CamJoysticks::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 CamJoysticks::timerCallback( int id )
{
	switch( id )
	{
	case TIMER_SETUP:
		timerStop(id);
		break;

	case INIT_POLL:		// Timer event used in initial poll of waveform monitor destination
		infoPoll(tallyInfodriver, MONITOR_SLOT, WAVEFORMFOLLOW);		// Re-issue the poll request
		break;

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


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

void CamJoysticks::ccuRoute(void)
{
	// This function is called when the GPI joystick states array has been updated. It checks the array to see 
	// if a joystick is pressed, and if so it routes the assocated CCU to the designated VO monitor

	sum = 0;		// Set initial value that corresponds to "No joystick pressed"
	for (int i = 0; i < JOYSTICKS; i++) sum += jstick[i];	// If any joysticks are pressed then sum != 0
	
	if (sum == 0) 
	{
		// Ensure that the default source is routed to the monitor
		if (currentSDIsourceID != defaultSDIsourceID)
		{
			routerCrosspoint(sdSDIdriver, defaultSDIsourceID, matrixDestination);
		}
		stickPressed = false;
	}
	else
	{
		// Compute the leftmost joystick and route associated CCU
		idx = 0;
		while ((jstick[idx] != 1) && (idx <= JOYSTICKS))  idx++;	// exits with idx one less than number of CCU to route

		idx++;												// Increment counter to correct value
		stickPressed = true;								// Note that at least one joystick is pressed
		if (currentSDIsourceID != myCams[idx])				// Check if this CCU is already routed to the vision monitor
		{
			routerCrosspoint(sdSDIdriver, myCams[idx], matrixDestination);		// Make new route.
		}
	}
}
