/*
PROGRAM NAME	TV_Mon_Select_PKG.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			25th March 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This DLL implements a scripted component that has an image of a TV monitor, a background behind the monitor that can be
set to indicate if the component is active, and a Under Monitor Display (UMD) that shows the current source routed to
the destination controlled by the component. This version of the component is intended for use with a WN package source
selector. This system has a BNCS device that has teh package names, and a second device that has a comma demimited list 
of sources or destinations. For Studio C the package has three levels - SDI, analogue audio, AES audio.

The user of the script panel may adjust a number of parameters in this component. The parameters that can be set are:
	instance			Name of the instance that provides the package router matrix and destination to be switched.
	sdiID				Name of the instance that provides the SDI router ID that will be switched.
	xptID				Name of the instance that provides the BNCS router ID that holds the crosspoint data.
	offState			The name of the statesheet to be used for the monitor background when the component is not selected.
	onState				The name of the statesheet to be used for the monitor background when the component is selected/active.
	
Run time messages accepted by the component are:	
	sourceID			Tells the component the source to route, if the component is "Active"
	clear=1				Tells the component to become inactive for routing (deselected state)
	
Return notifications:
	clear=1				Emitted when user presses the monitor icon to activate a component instance.
	setindex=-1			Emitted a short time after a new source has been routed. Used to send a "clear selection"
						message to source selecter grid. The short delay allows the highlight so be seen on the
						source selector to give the user confidence that they have pressed a selection button.
	
*/

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

#define MAIN_PANEL	1
#define PANEL_NAME "MyMonitor.bncs_ui"

#define BNCS_MIN_DRIVER 1
#define BNCS_MAX_DRIVER 999
#define BNCS_MIN_DEST 1
#define BNCS_MIN_SOURCE 1
#define BNCS_SRC_DBASE 0
#define BNCS_DEST_DBASE 1

#define TIMER_POLL	1
#define TIMER_CLR_SELECT 2				// Timer used to issue clear message back to connections/host
#define TIMER_CLR_SELECT_TIME 250		// Delay 250 ms after issue crosspoint make request

// Make our class visible to the outside world
EXPORT_BNCS_SCRIPT(TV_Mon_Select_PKG)

// Constructor - equivalent to ApplCore STARTUP
TV_Mon_Select_PKG::TV_Mon_Select_PKG(bncs_client_callback * parent, const char *path) : bncs_script_helper( parent, path )
{
	// Initialise required variables
	m_instance = "";
	m_sdiID_name = "";
	m_xptID_name = "";
	deselectedStatesheet = "";
	selectedStatesheet = "";

	selectedSource = 0;		// Holds Source package number
	m_selSrcSDI = 0;		// Holds Source SDI crosspoint requested
	m_instancePkgXpt = 0;			// Holds BNCS driver ID extracted from the instance data
	m_destID = 0;			// Holds destination package extracted from the instance data
	m_destIDsdi = 0;		// Holds Destination SDI crosspoint
	m_sdiID = 0;			// Holds BNCS SDI matrix ID
	m_revertIDreg = 0;		// Holds the device ID that is registered for revertives.

	I_am_active = false;
	revertiveRegistered = false;		// No revetive registration as yet.
	revertiveRx = false;
	hnTimerActive = false;

	// Show our panel
	panelShow(MAIN_PANEL, PANEL_NAME);

	// Set initial selection status to unselected
	textPut("statesheet", deselectedStatesheet, MAIN_PANEL, "StateIndicate");
}


// Destructor - equivalent to ApplCore CLOSEDOWN
TV_Mon_Select_PKG::~TV_Mon_Select_PKG()
{
	// Tidy up by deleting any revertive definitoions
	if (revertiveRegistered) routerUnregister(m_sdiID);

	//Stop any active timers
	if (!revertiveRx)	timerStop(TIMER_POLL);		// Stop revertive re-poll timer
	if (hnTimerActive) timerStop(TIMER_CLR_SELECT);	// Stop hostNotify delay timer
}


////////////////////////////////////////////////////////////////////////////////
// All button pushes and notifications come here.
// ==============================================
// For this component all messages should come from visual element "TV_Mon",
// which indicate that the user is making the current panel into the active panel.
// Hence this component must notify it's host that the other possible destinations
// should go into inactive state.
void TV_Mon_Select_PKG::buttonCallback( buttonNotify *b )
{
	if (b->panel() == MAIN_PANEL)
	{
		if (b->id() == "TV_Mon")
		{
			if (!I_am_active)
			{
				// Make this panel active
				I_am_active = true;		// Set flag to new state
				textPut("statesheet", selectedStatesheet, MAIN_PANEL, "StateIndicate");		// Highlight our monitor icon
			}
			hostNotify("clear=1");		// But tell our host to inform other panel to clear their highlights
		}
	}
}


