/*
PROGRAM NAME	CTA_CAR.DLL

USED BY			BBC Academy Studio A/CTA installation

VERSION			1.0
DATE			21st March 2015
PROGRAMMER		Andy Woodhouse

Overview
========
This panel enables users in various areas to manage the signals returned from the CTA to the CAR audio and video routers.

During the CTA design phase, the hardware designer decided to support 8 audio returns from the CTA matrix, but
only support 4 lines direct from the video matrix. A further 4 video inputs to the CAR router are supported by
plugging sources into the jackfield in CTA. This design decision means that this task direct manages the selector
buttons and tallies.

The destination packages are a sequence of id's, and are part of the "fixed id" part of the package names database
*/

#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "CTA_CAR.h"

#define MAIN_PANEL	1
#define MAIN_PANEL_NAME "CTA_CAR.bncs_ui"

#define LEVEL_ENABLE	"/images/SmallTick.png"
#define LEVEL_DISABLE	"/images/SmallCross.png"
#define BTN_SELECTED	"WNSourceSelected"
#define BTN_DESELECTED	"WNSourceDeselected"
#define TAKE_HIGH	"WN_TakeHigh"
#define TAKE_LOW	"WN_TakeLow"

#define DEST_PKG1 446	// First of 8 destination packages

#define TAKE_TIMER	1

// Make our class visible to the outside world
EXPORT_BNCS_SCRIPT( CTA_CAR )


// ++++++++++++++++++++++++++++++++++++++++++++
// Constructor - equivalent to ApplCore STARTUP
// ++++++++++++++++++++++++++++++++++++++++++++
CTA_CAR::CTA_CAR(bncs_client_callback *parent, const char *path ) : bncs_script_helper(parent, path)
{
	// Initialise variables
	package_names_id = 0;		// BNCS driver ID for CTA package names
	package_xpts_id = 0;		// BNCS driver ID for CTA Package contents (format xx,yy where xx is video, yy is audio)
	video_id = 0;				// BNCS driver ID for CTA video matrix 
	audio_id= 0;				// BNCS driver ID for CTA audio matrix
	source_pkg_id = 0;			// Holds the current active package number, or -1 for no selection
	dest_pkg_id = 0;			// Holds the current destination package number, or -1 for no selection
	dest_btn = 0;				// Holds the button number of the active destination, or 0 for no selection
	first_video = 0;			// Holds the destination id for the first video destination of CTA matrix
	first_audio = 0;			// Holds the destination id for the first audio destination of CTA matrix
	take_count = 0;				// Used by the take timeout processing
	undo_video_source = 0;		// Used by the UNDO process
	undo_audio_source = 0;
	undo_video_dest = 0;
	undo_audio_dest = 0;

	for (int i = 0; i <= 8; i++) video_tally[i] = 0;
	for (int i = 0; i <= 8; i++) audio_tally[i] = 0;

	take_enabled = false;			// Set true when the Take button is enabled 
	take_timer_active = false;		// Set true when the take timeout process is active.
	undo_enabled = false;			// Set true when Undo button is active
	video_selected = true;			// Set true when video level should be switched
	audio_selected = true;			// Set true when audio level should be switched

	// Get the BNCS driver numbers for CTA matricies
	getDev("CTA Package Names", &package_names_id);
	getDev("CTA Package XPTS", &package_xpts_id);
	getDev("CTA SDI Router", &video_id);
	getDev("CTA Audio Router", &audio_id);

	// Show our panel
	panelShow(MAIN_PANEL, MAIN_PANEL_NAME);

	controlDisable(MAIN_PANEL, "Take");		// Make sure that the Take button is disabled
	controlDisable(MAIN_PANEL, "Undo");		// Make sure that the Undo button is disabled

	// Set the level enables
	textPut("pixmap", video_selected ? LEVEL_ENABLE : LEVEL_DISABLE, MAIN_PANEL, "videolevel");
	textPut("pixmap", audio_selected ? LEVEL_ENABLE : LEVEL_DISABLE, MAIN_PANEL, "audiolevel");

	// Put the names on the destination buttons
	for (int i = 1; i <= 8; i++)
	{
		routerName(package_names_id, 1, DEST_PKG1 + i - 1, bttn_name);	// Read name from database
		textPut("text", bttn_name, MAIN_PANEL, bncs_string("Dest_%1").arg(i));
	}

	// Get the first video and audio destination to be monitored
	extract_xpts(DEST_PKG1, 1, &first_video, &first_audio);

	// Register for revertives
	routerRegister(video_id, first_video, first_video + 3);		// 4 video destinations to monitor
	routerRegister(audio_id, first_audio, first_audio + 7);		// 8 audio destinations to monitor

	// Poll for initial states
	routerPoll(video_id, first_video, first_video + 3);
	routerPoll(audio_id, first_audio, first_audio + 7);
}


