/*
PROGRAM NAME	MixerExtraIn.dll

USED BY			BBC Academy Studio C installation

Version	Date		Programmer				Comments
======= ==========  ==========              ========
1.0		12-08-2017	Andy Woodhouse			Initial Release

The Studio C installation has a mixer with 40 SDI inputs. Only the first 24 inputs are connected to
the SDI router. The final 16 inputs are wired to a CTP at the rear of teh mixer bay. If any extra
inputs were required the extras can conncet temporarilly to the CTP. However the source would not 
get a tally on the production stack.

By using spare outputs of the matrix as sources to the CTP, and telling the cue director software
about the route tally operation will occur. This routing data is stored in slots in the tally
infodriver. The purpose of this module is to enable the data in the infodriver to be edited.

*/

#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "MixerExtraIn.h"

#define MAIN_PNL	1
#define MAIN_PNL_NAME	"MixIn2540.bncs_ui"

#define TIMER_SETUP	1

#define SDIROUTERINST	"StC SDI Router"
#define TALLYINFODRV	"CTA Tally Infodriver"
#define MIX_SLOT25		181			// Tally Infodriver slot that holds mixer input 25 matrix destination
#define MIX_SLOT40		196			// Tally Infodriver slot that holds mixer input 40 matrix destination

/////////////////////////////////////////////////
// Make our class visible to the outside world //
/////////////////////////////////////////////////
EXPORT_BNCS_SCRIPT( MixerExtraIn )


//////////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP //
//////////////////////////////////////////////////
MixerExtraIn::MixerExtraIn(bncs_client_callback *parent, const char *path) : bncs_script_helper( parent, path )
{
	// Initialise working stores
	tally_drv = 0;
	sdi_drv = 0;
	selInput = 0;
	selDest = 0;

	for (int i = 0; i <= MIXER_PLUGGED; i++)	tallyVal[i] = 0;
	for (int i = 0; i <= MIXER_PLUGGED; i++)	newVal[i] = 0;
	for (int i = 0; i <= MIXER_PLUGGED; i++)	tallyList[i] = false;

	// Make panel resources available for access
	panelInit(MAIN_PNL, MAIN_PNL_NAME);

	controlDisable(MAIN_PNL, "Select");
	controlDisable(MAIN_PNL, "Deselect");
	controlDisable(MAIN_PNL, "Network");
	haveTally = false;
	haveDest = false;

	// Add rows of data to list component ready for user data to be added
	for (int idx = 1; idx <= MIXER_PLUGGED; idx++)
	{
		textPut("add", bncs_string("%1;;").arg(idx+24), MAIN_PNL, "Mixer");
	}

	// Read the driver IDs from instances
	if (!getDev(SDIROUTERINST, &sdi_drv))
	{
		debug("MixerExtraIn:: Failed to find SDI driver ID in Instances.xml\n");
	}

	if (!getDev(TALLYINFODRV, &tally_drv))
	{
		debug("MixerExtraIn:: Failed to find tally infodriver ID in Instances.xml\n");
	}

	// Show the main panel
	panelShow(MAIN_PNL, MAIN_PNL_NAME);

	// Register for revrtives from tally infodriver, and poll for current settings
	infoRegister(tally_drv, MIX_SLOT25, MIX_SLOT40);
	infoPoll(tally_drv, MIX_SLOT25, MIX_SLOT40);
	timerStart(TIMER_SETUP, 1000);					// Used to check for replies from infodriver
}


///////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN //
///////////////////////////////////////////////////
MixerExtraIn::~MixerExtraIn()
{
	if (tally_drv) infoUnregister(tally_drv);
}


