////////////////////////////////////////////////////////////////////////////////
// Problems with the available libraries under Visual Studio 2013 comilation and
// linking meant that it was desirable to create a "clean" version of the
// support library written by David Yates of ATOS.

#include "stdafx.h"
#include "awExtInfo.h"

/*************************************/
/* Written by David Yates            */
/* Copyright Siemens IT Systems 2007 */
/*************************************/
#pragma warning (disable :4786)
#include "cc.h"
#include "AwCcConfig.h"
#include "include\bncs.h"
#include "include\bbc_mxc.h"
#include "include\bncs32.h"
#include "include\bncswm32.h"
#include "include\bncsif32.h"
#include "include\decodebncsstring.h"

#ifndef HWND_MESSAGE
#define HWND_MESSAGE     ((HWND)-3)
#endif

/**
\class awExtInfo
\brief A BNCS Infodriver class

This is a class that provides full Infodriver functionality for BNCS drivers.
\par Usage Example
\code
class myDriver: public IccInfodriverCallback
{
public:
myDriver( int device )
{
  m_info = new awExtInfo( this );
  m_info->connect( device );
};
virtual ~myDriver() {	delete m_info; };

protected:
// Reimplement those functions we need (declared in IccInfodriverCallback)
void cciSlotRequest( int device, int index, const string & slot )
{
  Debug("slot request. Device %d, index %d contents %s", device, index, slot.c_str());
};

private:
awExtInfo *m_info;
}
\endcode

Redundancy
==========
Multiple infodrivers of the same identity can run on a BNCS network. There is a
simple mechanism to enable automatic changeover in case of failure which takes a
previously passive device on the network and enable it to take over.

Modes
=====
There are a couple of distinct modes that redundancy can run in. Most of the
time either of two (or more) drivers can run with any one of them able to 
provide the service to the network. This is the cc::multipleControllerCapable
mode.

Occasionally, however, there are devices such as serially controlled units where
there is only a single control port that cannot be shared. This is 
cc::singleControllerCapable mode. In this case only one instance of the driver
can be active on the network at one time - importantly in this mode if a driver
detects it has active comms it will aggresively try and force itself onto the
network since no other driver can have communications with the controlled device.
*/


/** Construct a Colledia Control infodriver class and tell it where to send notifications
(ie pass it an interface to the class to callback). If the callback is not specified this
class will be able to get slots but be unable to receive asynchronous notifications of
change of state etc.
\param[in] callback pointer to the class that implements the IccInfodriverCallback class
\param[in] debugStringHeader The member functions of this class generate debug messages for out of range values to which this string is prepended. A message such as "Error: Device=0" message in itself is not useful without some context!
*/
awExtInfo::awExtInfo(IccInfodriverCallback * callback, const string & debugStringHeader) : ccCheck(debugStringHeader)
{
	m_redundancyMode = cc::multipleControllerCapable;	// Set default mode to multi-controller mode
	m_driverState = cc::ok;								// Assume that everything will work.
	m_redundancyState = cc::unknown;					// We do not know status until a connection is made

	// Initialise a few variables
	m_callback = callback;								// Save the pointer to the callback routine
	m_hInfo = 0;										// Handle to which to send infodriver messages
	m_hWndMessage = 0;
	m_ipc = cc::none;									// Communications mechanism.
	m_ipcOptions = 0;									// No options yet available.
	m_workstation = AwCcConfig::workstation();			// 

	// Register a messaging window for comms with infodriver.
	WNDCLASS wc;

	memset(&wc, 0, sizeof(wc));							// Most of the entries will be zero, so fill the memory with 0

	wc.lpszClassName = "awExtInfoMessageWindow";		// Provide a name for the messaging interface
	wc.cbWndExtra = sizeof(awExtInfo *);				
	wc.lpfnWndProc = WndProc;							// Address of the callback processor

	RegisterClass(&wc);

	// Create the messaging window
	m_hWndMessage = CreateWindow(wc.lpszClassName, "", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, this);
	if (!m_hWndMessage)
		::MessageBox(0, "Cannot create awExtInfoMessageWindow", "Very Serious Error!", 0);
}

awExtInfo::~awExtInfo()
{
	if (m_hWndMessage)
		DestroyWindow(m_hWndMessage);
}

/** Simple function to ensure that we can tell what version this class is programatically
\returns HIWORD( version()) returns major version number, LOWORD(version()) the minor revision number
*/
long awExtInfo::version(void)
{
	return MAKELONG(1, 0);
}