// +++++++++++++++++++++++++++++++++++++++++++++
// Destructor - equivalent to ApplCore CLOSEDOWN
// +++++++++++++++++++++++++++++++++++++++++++++
CTA_CAR::~CTA_CAR()
{
	// Unregister routers
	routerUnregister(video_id);
	routerUnregister(audio_id);
}


// ++++++++++++++++++++++++++++++++++++++++++++++
// All button pushes and notifications come here.
// ++++++++++++++++++++++++++++++++++++++++++++++
void CTA_CAR::buttonCallback( buttonNotify *b )
{
	if (b->panel() == MAIN_PANEL)
	{
		if (b->id() == "Sources")				// Change of source id?
		{
			if (b->command() == "index")		// Defensive programming - just in case we add extra outputs in the future
			{
				source_pkg_id = b->value().toInt();		// Extract the new source value
				clear_undo();							// If undo button is enabled - disable it

				// Check if take needs to be enabled
				checkTake();
				return;
			}
		}

		if (b->id().startsWith("Dest_"))		// New destination selection?
		{
			if (b->id().firstInt() != dest_btn)	textPut("statesheet", BTN_DESELECTED, MAIN_PANEL, bncs_string("Dest_%1").arg(dest_btn));	// Clear old highlight
			clear_undo();						// If undo button is enabled - disable it

			// Save new destination id
			dest_btn = b->id().firstInt();	

			// Highlight the new button
			textPut("statesheet", BTN_SELECTED, MAIN_PANEL, bncs_string("Dest_%1").arg(dest_btn));	

			// See if the take button should be enabled
			checkTake();
			return;
		}

		if (b->id() == "videolevel")			// Video level enable toggle?
		{
			video_selected = video_selected ? false : true;	// Toggle the flag
			textPut("pixmap", video_selected ? LEVEL_ENABLE : LEVEL_DISABLE, MAIN_PANEL, "videolevel");
			clear_undo();
			return;
		}

		if (b->id() == "audiolevel")			// Audio level enable toggle?
		{
			audio_selected = audio_selected ? false : true;	// Toggle the flag
			textPut("pixmap", audio_selected ? LEVEL_ENABLE : LEVEL_DISABLE, MAIN_PANEL, "audiolevel");
			clear_undo();
			return;
		}

		if (b->id() == "Take")					// Take button pressed?
		{
			doTake();	
			return;
		}

		if (b->id() == "Undo")					// Undo button pressed?
		{
			do_Undo();
			return;
		}

		if (b->id() == "Clear")					// Clear button pressed?
		{
			clear_selections();
			return;
		}

	}	// End of if (b->panel() == MAIN_PANEL)
}


// +++++++++++++++++++++++++
// All revertives come here.
// +++++++++++++++++++++++++
int CTA_CAR::revertiveCallback( revertiveNotify * r )
{

	if (r->device() == video_id)
	{
		video_tally[r->index() - first_video + 1] = r->info();	// Keep a copy of the revertive for undo operations
		textPut("text", r->sInfo(), MAIN_PANEL, bncs_string("VidTally_%1").arg(r->index() - first_video + 1));
		return 0;
	}

	if (r->device() == audio_id)
	{
		audio_tally[r->index() - first_audio + 1] = r->info();	// Keep a copy of the revertive for undo operations
		textPut("text", r->sInfo(), MAIN_PANEL, bncs_string("AudTally_%1").arg(r->index() - first_audio + 1));
		return 0;
	}
	
	return 0;
}


