/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		GPI_Button.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			16th February 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a button/label that is intended for use with GPI drivers. Whilst much of the
functionality could be realised in an Enum button, more control is available with this script 
control. 

The selected contact is only accessible by using the entries in instances.xml. One extra parameter 
called "idtext" is read from the instance data. This is provided so that the user may set the 
button/label name in the instance file whilst leaving the altid property for use by the alarms
system.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

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

#define MAIN_PAN	1
#define PAN_NAME	"myButton.bncs_ui"
#define BTTN_NAME	"MyGPI_Button"

#define POLLTIMER	1
#define POLLPERIOD	10000

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

// Constructor - equivalent to ApplCore STARTUP
GPI_Button::GPI_Button( bncs_client_callback * parent, const char * path ) : bncs_script_helper( parent, path )
{
	// Initialise global data to default values
	m_myInstance = "";		// Default instance data for this button
	m_myText = "";			// Default text to display on the button
	m_myStyle = "";			// Default stylesheet
	m_myOnState = "";		// Default state for GPI=1
	m_myOffState = "";		// Default state for GPI=0
	
	m_myDeviceID = 0;		// The Device ID for the GPI driver
	m_myContact = 0;		// The contact ID for the GPI
	m_my_OnOffState = -1;	// Remembers the state of the contact received in a revertive. Setting -1 as initial value
							// forces the first change or revertive to update the storage

	m_myMode = true;		// Flag to indicate button or label style of display. Assume button
	m_Rx_revert = false;	// Set true when a revertive has been received
	m_useText = false;		// Set true to use text defined in script box dialog, false to use text defined in instances idtext
	m_sendUpdate = true;	// Set to false to stop new GPI levels being sent to driver

	// Start the background poll request timer
	timerStart(POLLTIMER, POLLPERIOD);

	// Show the panel defined in "myButton.bncs_ui", and we'll reference it as MAIN_PNL
	panelShow(MAIN_PAN, PAN_NAME);

}	// End of function GPI_Button::GPI_Button(....


// ============================================= //
// Destructor - equivalent to ApplCore CLOSEDOWN //
// ============================================= //
GPI_Button::~GPI_Button()
{
	// Remove any registrations for revertives
	if ((m_myDeviceID != 0) && (m_myContact != 0))
	{
		gpiUnregister(m_myDeviceID);
	}

	timerStop(POLLTIMER);
}


// ================================================== //
// All button pushes and notifications come via here. //
// ================================================== //
void GPI_Button::buttonCallback(buttonNotify *b)
{
	if ((b->panel() == MAIN_PAN) && (b->id() == BTTN_NAME))
	{
		// Toggle the last known state of the GPI contact,
		// provided we have a driver ID and contact ID
		if ((m_myDeviceID != 0) && (m_myContact != 0))
		{
			if (m_sendUpdate)
			{
				if (m_my_OnOffState != -1)
				{
					gpiSwitch(m_myDeviceID, (m_my_OnOffState == 0 ? 1 : 0), m_myContact, true);
				}
			}
		}
	}	// End if b->panel()...
}

// ======================== //
// All revertives come here //
// ======================== //
int GPI_Button::revertiveCallback(revertiveNotify *r)
{
	if ((r->device() == m_myDeviceID) && (r->index() == m_myContact))
	{
		// We have received a revertive for our GPI contact.
		// Set the internal status according to the received value, if
		// the status is different to what we have in the store.
		if (r->info() != m_my_OnOffState)
		{
			if (r->info() == 0)
			{
				// Low/Off
				textPut("statesheet", m_myOffState, MAIN_PAN, BTTN_NAME);
				m_my_OnOffState = 0;
			}
			else
			{
				// High/On
				textPut("statesheet", m_myOnState, MAIN_PAN, BTTN_NAME);
				m_my_OnOffState = 1;
			}

			// Finally, tell our host in case there is an active connection
			hostNotify(bncs_string("GPIvalue=%1").arg(m_my_OnOffState));
		}
	}

	return 0;
}


// ========================================= //
// All database name changes come back here. //
// ========================================= //
void GPI_Button::databaseCallback( revertiveNotify * r )
{
}