///////////////////////////////////////////////////
// All button pushes and notifications come here //
///////////////////////////////////////////////////
void MixerExtraIn::buttonCallback(buttonNotify *b)
{
	if (b->panel() == MAIN_PNL)
	{
		if (b->id() == "Mixer")
		{
			// User has made a selection in the list box.
			bncs_string rowNum = "";
			int myId;

			// Ask control for the active row index. A value of -1 is returned if no row is selected.
			// Note that the row number is ZERO for the topmost row.
			textGet("selectedindex", MAIN_PNL, "Mixer", rowNum);	

			myId = rowNum.toInt();	// Convert to number

			selInput = (myId == -1) ? 0 : myId + 1;	// Save the index with 1 as lowest real value.
			
			if (!haveTally)
			{
				controlEnable(MAIN_PNL, "Deselect");	// Enable the Deselect this mixer input button
				haveTally = true;
			}

			// If we also have a destination selection enable the 'make route' control button
			if (haveDest) 	controlEnable(MAIN_PNL, "Select");

			return;
		}

		if (b->id() == "Dest")
		{
			selDest = b->value().toInt();
			if (!haveDest && haveTally)
			{
				// Enable the Deselect this mixer input button
				controlEnable(MAIN_PNL, "Select");
			}

			haveDest = true;
			
			// If a mixer input has already been selected enable the 'make route' command
			if (haveTally) controlEnable(MAIN_PNL, "Select");
			
			return;
		}

		if (b->id() == "Clear")
		{
			// User wishes to unhighlight any selections of mixer input or matrix destination.
			clearHighlight();
			return;
		}

		if (b->id() == "Select")
		{
			// Set mixer input source to selected matrix destination id.
			newVal[selInput] = selDest;			// Save new destination selection
			showInputData(selInput, selDest);	// Show updated destination on the control
			clearHighlight();					// Remove selections
			checkNetworkUpdateEnable();
			return;
		}

		if (b->id() == "Deselect")
		{
			// Set mixer input controlling destination to 0, disabling the mixer cue operation for this input.
			newVal[selInput] = 0;
			showInputData(selInput, 0);			// Update list box
			clearHighlight();					// Remove any highlighting
			checkNetworkUpdateEnable();
			return;
		}

		if (b->id() == "Network")
		{
			// User has asked to send any changes to the network
			for (int idx = 0; idx < MIXER_PLUGGED; idx++)
			{
				if (newVal[idx + 1] != tallyVal[idx + 1])
				{
					infoWrite(tally_drv, bncs_string(newVal[idx + 1]), MIX_SLOT25 + idx, true);
				}
				// The revertives will update the display in the map table
			}
			clearHighlight();
			controlDisable(MAIN_PNL, "Network");
			return;
		}

		if (b->id() == "Restore")
		{
			// User has asked to restore local edit table to match Tally Infodriver values
			for (int idx = 0; idx < MIXER_PLUGGED; idx++)
			{
				newVal[idx + 1] = tallyVal[idx + 1];		// Restore the value
				showInputData(idx + 1, newVal[idx + 1]);	// Update the control display
			}
			clearHighlight();
			checkNetworkUpdateEnable();
			return;
		}
	}
}


//////////////////////////////
// All revertives come here //
//////////////////////////////
int MixerExtraIn::revertiveCallback(revertiveNotify *r)
{
	int	offset = 0;
	int mtxDest = 0;
	bncs_string rowId, destName;

	if (r->device() == tally_drv)
	{
		// Registered for revertives from slots MIX_SLOT25 to MIX_SLOT40 inclusive.
		if ((r->index() >= MIX_SLOT25) && (r->index() <= MIX_SLOT40))
		{
			offset = r->index() - MIX_SLOT25 + 1;	// Compute offset into our  0..16 array of displayed data
			mtxDest = r->sInfo().toInt();			// Compute destination value
			tallyVal[offset] = mtxDest;

			tallyList[offset] = true;				// Note that tally has been seen from this index

			// By definition, what is in the tally infodriver is also the current route. So even if this user has changed
			// the route but not modified the infodriver by a network transmission, it also sets the current for this user.
			newVal[offset] = mtxDest;

			showInputData(offset, mtxDest);

			//checkNetworkUpdateEnable();
		}
	}

	return 0;
}