// The callback function for the infodriver messsaging window.
// hWnd is the handle of the message sender.
LRESULT CALLBACK awExtInfo::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
		case WM_COPYDATA:
			BNCS32ReceiveMessage(hWnd, (HWND)LOWORD(wParam), (COPYDATASTRUCT*)lParam);
			return true;

		case BBC_COPYDATA:
			return BNCS32RxMessage(hWnd, wParam, lParam);

		case WM_CREATE:
			SetWindowLong(hWnd, 0, (long)((CREATESTRUCT *)lParam)->lpCreateParams);
			break;
	}

	awExtInfo* c = (awExtInfo*)GetWindowLong(hWnd, 0);
	if (c)
	{
		switch (uMsg)
		{
			case BBC_INTERNALPOST:
			{
				while (c->postedQueue.size())
				{
					ccInfodriverPost p = c->postedQueue.front();
					c->postedQueue.pop();

					if (c->m_callback)
					{
						if (p.m_type == BBC_IDSLOTMSGPOSTED)
						{
							c->m_callback->cciSlotRequest(c->m_device, p.m_slot, p.m_contents);
						}
						else if (p.m_type == BBC_DEVCOMMANDMSGPOSTED)
						{
							decodeBNCSString dec(p.m_contents.c_str());

							// message is of format:
							//	NS <dev> <toWs> <msg> <fromWs> <handle> <reference>
							// e.g.
							//	NS 123 909 'Hello!' 100 12345 12345
							if (dec.subs < 7)
								break;

							// extract and check device number
							int dev = atoi(dec.sub[1]);
							if (c->checkDevice(dev))
								break;
							if (c->m_device != dev)
								break;


							// extract and check for our workstation number
							int toWs = atoi(dec.sub[2]);
							if (c->checkWorkstation(toWs))
								break;
							if (c->m_workstation != toWs)
								break;

							// extract and check the message
							string msg = dec.sub[3];
							if (c->checkString(msg))
								break;

							// extract and check originating workstation number
							int sendingWs = atoi(dec.sub[4]);
							if (c->checkWorkstation(sendingWs))
								break;

							// extract and check the client handle
							string client = dec.sub[5];
							if (c->checkString(client))
								break;

							// extract and check the free form reference string
							string ref = dec.sub[6];
							if (c->checkString(ref))
								break;

							// after all that bounds checking and destination checking 
							//  we can finally pass things on to our client
							c->m_callback->cciClientMessage(sendingWs, dev, msg, client, ref);
						}
					}
				}
				break;
			}

			case BBC_DEVCOMMANDMSG:
			{
				// QUEUE MESSAGE FOR PROCESSING LATER
				c->postedQueue.push(ccInfodriverPost(BBC_DEVCOMMANDMSGPOSTED, 0, string((LPSTR)lParam)));

				PostMessage(hWnd, BBC_INTERNALPOST, 0, 0);
			}
				return true;

			case BBC_IDSLOTMSG:
				// QUEUE MESSAGE FOR PROCESSING LATER
				c->postedQueue.push(ccInfodriverPost(BBC_IDSLOTMSGPOSTED, wParam, string((LPSTR)lParam)));

				PostMessage(hWnd, BBC_INTERNALPOST, 0, 0);
				return true;

			case BBC_CSICLOSING:
				if (c->m_callback)
					c->m_callback->cciDisconnected();
				break;

			case BBC_IDHANDLE:
				c->m_hInfo = (HWND)wParam;
				break;

			case WM_DESTROY:
				c->disconnect();
				break;

			case BBC_REQDEVGORXONLY:
				if (c->m_driverState == cc::ok)
					return false;
				return true;

			case BBC_REQDEVGOTXRX:
				// only go TXRX if we have active comms
				if (c->m_driverState == cc::ok)
				{
					c->m_redundancyState = cc::tx;
					c->m_callback->cciRedundancyState(cc::tx);
					return true;		// ok go txrx
				}
				return false;		// refuse to go txrx

			case BBC_INQDEVGOTXRX:
				// don't ask to go TXRX if we can't offer a reserve
				if (c->m_driverState == cc::ok)
					return true;
				return false;

			case BBC_DEVICEIDINUSE:
				// this device is in use
				c->m_redundancyState = cc::rx;
				if (c->m_callback)
					c->m_callback->cciRedundancyState(cc::rx);
				return 0;

			default:
				break;
		}
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

LRESULT awExtInfo::BNCSSendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam, int dataLen, int expectedReturnDataLen, int iDataType)
{
	if (m_ipc == cc::wm)
		return BNCS32SendMessage(m_hWndMessage, m_hInfo, uMsg, wParam, lParam, expectedReturnDataLen, iDataType);
	else if (m_ipc == cc::bbc)
		return BNCS32TxMessage(m_hInfo, uMsg, wParam, lParam, iDataType, max(dataLen, expectedReturnDataLen));
	return 0;
}