////////////////////////////////////////////////////////////////////////////////
// All revertives come here.
//
// We get revertives only for the SDI destination we need to monitor
int TV_Mon_Select_PKG::revertiveCallback(revertiveNotify *r)
{
	if ((r->device() == m_sdiID) && (r->index() == m_destIDsdi))
	{
		// We use default source names database, so r->sInfo() tells us the name
		textPut("text", r->sInfo(), MAIN_PANEL, "UMD");

		if (!revertiveRx)
		{
			// Waiting for a revertive, now received, stop background poll action.
			revertiveRx = true;
			timerStop(TIMER_POLL);
		}
	}

	return 0;
}


////////////////////////////////////////////////////////////////////////////////
// All database name changes come back here
// No database name changes for this component
void TV_Mon_Select_PKG::databaseCallback(revertiveNotify *r)
{
	if ((r->device() == m_sdiID) && (r->database() == BNCS_SRC_DBASE) && (r->info() == m_selSrcSDI))
	{
		// The SDI source on our UMD has a new name, apply update.
		textPut("text", r->sInfo(), MAIN_PANEL, "UMD");
	}
}


////////////////////////////////////////////////////////////////////////////////
// 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 TV_Mon_Select_PKG::parentCallback( parentNotify *p )
{
	if (p->command() == "sourceID")
	{
		// Instruction to route a new source, provided source number is > 0 and
		// we are an active panel. The processing must extract the SDI crosspoint 
		// from the package ID.
		bncs_string cdt, vid, residue;

		if (I_am_active)
		{
			selectedSource = p->value().toInt();	// Extract the package source ID

			if (selectedSource >= BNCS_MIN_SOURCE)	// Only process if package_id is finite and positive
			{
				routerName(m_instancePkgXpt, BNCS_SRC_DBASE, selectedSource, cdt);	// Fetch comma delimited source string
				cdt.split(',', vid, residue);			// Extract the sdi video source number string to "vid"
				m_selSrcSDI = vid.toInt();				// Convert string to an integer

				if (m_selSrcSDI >= BNCS_MIN_SOURCE)
				{
					routerCrosspoint(m_sdiID, m_selSrcSDI, m_destIDsdi);
				}
			}

			timerStart(TIMER_CLR_SELECT, TIMER_CLR_SELECT_TIME);	// Trigger hostNotify("setindex=-1") to host. 
			hnTimerActive = true;
		}
		return "";
	}

	else if (p->command() == "clear")
	{
		// Instruction to deselect the active state
		if (p->value() == "1")
		{
			// We have received a "clear=1" command so if this panel is active
			// place it in deselected mode.
			if (I_am_active)
			{
				textPut("statesheet", deselectedStatesheet, MAIN_PANEL, "StateIndicate");
				I_am_active = false;
			}
		}

		return "";
	}

	else if (p->command() == "return")
	{
		if (p->value() == "all")
		{	
			// Persisting values for bncs_vis_ed use.
			bncs_stringlist sl;
			
			sl << bncs_string("sdiID=%1").arg(m_sdiID_name);
			sl << bncs_string("offState=%1").arg(deselectedStatesheet);
			sl << bncs_string("onState=%1").arg(selectedStatesheet);
			
			return sl.toString('\n');
		}

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

		else if (p->value() == "offState")
		{
			// Specific value being asked for by a textGet - State sheet name for "off"
			return(bncs_string("%1=%2").arg(p->value()).arg(deselectedStatesheet));
		}

		else if (p->value() == "onState")
		{
			// Specific value being asked for by a textGet - State sheet name for "on"
			return(bncs_string("%1=%2").arg(p->value()).arg(deselectedStatesheet));
		}
	}

	else if ((p->command() == "instance") && (p->value() != m_instance))
	{	
		// Our instance is being set/changed. This may require registration for revertives for UMD display
		int dev, dest;
		bncs_string cdt, sdi_part, residue;		// cdt = comma delimited text
		
		m_instance = p->value();
		
		// Extract the crosspoint comma delimited list BNCS driver ID and crosspoint index data
		getDev(m_instance, &dev, &dest);

		if ((dev >= BNCS_MIN_DRIVER) && (dev <= BNCS_MAX_DRIVER))	// Check device ID is in range.
		{
			m_instancePkgXpt = dev;
			m_destID = dest;

			// Now extract the SDI destination from the crosspoint list
			if (m_destID >= BNCS_MIN_SOURCE)
			{
				routerName(m_instancePkgXpt, BNCS_DEST_DBASE, m_destID, cdt);
				cdt.split(',', sdi_part, residue);
				m_destIDsdi = sdi_part.toInt();
			}
			// See if there is data to create an sdi registration, and register if valid.
			validateSDIdest();
		}

		return "";
	}


	else if ((p->command() == "sdiID") && (p->value() != m_sdiID_name))
	{
		// Instance used to define the SDI router is being set
		int dev, dest;

		m_sdiID_name = p->value();		// Save new name
		
		getDev(m_sdiID_name, &dev, &dest);	// Extract the device number
		if ((dev >= BNCS_MIN_DRIVER) && (dev <= BNCS_MAX_DRIVER))
		{
			m_sdiID = dev;				// Save SDI router ID

			// Check if registration is affected, update as needed
			validateSDIdest();
		}

		return "";
	}


	else if (p->command() == "offState")
	{	
		// The statesheet used for deselected state is being set
		deselectedStatesheet = p->value();

		if (!I_am_active)
		{
			// Need to update our display with new statesheet value
			textPut("statesheet", deselectedStatesheet, MAIN_PANEL, "StateIndicate");
		}
		return "";
	}

	else if (p->command() == "onState")
	{
		// The statesheet used for deselected state is being set
		selectedStatesheet = p->value();

		if (I_am_active)
		{
			// Need to update our display with new statesheet value
			textPut("statesheet", deselectedStatesheet, MAIN_PANEL, "StateIndicate");
		}
		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 << "clear=*";	
		sl << "setindex=*";
		
		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 << "sourceID=[value]";
		sl << "clear=[value]";

		return sl.toString( '\n' );
	}

	return "";
}