// +++++++++++++++++++++++++++++++++++++++++
// All database name changes come back here.
// +++++++++++++++++++++++++++++++++++++++++
void CTA_CAR::databaseCallback( revertiveNotify * r )
{
	// Name changes to audio sources will be delt with by the re-transmit of tally data.
	// Destination names are froom fixed packages - so names do not change.
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// All parent notifications come here. For example when this script
// is just one component of another dialog, our host might want to
// tell us things.
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bncs_string CTA_CAR::parentCallback( parentNotify *p )
{
	if( p->command() == "return" )
	{
		if( p->value() == "all" )
		{	// Persisting values for bncs_vis_ed
			bncs_stringlist sl = "";
			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 = "";
		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 = "";
		return sl.toString( '\n' );
	}

	return "";
}


// +++++++++++++++++++++++
// Timer events come here.
// +++++++++++++++++++++++
void CTA_CAR::timerCallback( int id )
{
	switch( id )
	{
	case TAKE_TIMER:
		// Decrement the take counter, then toggle the Take button state
		take_count--;
		if ((take_count & 1) == 0)	// Change the Take button highlighting
			textPut("statesheet", TAKE_HIGH, MAIN_PANEL, "Take");
		else
			textPut("statesheet", TAKE_LOW, MAIN_PANEL, "Take");
		if (take_count == 0)
		{
			timerStop(TAKE_TIMER);
			controlDisable(MAIN_PANEL, "Take");
			take_timer_active = false;
		}
		break;

	default:	// Unhandled timer event
		timerStop(id);
		break;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////


void CTA_CAR::extract_xpts(int pkg_id, int database, int *viddest, int *auddest)
{
	// pkg is the page number to process, databases is 0 for sources, 1 for destinations
	// viddest and auddest are the addresses for the results.
	bncs_string xptlist, p1, p2;

	routerName(package_xpts_id, database, pkg_id, xptlist);	// Read comma delimited string
	xptlist.split(',', p1, p2);								// Divide into two parts
	if (p1 == "-") *viddest = 0;
	else *viddest = p1.toInt();
	if (p2 == "-") *auddest = 0;
	else *auddest = p2.toInt();
}


// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
// function clear_selections() is called to action the //
// cleardown of all selected sources and destinations. //
// +++++++++++++++++++++++++++++++++++++++++++++++++++ //
void CTA_CAR::clear_selections(void)
{
	// Is Take enabled - if so disable and stop timer
	if (take_enabled == true)
	{
		controlDisable(MAIN_PANEL, "Take");
		textPut("statesheet", TAKE_HIGH, MAIN_PANEL, "Take");
		take_enabled = false;
	}
	if (take_timer_active)
	{
		timerStop(TAKE_TIMER);
		take_timer_active = false;
	}

	// If UNDO is enabled - disable it
	if (undo_enabled == true)
	{
		controlDisable(MAIN_PANEL, "Undo");
		undo_enabled = false;
		undo_video_source = 0;
		undo_audio_source = 0;
		undo_video_dest = 0;
		undo_audio_dest = 0;
	}

	// If a source is selected - deselect it
	if (source_pkg_id > 0)
	{
		source_pkg_id = -1;
		textPut("select=-1", MAIN_PANEL, "Sources");
	}

	// If a destination is enabled - deselect it
	if (dest_btn > 0)
	{
		textPut("statesheet", BTN_DESELECTED, MAIN_PANEL, bncs_string("Dest_%1").arg(dest_btn));
		dest_btn = 0;
	}

}	// End of clear_selections()


// +++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// checkTake() looks at the source and destination data, //
// and enables the TAKE button if valid data present.    //
// +++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void CTA_CAR::checkTake(void)
{
	if ((source_pkg_id > 0) && (dest_btn > 0))
	{
		// Have source and destination packages available.
		if (take_enabled)
		{
			textPut("statesheet", TAKE_HIGH, MAIN_PANEL, "Take");
			timerStop(TAKE_TIMER);
		}

		controlEnable(MAIN_PANEL, "Take");
		take_enabled = true;
		take_count = 10;
		timerStart(TAKE_TIMER, 1000);
		take_timer_active = true;
	}
	else
	{
		// Stop any TAKE enables etc
		if (take_timer_active)
		{
			timerStop(TAKE_TIMER);
			take_timer_active = false;
		}

		if (take_enabled)
		{
			textPut("statesheet", TAKE_HIGH, MAIN_PANEL, "Take");
			controlDisable(MAIN_PANEL, "Take");
			take_enabled = false;
		}
	}	// End else clause
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
// Function doTake() is called to action the Take function. //
// A source package and destination package are selected.   //
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //
void CTA_CAR::doTake(void)
{
	bncs_string sp, dp;
	int sv, sa, dv, da;	// Temporary variables that hold the source and destination video and audio crosspoints

	// Clear down the TAKE button and counter
	if (take_timer_active)
	{
		timerStop(TAKE_TIMER);			// Stop the take timer
		take_timer_active = false;
	}
	textPut("statesheet", TAKE_HIGH, MAIN_PANEL, "Take");
	controlDisable(MAIN_PANEL, "Take");

	extract_xpts(source_pkg_id, 0, &sv, &sa);			// Get the source audio and video id's

	dest_pkg_id = DEST_PKG1 + dest_btn - 1;				// Compute the destination package
	extract_xpts(dest_pkg_id, 1, &dv, &da);				// Get the destination audio and video id's

	// Make copies of source and destination data for the UNDO operation
	undo_video_source = video_tally[dest_btn];
	undo_audio_source = audio_tally[dest_btn];
	undo_video_dest = dv;
	undo_audio_dest = da;

	// Issue the router crosspoint commands. Requires level enable, and non-zero sources and destinations
	if (video_selected && (sv != 0) && (dv != 0))	routerCrosspoint(video_id, sv, dv);
	if (audio_selected && (sa != 0) && (da != 0))	routerCrosspoint(audio_id, sa, da);

	// Clear the selections
	textPut("select=-1", MAIN_PANEL, "Sources");
	source_pkg_id = -1;
	textPut("statesheet", BTN_DESELECTED, MAIN_PANEL, bncs_string("Dest_%1").arg(dest_btn));
	dest_btn = 0;
	dest_pkg_id = 0;

	// Enable the UNDO button
	controlEnable(MAIN_PANEL, "Undo");
	undo_enabled = true;

}	// End of function doTake()


// +++++++++++++++++++++++++++++++++++++++++++
// function do_Undo() is called to execute the
// commands needed to do an UNDO operation
// +++++++++++++++++++++++++++++++++++++++++++
void CTA_CAR::do_Undo(void)
{
	// Only get here is the UNDO button was enabled.
	undo_enabled = false;					// Clear the flag
	controlDisable(MAIN_PANEL, "Undo");		// Disable the UNDO button

	// Restore the video and audio levels - assuming non-zero contents and level is enabled
	if ((video_selected) && (undo_video_source != 0) && (undo_video_dest != 0))
		routerCrosspoint(video_id, undo_video_source, undo_video_dest);
	if ((audio_selected) && (undo_audio_source != 0) && (undo_audio_dest != 0))
		routerCrosspoint(audio_id, undo_audio_source, undo_audio_dest);
	// Clear working data stores
	undo_video_source = 0;
	undo_audio_source = 0;
	undo_video_dest = 0;
	undo_audio_dest = 0;
}

// +++++++++++++++++++++++++++++++++++
// function clear_undo(void) is called
// to switch off the undo facility.
// +++++++++++++++++++++++++++++++++++
void CTA_CAR::clear_undo(void)
{
	if (undo_enabled == true)
	{
		controlDisable(MAIN_PANEL, "Undo");
		undo_enabled = false;
		undo_video_source = 0;
		undo_audio_source = 0;
		undo_video_dest = 0;
		undo_audio_dest = 0;
	}
}