/** Connect to the specified infodriver using the specified IPC mechanism
\param[in] ipc IPC type as defined in the cc::ipcType enumeration
\param[in] ipcOptions values relevant to the ipc type chosen (none are defined at the time of writing)
\returns true on error, false on success
*/
bool awExtInfo::connect(int device, enum cc::ipcType ipc, long ipcOptions)
{
	if (isConnected())
		disconnect();

	m_device = device;
	m_ipc = ipc;
	m_ipcOptions = ipcOptions;

	if (m_ipc == cc::wm)
		SendMessage(HWND_BROADCAST, BBC_IDCONNECT, MAKEWPARAM(m_hWndMessage, 0), (LPARAM)m_device);
	else if (m_ipc == cc::bbc)
		SendMessage(HWND_BROADCAST, BBC_IDCONNECT32, MAKEWPARAM(m_hWndMessage, 0), (LPARAM)m_device);

	if (m_hInfo)
	{
		if (m_callback)
			m_callback->cciConnected();

		switch (SendMessage(m_hInfo, BBC_IDGETTXRXMODE, 0, 0))
		{
			case IFMODE_FORCETXRX:
			case IFMODE_TXRX:
				m_redundancyState = cc::tx;
				m_callback->cciRedundancyState(cc::tx);
				break;

			case IFMODE_RXONLY:
				m_redundancyState = cc::rx;
				m_callback->cciRedundancyState(cc::rx);
				break;

			default:
			case IFMODE_NONE:
				m_redundancyState = cc::unknown;
				m_callback->cciRedundancyState(cc::unknown);
				break;
		}
		return false;
	}
	return true;
}

/** Disconnect from infodriver. This calls our cciDisconnected event if we successfully disconnected
\returns false on successful disconnection or false on there being no connection to disconnect.
*/
bool awExtInfo::disconnect(void)
{
	m_redundancyState = cc::unknown;

	if (isConnected())
	{
		SendMessage(m_hInfo, BBC_IDDISCONNECT, 0, (LPARAM)m_device);
		m_hInfo = 0;
		m_ipc = cc::none;
		if (m_callback)
			m_callback->cciDisconnected();
		return false;
	}
	return true;
}

/** Are we connected to an Infodriver? This is also checks whether the handle we have for it is valid
\returns true on connected, false on not connected
*/
bool awExtInfo::isConnected(void)
{
	if (m_hInfo)
		if (IsWindow(m_hInfo))
			return true;
	return false;
}

/** Get value of an infodriver slot
\param[in] index The slot number
\param[out] value The variable to stick return value
\returns Returns true on error
*/
bool awExtInfo::slot(int index, string & value)
{
	if (checkIndex(index))
		return true;

	char buf[300];
	memset(buf, 0, sizeof(buf));

	BNCSSendMessage(BBC_IDGETSLOT, index, (LPARAM)buf, 0, sizeof(buf), CBDATA_TYPE_POINTER);

	value = buf;
	return false;
}

/** Set the value of an infodriver slot
\param[in] index The slot number
\param[in] value The new contents of the slot
\param[in] send Whether to send a change of state message to the network or just update the slot contents silently
\returns true on error
*/
bool awExtInfo::setSlot(int index, const string & value, bool send)
{
	if (checkIndex(index))
		return true;
	if (checkString(value))
		return true;

	char buf[300];
	strcpy_s(buf, 300, value.c_str());

	UINT msg;
	if (send)
		msg = BBC_IDSETSLOT;
	else
		msg = BBC_IDSETSLOTONLY;

	BNCSSendMessage(msg, index, (LPARAM)buf, sizeof(buf), 0, CBDATA_TYPE_POINTER);
	return false;
}


/** Set the value of an infodriver slot to an integer value
\param[in] index The slot number
\param[in] value The new contents of the slot
\param[in] format Format specifier e.g. for zero padded string to 6 characters format would be "%06d". Format may be decimal (d) or hex (x or X)
\param[in] send Whether to send a change of state message to the network or just update the slot contents silently
\returns true on error
\note The format parameter is the argument to "sprintf" and so must not contain more than one placeholder. If a percentage symbol is required then this should be entered as "%%" e.g. "%d%%".
*/
bool awExtInfo::setSlot(int index, int value, const string & format, bool send)
{
	if (checkString(format, 16))
		return true;

	char buf[300];

	sprintf_s(buf, 300, format.c_str(), value);

	return setSlot(index, buf, send);
}