// timer events come here
void TV_Mon_Select_PKG::timerCallback( int id )
{
	switch( id )
	{
	case TIMER_POLL:
		// Timeout occured without revertive being received - so issue a re-poll.
		// Timer is stoped by recepit of revertive.
		routerPoll(m_sdiID, m_destIDsdi, m_destIDsdi);
		break;


	case TIMER_CLR_SELECT:
		// When this timer ticks, send hostNotify message to clear down selection
		// then stop the timer.
		hostNotify("setindex=-1");
		timerStop(TIMER_CLR_SELECT);
		hnTimerActive = false;
		break;

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


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


////////////////////////////////////////////////////////////////////////////////
// Function void validateSDIdest(void) is called whenever the "instance" or
// "sdiID" or "xptID" are updated by the host. If there is an existing
// registration, it is cancelled and new data computed. If there is a valid 
// combination of package device ID, package destination, SDI router device ID, and
// SDI destination ID a new revertive registration is made, and the destination
// polled. All required data is available in the private global data stores.
//
void TV_Mon_Select_PKG::validateSDIdest(void)
{
	// Is there an existing revertive destination? If so, cancel it.
	if (revertiveRegistered)
	{
		routerUnregister(m_revertIDreg);	// Unregister the revertive
		revertiveRegistered = false;		// Clear the flag
		m_revertIDreg = 0;					// Clear the device id
	}

	// Test the variables for valid content. Valid means we have: 
	// 1) A non-zero crosspoint comma delimited device ID - variable m_instancePkgXpt
	// 2) A non-zero destination package ID - variable m_destID
	// 3) A non-zero sdi driver ID - variable m_sdiID
	// 4) A non-zero sdi destination id - variable m_destIDsdi
	
	if (((m_instancePkgXpt >= BNCS_MIN_DRIVER) && (m_instancePkgXpt <= BNCS_MAX_DRIVER)) &&
		(m_destID >= BNCS_MIN_SOURCE) &&
		(m_sdiID >= BNCS_MIN_SOURCE) &&
		(m_destIDsdi >= BNCS_MIN_SOURCE))
	{
		// Valid combination found, register for revertives on m_destIDsdi
		routerRegister(m_sdiID, m_destIDsdi, m_destIDsdi);
		revertiveRegistered = true;
		m_revertIDreg = m_sdiID;
		routerPoll(m_sdiID, m_destIDsdi, m_destIDsdi);
		
		// Start timer to repeat the poll if get no reply after 10 seconds.
		timerStart(TIMER_POLL, 10000);
	}

}