//////////////////////////////////////////////
// All database name changes come back here //
//////////////////////////////////////////////
void MixerExtraIn::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 MixerExtraIn::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 MixerExtraIn::timerCallback(int id)
{
	switch( id )
	{
	case TIMER_SETUP:
		// Check the number of tally infodriver revertives that have been received
		if (allRevertiveRx())
		{
			// Stop the timer
			timerStop(id);
			checkNetworkUpdateEnable();	// Enable/disable Network Update Button
		}
		else
		{
			// Repeat the poll, leave timer running
			debug("MixerExtraIn: Repeating poll of tally infodriver.\n");
			infoPoll(tally_drv, MIX_SLOT25, MIX_SLOT40);
		}
		break;

	default:	// Unhandled timer event
		timerStop(id);
		break;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////
// clearHighlight() is a support function that removes the active selection in both the listbox
// and the name grid.
void MixerExtraIn::clearHighlight(void)
{
	// User wishes to unhighlight any selections
	textPut("clearSelection", MAIN_PNL, "Mixer");
	textPut("index=-1", MAIN_PNL, "Dest");
	controlDisable(MAIN_PNL, "Select");
	controlDisable(MAIN_PNL, "Deselect");
	haveTally = false;
	haveDest = false;
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// function showInputData(input, destination) is a service routine called by several button action
// routines. It sets the data display for row (input-1) to the data associated with destination id
// passed as a parameter.
void MixerExtraIn::showInputData(int input, int destination)
{
	bncs_string rowId, dest_id;

	rowId = bncs_string("%1").arg(input - 1);		// The -1 is offset between 0 based and 1 based index space
	textPut("cell." + rowId + ".1", bncs_string(destination,'0',3U), MAIN_PNL, "Mixer");	// Set mixer dest display new destination

	if (destination)
		routerName(sdi_drv, 1, destination, dest_id);				// Get name from SDI driver
	else
		dest_id = "";

	dest_id.replace('|', ' ');	// Replace any line return marks with spaces

	textPut("cell." + rowId + ".2", dest_id, MAIN_PNL, "Mixer");	// Clear destination name

	if (newVal[input] != tallyVal[input])
	{
		// Tally infodriver value and current value differ add highlight mark to column 3
		textPut("cell." + rowId + ".3", "*", MAIN_PNL, "Mixer");
	}
	else
	{
		// Tally infodriver value and current value are the same, remove any highlight mark from column 3
		textPut("cell." + rowId + ".3", "", MAIN_PNL, "Mixer");
	}
}	// End of showInputData(int input, int destination)


////////////////////////////////////////////////////////////////////////////////////////////////////
// function checkNetworkUpdateEnable() is called to set the Network Update button to the enable or
// disable state. If the tallyVal[] store matches the newVal[] store, the control is disabled.
void MixerExtraIn::checkNetworkUpdateEnable(void)
{
	bool enable = false;

	// Network button should be active if there are any differences in the content of tables newVal[] and tallyVal[]
	for (int i = 0; i <= MIXER_PLUGGED; i++)
	{
		if (newVal[i] != tallyVal[i]) enable = true;
	}

	if (enable)
		controlEnable(MAIN_PNL, "Network");
	else
		controlDisable(MAIN_PNL, "Network");
}


////////////////////////////////////////////////////////////////////////////////////////////////////
// allRevertiveRx() is a support function that counts the number of "revertive received" flags set
// true by the revertive processing function.
bool MixerExtraIn::allRevertiveRx(void)
{
	int sum = 0;

	// Count the number of revertives received from tally infodriver
	for (int i = 0; i <= MIXER_PLUGGED; i++)
	{
		if (tallyList[i]) sum++;
	}

	return (sum == MIXER_PLUGGED) ? true : false;
}