// ======================================================================== //
// All parent notifications come via here i.e. when this script is just one //
// component of another dialog then our host might want to tell us things   //
// ======================================================================== //
bncs_string GPI_Button::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if (p->value() == "all")
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string("useButton=%1").arg(m_myMode ? "true" : "false");
			sl << bncs_string("sendGPI=%1").arg(m_sendUpdate ? "true" : "false");
			sl << bncs_string("useText=%1").arg(m_useText ? "true" : "false");
			sl << bncs_string("buttonText=%1").arg(m_myText);
			sl << bncs_string("stateSheet=%1").arg(m_myStyle);
			sl << bncs_string("offState=%1").arg(m_myOffState);
			sl << bncs_string("onState=%1").arg(m_myOnState);

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

		else if (p->value() == "useButton")
		{
			// User wants to know if we are working in button or label mode
			return (bncs_string("%1=%2").arg(p->value()).arg(m_myMode ? "true" : "false"));
		}

		else if (p->value() == "sendGPI")
		{
			// User wants to know if we are sending button clicks to the driver
			return (bncs_string("%1=%2").arg(p->value()).arg(m_sendUpdate ? "true" : "false"));
		}

		else if (p->value() == "useText")
		{
			// User wants to know if we are using text from the control (=true) or instances.xml (=false)
			return (bncs_string("%1=%2").arg(p->value()).arg(m_useText ? "true" : "false"));
		}
		
		else if (p->value() == "buttonText")
		{
			// User wants to know what text is on the button.
			return (bncs_string("%1=%2").arg(p->value()).arg(m_useText ? m_myText : m_myInstance));
		}
	}

	// +++++++++++++++ //
	// Instance Change //
	// +++++++++++++++ //
	else if ((p->command() == "instance") && (p->value() != m_myInstance))
	{
		bncs_string IDtext = "";
		int dev = 0;
		int slot = 0;

		debug(bncs_string("Instance change code called - Instance=%1\n").arg(p->value()));
		
		// Is the new value different to the one we already know?
		if (p->value() != m_myInstance)
		{
			// Was previous Instance non-null - if so we may have a revertive registration to cancel.
			if (m_myInstance != "")
			{
				if ((m_myDeviceID != 0) && (m_myContact !=0))
				{
					gpiUnregister(m_myDeviceID);	// Unregister old contact revertive
				}
			}
			
			m_myInstance = p->value();		// Keep a note of the updated value
			
			// Ask for driver and contact info from Instances.xml
			getDev(m_myInstance, &dev, &slot);

			if (((dev != 0) && (dev < 1000)) && ((slot != 0) && (slot < 1000)))
			{
				// Have a valid new device and contact number.
				m_myDeviceID = dev;
				m_myContact = slot;
				gpiRegister(m_myDeviceID, m_myContact, m_myContact, false);	// Register new contact for revertives
				gpiPoll(m_myDeviceID, m_myContact, m_myContact);			// Find current state

				// Do we need to set the name onto the button from "idtext" text?
				if (m_useText)
				{
					textPut("text", m_myText, MAIN_PAN, BTTN_NAME);
				}
				else
				{
					// Get the field "idtext"
					bncs_config cfg(bncs_string("instances.%1").arg(m_myInstance));
					IDtext = cfg.attr("idtext");
					textPut("text", IDtext, MAIN_PAN, BTTN_NAME);
				}
			}
			else
			{
				debug("No valid device or contact\n");
			}


		}	// End of if (p->value() != m_myInstance)
	}	// End of Instance Change code 


	// +++++++++++++++++ //
	// Parameter Changes //
	// +++++++++++++++++ //

	else if (p->command() == "useButton")
	// Set the button/label operate mode
	{
		if (p->value().lower() == "false")
		{
			// Need to display as a label.
			m_myMode = false;
			textPut("style=label", MAIN_PAN, BTTN_NAME);
		}

		if (p->value().lower() == "true")
		{
			// Need to display as a clickable button.
			m_myMode = true;
			textPut("style=button", MAIN_PAN, BTTN_NAME);
		}
	}	// end of else if (p->command() == "useButton")

	else if (p->command() == "sendGPI")
	// sendGPI controls if clicks cause GPI changes to be sent to the driver
	{
		if (p->value().lower() == "false") m_sendUpdate = false;

		if (p->value().lower() == "true") m_sendUpdate = true;

	}	// End of else if (p->command() == "sendGPI")

	else if (p->command() == "useText")
	// Change of useText flag.
	{
		if (p->value().lower() == "false")
		{
			bncs_string myIDtext;

			// Need to see if the alternate text is available from the instances entry. If not, will show a blank entry
			m_useText = false;
			bncs_config cfg(bncs_string("instances.%1").arg(m_myInstance));
			myIDtext = cfg.attr("idtext");
			textPut("text", myIDtext, MAIN_PAN, BTTN_NAME);
		}

		if (p->value().lower() == "true")
		{
			// Show the text loaded by the user into data field
			m_useText = true;
			textPut("text", m_myText, MAIN_PAN, BTTN_NAME);
		}
	}	// end of else if (p->command() == "useText")

	else if (p->command() == "buttonText")
	// Define the text to be used on the button. Entered in the persisting data of the script host.
	{
		m_myText = p->value();	// Keep a note of the text
		// Are we in display text mode? If so show the new text
		if (m_useText)
		{
			textPut("text", m_myText, MAIN_PAN, BTTN_NAME);
		}
	}

	else if (p->command() == "stateSheet")
	// The style sheet for the control
	{
		m_myStyle = p->value();
		textPut("stylesheet", m_myStyle, MAIN_PAN, BTTN_NAME);
	}

	else if (p->command() == "offState")
	//	Set the state for switch in the off state
	{
		m_myOffState = p->value();
		if (m_my_OnOffState == 0) textPut("statesheet", m_myOffState, MAIN_PAN, BTTN_NAME);
	}

	else if (p->command() == "onState")
	// Set the state for switch in the on state
	{
		m_myOnState = p->value();
		if (m_my_OnOffState == 1) textPut("statesheet", m_myOnState, MAIN_PAN, BTTN_NAME);
	}


	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if (p->command() == "_events")
	{	// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;

		sl << "GPIvalue=*";		
		
		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 << "useButton=[value]";
		sl << "sendGPI=[value]";
		sl << "useText=[value]";
		sl << "buttonText=[value]";
		
		return sl.toString( '\n' );
	}

	return "";

}	// End of function bncs_string GPI_Button::parentCallback(parentNotify *p)


// ====================== //
// Timer events come here //
// ====================== //
void GPI_Button::timerCallback( int id )
{
	switch( id )
	{
	case POLLTIMER:
		if ((m_myDeviceID != 0) && (m_myContact != 0))
		{
			gpiPoll(m_myDeviceID, m_myContact, m_myContact);
		}
		break;

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


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