/** Set the value of an infodriver slot to a double value
\param[in] index The slot number
\param[in] value The new contents of the slot
\param[in] format Format specifier e.g. for 3 decimal places the format string would be "%1.3f", to add the units "MHz" the format string would be "%1.3fMHz"
\param[in] send Whether to send a change of state message to the network or just update the slot contents silently
\returns true on error
\note The format parameter is the argument to "sprintf" and so must not contain more than one placeholder. If a percentage symbol is required then this should be entered as "%%" e.g. "%1.1f%%"
*/
bool awExtInfo::setSlot(int index, double value, const string & format, bool send)
{
	if (checkString(format, 16))
		return true;

	char buf[300];

	sprintf_s(buf, 300, format.c_str(), value);

	return setSlot(index, buf, send);
}

/** Update slot contents.
This function is equivalent to setSlot() but only sends any message to the network if old and new contents are different
\param[in] index The slot number
\param[in] value The slot contents
\param[out] updated Return value which indicates whether the slot had changed (true) or was the same (false)
\sa setSlot();
*/
bool awExtInfo::updateSlot(int index, const string & value, bool & updated)
{
	if (checkIndex(index))
		return true;

	string ret;

	slot(index, ret);

	if (value != ret)
	{
		updated = true;
		return setSlot(index, value);
	}
	updated = false;
	return false;
}

/** Send a response to a cciClientMessage
\param[in] workstation The client that requested this message
\param[in] device The CC device number
\param[in] message The message to be sent
\param[in] client The client identifier (simply passed on from the client notification)
\param[in] reference The client reference (simply passed on from the client notification)

Typical usage follows.....
\code
virtual void myApp::cciClientMessage( int ws, int dev, const string & msg, const string & client, const string & ref )
{
string myMsg;

// do something interesting here to answer the message sent to us in "message"
//  and send the reply back to the client that sent it (i.e. keep the parameters we're passed intact)
clientMessage( ws, dev, myMsg, client, ref);
}
\endcode
\note Implementation of this messaging mechanism is patchy so beware....
*/
void awExtInfo::clientMessage(int workstation, int device, const string & message, const string & client, const string & reference)
{
	if (checkWorkstation(workstation))
		return;
	if (checkDevice(device))
		return;
	if (checkString(message))
		return;

	char buf[512];
	sprintf_s(buf, 512, "NS %d %d '%s' %d %s %s", device, AwCcConfig::workstation(), message.c_str(), workstation, client.c_str(), reference.c_str());

	BNCSSendMessage(BBC_DEVSTATUSMSG, 0, (LPARAM)buf, strlen(buf) + 1, 0, CBDATA_TYPE_POINTER);

}

// Set the single or multi controller mode.
void awExtInfo::setRedundancyMode(enum cc::redundancyMode mode)
{
	m_redundancyMode = mode;
}

/** Force the TXRX status of this driver
\param[in] mode Either tx or rx

Typical usage follows.....
\code
setRedundancyState( cc:rx );	// I'm broken - allow to go RX only
...
setRedundancyState( cc:tx );	// I'm working - force myself TX/RX
\endcode
*/
void awExtInfo::setRedundancyState(enum cc::redundancyState state)
{
	if (state == cc::tx)
	{
		SendMessage(m_hInfo, BBC_IDSETTXRXMODE, IFMODE_FORCETXRX, 0);

		// remember our mode
		m_redundancyState = cc::tx;

		// we don't get separate indication of going to TXRX so generate one
		if (m_callback)
			m_callback->cciRedundancyState(cc::tx);

	}
	else if (state == cc::rx)
	{
		SendMessage(m_hInfo, BBC_IDSETTXRXMODE, IFMODE_RXONLY, 0);

		// remember our mode
		m_redundancyState = cc::rx;

		if (m_callback)
			m_callback->cciRedundancyState(cc::rx);
	}
}


void awExtInfo::setDriverState(enum cc::driverState state, bool fNow)
{
	m_driverState = state;

	if (fNow)
	{
		if (m_driverState == cc::ok)
			setRedundancyState(cc::tx);
		else if (m_driverState == cc::fail)
			setRedundancyState(cc::rx);
	}
}


enum cc::redundancyState awExtInfo::redundancyState(void)
{
	return m_redundancyState;
}
