// CPolhemusStatusBarActiveXControlCtrl.cpp : Implementation of the CCPolhemusStatusBarActiveXControlCtrl ActiveX Control class.

#include "stdafx.h"

#include "CPolhemusStatusBarActiveXControl.h"
#include "CPolhemusStatusBarActiveXControlCtrl.h"
#include "CPolhemusStatusBarActiveXControlPropPage.h"
#include ".\cpolhemusstatusbaractivexcontrolctrl.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// Enables objects of CObject-derived classes to be created dynamically at run time when 
// used with the DECLARE_DYNCREATE macro.
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_IMPLEMENT_DYNCREATE.htm
IMPLEMENT_DYNCREATE(CCPolhemusStatusBarActiveXControlCtrl, COleControl)

// Message map
// Windows messages this object will listen for and respond to 
// callback method with the listed
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_BEGIN_MESSAGE_MAP.htm
BEGIN_MESSAGE_MAP(CCPolhemusStatusBarActiveXControlCtrl, COleControl)

	// ON_OLEVERB macro defines a message map entry that maps a custom 
	// verb to a specific member function of your control.
	// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_ON_OLEVERB.htm
	ON_OLEVERB(AFX_IDS_VERB_EDIT, OnEdit)
	ON_OLEVERB(AFX_IDS_VERB_PROPERTIES, OnProperties)

	// ON_MESSAGE Indicates which function will handle a user-defined message.
	// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_ON_MESSAGE.htm
	ON_MESSAGE(OCM_COMMAND, OnOcmCommand)	// OLE control messages
	ON_MESSAGE(WM_PI_RAWDATA_READY	,	OnRawDataReady)
	ON_MESSAGE(WM_PI_DATA_STARTED	,	OnDataStarted )
	ON_MESSAGE(WM_PI_DATA_STOPPED	,	OnDataStopped )
	ON_MESSAGE(WM_PI_RAWDATA_ERROR	,	OnDataError )
	ON_MESSAGE(WM_PI_RAWDATA_WARNING,	OnDataWarning )

	// Predefined message handlers
	// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_handlers_for_wm_messages.htm
	ON_WM_CREATE()
	//ON_WM_SIZE()
	ON_WM_DESTROY()
	ON_WM_TIMER()
END_MESSAGE_MAP()



// Dispatch map
// Functions that are made public and callable via COM.
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_MFC_DISP_FUNCTION.htm
BEGIN_DISPATCH_MAP(CCPolhemusStatusBarActiveXControlCtrl, COleControl)
	DISP_FUNCTION_ID(CCPolhemusStatusBarActiveXControlCtrl, "AboutBox", DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
	DISP_FUNCTION_ID(CCPolhemusStatusBarActiveXControlCtrl, "GetStylusPosition", dispidGetStylusPosition, GetStylusPosition, VT_I4, VTS_PR4 VTS_PR4 VTS_PR4)
	DISP_FUNCTION_ID(CCPolhemusStatusBarActiveXControlCtrl, "DisconnectPolhemus", dispidDisconnect, DisconnectPolhemus, VT_EMPTY, VTS_NONE)
	DISP_FUNCTION_ID(CCPolhemusStatusBarActiveXControlCtrl, "ConnectPolhemus", dispidConnectPolhemus, ConnectPolhemus, VT_EMPTY, VTS_NONE)
	DISP_PROPERTY_NOTIFY_ID(CCPolhemusStatusBarActiveXControlCtrl, "AutoReconnectPolhemus", dispidAutoReconnectPolhemus, m_AutoReconnectPolhemus, OnAutoReconnectPolhemusChanged, VT_I4)
	DISP_PROPERTY_NOTIFY_ID(CCPolhemusStatusBarActiveXControlCtrl, "IsConnected", dispidIsConnected, m_IsConnected, OnIsConnectedChanged, VT_I4)
END_DISPATCH_MAP()

// Event map
// Events this object fires for other to respond to
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_BEGIN_EVENT_MAP.htm
BEGIN_EVENT_MAP(CCPolhemusStatusBarActiveXControlCtrl, COleControl)
	EVENT_CUSTOM_ID("WM_CPOLHEMUS_STYLUSBTN_DATA", eventidWM_CPOLHEMUS_STYLUSBTN_DATA, WM_CPOLHEMUS_STYLUSBTN_DATA, VTS_R4 VTS_R4 VTS_R4)
END_EVENT_MAP()

// Property pages
//ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_BEGIN_PROPPAGEIDS.htm
BEGIN_PROPPAGEIDS(CCPolhemusStatusBarActiveXControlCtrl, 1) // Remember to increase the count!
	PROPPAGEID(CCPolhemusStatusBarActiveXControlPropPage::guid)
END_PROPPAGEIDS(CCPolhemusStatusBarActiveXControlCtrl)

// Initialize class factory and guid
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_IMPLEMENT_OLECREATE_EX.htm
IMPLEMENT_OLECREATE_EX(CCPolhemusStatusBarActiveXControlCtrl, "CPOLHEMUSSTATUSB.CPolhemusStatusBCtrl.1",
	0xc1671ac2, 0x2a6a, 0x4f75, 0xb2, 0xf9, 0xdf, 0xbf, 0xea, 0xa8, 0xad, 0x7f)

// Type library ID and version
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/_mfc_IMPLEMENT_OLETYPELIB.htm
IMPLEMENT_OLETYPELIB(CCPolhemusStatusBarActiveXControlCtrl, _tlid, _wVerMajor, _wVerMinor)

// Interface IDs
// These are the GUIDs for the two interfaces
const IID BASED_CODE IID_DCPolhemusStatusBarActiveXControl =
		{ 0xE72E5855, 0xCE84, 0x4E97, { 0xB3, 0x69, 0x4, 0x63, 0xC0, 0x32, 0x6F, 0xA4 } };
const IID BASED_CODE IID_DCPolhemusStatusBarActiveXControlEvents =
		{ 0x1A1062CC, 0x91D2, 0x45B7, { 0xA1, 0x45, 0xDE, 0xCA, 0x7E, 0xDE, 0xE, 0x16 } };


// Control type information
// a set of bitwise constants that can be combined to describe miscellaneous
// characteristics of an object or class of objects
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/com/htm/oen_a2z_5vz7.htm
static const DWORD BASED_CODE _dwCPolhemusStatusBarActiveXControlOleMisc =
	OLEMISC_NOUIACTIVATE   | // there's no UI for this control
	OLEMISC_CANTLINKINSIDE | // no linking to internals
	OLEMISC_INSIDEOUT		 // can activate in place w/ out the rest of the UI boot strapped
;



// Implements the GetUserTypeNameID and GetMiscStatus member functions of your control class.
// ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/vclib/html/vcrefIMPLEMENT_OLECTLTYPE.htm
IMPLEMENT_OLECTLTYPE(CCPolhemusStatusBarActiveXControlCtrl, IDS_CPOLHEMUSSTATUSBARACTIVEXCONTROL, _dwCPolhemusStatusBarActiveXControlOleMisc)


// CCPolhemusStatusBarActiveXControlCtrl::CCPolhemusStatusBarActiveXControlCtrlFactory::UpdateRegistry -
// Adds or removes system registry entries for CCPolhemusStatusBarActiveXControlCtrl
BOOL CCPolhemusStatusBarActiveXControlCtrl::CCPolhemusStatusBarActiveXControlCtrlFactory::UpdateRegistry(BOOL bRegister)
{
	// TODO: Verify that your control follows apartment-model threading rules.
	// Refer to MFC TechNote 64 for more information.
	// If your control does not conform to the apartment-model rules, then
	// you must modify the code below, changing the 6th parameter from
	// afxRegInsertable | afxRegApartmentThreading to afxRegInsertable.

	if (bRegister)
		return AfxOleRegisterControlClass(
			AfxGetInstanceHandle(),
			m_clsid,
			m_lpszProgID,
			IDS_CPOLHEMUSSTATUSBARACTIVEXCONTROL,
			IDB_CPOLHEMUSSTATUSBARACTIVEXCONTROL,
			afxRegInsertable | afxRegApartmentThreading,
			_dwCPolhemusStatusBarActiveXControlOleMisc,
			_tlid,
			_wVerMajor,
			_wVerMinor);
	else
		return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}


// CCPolhemusStatusBarActiveXControlCtrl::CCPolhemusStatusBarActiveXControlCtrl - Constructor
CCPolhemusStatusBarActiveXControlCtrl::CCPolhemusStatusBarActiveXControlCtrl()
{
	InitializeIIDs(&IID_DCPolhemusStatusBarActiveXControl, &IID_DCPolhemusStatusBarActiveXControlEvents);

	hardwareFrameCount=0;
	lastHardwareFrameCount=0;
	lastSoftwareFrameCount=0;
	m_dwFrameSize = 0;
	stylusButtonReleased=FALSE;
	bIsPolhemusConnected=FALSE; 
	m_IsConnected=0;
	for(int i=0;i<POLHEMUS_BUFFER_SIZE;i++)
	{
		g_pMotionBuf[POLHEMUS_BUFFER_SIZE]=0;
	}

}


// CCPolhemusStatusBarActiveXControlCtrl::~CCPolhemusStatusBarActiveXControlCtrl - Destructor
CCPolhemusStatusBarActiveXControlCtrl::~CCPolhemusStatusBarActiveXControlCtrl()
{
}


int CCPolhemusStatusBarActiveXControlCtrl::OnCreate(LPCREATESTRUCT lpcs )
{
    if (CWnd::OnCreate(lpcs) == -1)
        return -1;

	// only connect if not in design mode
	if( AmbientUserMode() )
	{
		TearDownPolhemus();
		up_PolhemusStatusTimer = SetTimer(1,2000,0);
	}

	return S_OK;
}


// CCPolhemusStatusBarActiveXControlCtrl::AboutBox - Display an "About" box to the user
void CCPolhemusStatusBarActiveXControlCtrl::AboutBox()
{
	CDialog dlgAbout(IDD_ABOUTBOX_CPOLHEMUSSTATUSBARACTIVEXCONTROL);
	dlgAbout.DoModal();
}


void CCPolhemusStatusBarActiveXControlCtrl::OnDestroy()
{
	KillTimer(up_PolhemusStatusTimer);
	TearDownPolhemus();
	COleControl::OnDestroy();
}

// software && hardware check
BOOL CCPolhemusStatusBarActiveXControlCtrl::IsPolhemusConnected()
{
	BOOL retVal = FALSE;
	DWORD sFrameCount = 0;

	if ( polhemus.CnxReady() ) // software thinks it's connected?	
	{
		// if the software is connected, then a 
		// frame number from the device has changed
		// means the hardware is connected and sending frames
		// in continuous mode
		if( polhemus.LastHostFrameCount(sFrameCount) )
		{
			if( sFrameCount != lastSoftwareFrameCount &&
				hardwareFrameCount != lastHardwareFrameCount )
			{
				lastSoftwareFrameCount = sFrameCount;
				lastHardwareFrameCount = hardwareFrameCount;
				retVal=TRUE;
			}
			else
			{
				// frameCount hasn't changed
				retVal=FALSE;
			}
		}
		else
		{
			// LastFrameCount failed
			retVal=FALSE;
		}
	}
	else
	{	
		// software doesn't think it's connected
		retVal = FALSE;
	}

	return retVal;
}

// return value is true if the state has changed
void CCPolhemusStatusBarActiveXControlCtrl::ConnectPolhemus()
{
	// try to connect
	TRACE("Trying to connect to software\n");
	polhemus.DiscoverCnx()	; // supposedly disconnects() internally

	if( polhemus.CnxReady() ){
		// software successfully connected. So hardware is connected.
		TRACE("Software successfully connected to hardware\n");
		BuildUpPolhemus();
		bIsPolhemusConnected = TRUE;
		m_IsConnected = 1;
		// make the frameCounts differ so the IsConnected() check passes the first time
		lastSoftwareFrameCount = -1;
		lastHardwareFrameCount = -1;
	}
	else
	{
		// software failed to connect. So hardware is NOT connected.
		TRACE("Software failed to connect to hardware\n");
		TearDownPolhemus();
	}
}


void CCPolhemusStatusBarActiveXControlCtrl::TearDownPolhemus()
{
	TRACE("Tearing down the software connection\n");

	// disconnect the software
	polhemus.StopContPno();
	TRACE("StopContPno result: "); TRACE(polhemus.GetLastResultStr()); TRACE("\n");
	polhemus.ResetPnoPtr(); 
	TRACE("ResetPnoPtr result: "); TRACE(polhemus.GetLastResultStr()); TRACE("\n");
	polhemus.ResetHostFrameCount();
	TRACE("ResetHostFrameCount result: "); TRACE(polhemus.GetLastResultStr()); TRACE("\n");
	polhemus.Disconnect();
	TRACE("Disconnect result: "); TRACE(polhemus.GetLastResultStr()); TRACE("\n");

	hardwareFrameCount=0;
	lastHardwareFrameCount=0;
	lastSoftwareFrameCount=0;
	stylusButtonReleased=FALSE;
	bIsPolhemusConnected=FALSE;
	m_IsConnected=0;
}

void CCPolhemusStatusBarActiveXControlCtrl::BuildUpPolhemus()
{
	TRACE("Building up the software connection\n");

	polhemus.ResetFrameCount();
	TRACE("ResetFrameCount result: "); TRACE(polhemus.GetLastResultStr()); TRACE("\n");

	//Stylus Mouse Mode
	//In stylus Mouse mode, each time a stylus button is pressed, a flag 
	//is added to the station's current P&O frame indicating the stylus 
	//button push event.  To capture this stylus event in your program, 
	//perform the following steps:
	//
	//1 Configure the station stylus mode to PI_STYMODE_MOUSE with a call 
	//	to CPDIdev::SetSStylusMode(). 
	//2 Make sure that the stylus is active by calling CPDIdev::EnableSensor()
	//	or CPDIdev::SetStationMap(). 
	//3 Configure the stylus' CPDImdat output motion data list to include 
	//	the PDI_MODATA_STYLUS stylus flag item.  Do this with a call 
	//	to CPDIdev::SetSDataList(). 
	//4 Collect motion data from the tracker, in either continuous output 
	//	or single frame mode.  See Collect Motion Data for details. 

	//polhemus.SetSHemiTrack(-1);
	// this vector is perpindicular to the plane that splits hemispheres.
	// you are sampling the hemisphere on the side of the plane the vector points
	FLOAT pv[3] = {1.0f, 1.0f, 0.0f}; 
	polhemus.SetSHemisphere(-1,(PDI3vec)pv); // hemisphere containing positive x & y

	// assign a memory block for PDI to use as a circular buffer
	// to store motion frames
	polhemus.SetPnoBuffer( g_pMotionBuf, POLHEMUS_BUFFER_SIZE );

	// put all sensors in mouse mode
	polhemus.SetSStylusMode(-1,PI_STYMODE_MOUSE);

	// enable all sensors
	polhemus.EnableSensor(-1,TRUE);   // enable all the sensors

	// format our motion frames
	m_pdiMDat.Empty();
	m_pdiMDat.Append( PDI_MODATA_FRAMECOUNT );	// include a hardware frame count
	m_pdiMDat.Append( PDI_MODATA_STYLUS  );		// include a stylus button flag
	m_pdiMDat.Append( PDI_MODATA_POS );			// include cartesian coordinates

	// how big our frames will be
	//4 bye INT32 counter + 4 byte INT32 "bool" + 3 * 4 byte FLOAT32 for cartesian xyz
	m_dwFrameSize = 4 + 4 + (3 * sizeof(FLOAT) ); 

	// upload frame format to the device
	polhemus.SetSDataList(-1,m_pdiMDat);

	// start collecting frames
	polhemus.StartContPno(m_hWnd);		// continuous click mode


}

// Handles WM_PI_RAWDATA_READY messages
// Loops through the range of frames the device offers. Fires an custom event
// when it finds the first frame marked with a stylus button press.
LRESULT	CCPolhemusStatusBarActiveXControlCtrl::OnRawDataReady( WPARAM wP, LPARAM lP )
{
	PBYTE buf = (PBYTE)lP;
	DWORD numFrames = (DWORD)wP;
	UINT i=8;	// 9 is size to skip header

	while( i < numFrames)
	{
		// 4 byte dword frameCount
		hardwareFrameCount	= *( (DWORD*) &(buf[i]) );
		i+=sizeof(DWORD); 

		// 4 byte "bool", is stylus depressed?
		DWORD stylusButton = *( (DWORD*) &(buf[i]) );
		i+=sizeof(DWORD);

		// recieve stylus button presses and emit WM_CPOLHEMUS_STYLUSBTN_DATA 
		if( (stylusButton==TRUE) )
		{
			// has the stylus been released? 
			if( stylusButtonReleased==TRUE )
			{ 
				// Then this is a first stylus depressed frame, so we should fire
				// a stylus event with the cartesian coordinates
				PFLOAT pPno = (PFLOAT) &(buf[i]);
				WM_CPOLHEMUS_STYLUSBTN_DATA(pPno[0],pPno[1],pPno[2]);
				stylusButtonReleased=FALSE;
			}
			else
			{
				// The stylus button was depressed, but it is a continuation 
				// of the last press. Note, the device's sample rate is faster than the
				// human can depress and release the button. So we get multiple
				// frames marked with stylus flags.
			}
		}
		else 
		{
			// the stylus button was not depressed
			// we can release the stylus flag to be ready for 
			// the next stylus button press
			stylusButtonReleased=TRUE;
		}

		// skip ahead the 3 x 4 byte FLOAT coordinates to the next frame
		i += (3 * sizeof(FLOAT));
	}
	return S_OK;
}

// Handles WM_PI_DATA_STARTED messages, mostly for debugging
LRESULT	CCPolhemusStatusBarActiveXControlCtrl::OnDataStarted( WPARAM wP, LPARAM lP )
{
	TRACE("Polhemus Data Started\n");
    return S_OK;
}

// Handles WM_PI_DATA_STOPPED messages, mostly for debugging
LRESULT CCPolhemusStatusBarActiveXControlCtrl::OnDataStopped( WPARAM wP, LPARAM lP )
{
	TRACE("Polhemus Data Stopped\n");
    return S_OK;
}

// Handles WM_PI_RAWDATA_ERROR messages, mostly for debugging
LRESULT	CCPolhemusStatusBarActiveXControlCtrl::OnDataError( WPARAM wP, LPARAM lP )
{
	CString cs;
	cs.Append(polhemus.ResultStr( (ePiErrCode)wP, (ePiDevError)lP ));
	cs.Append("\n");
	TRACE(cs.GetBuffer());
    return S_OK;
}

// Handles WM_PI_RAWDATA_WARNING messages, mostly for debugging
LRESULT	CCPolhemusStatusBarActiveXControlCtrl::OnDataWarning( WPARAM wP, LPARAM lP )
{
	CString cs;
	cs.Append(polhemus.ResultStr( (ePiErrCode)wP, (ePiDevError)lP ));
	cs.Append("\n");
	TRACE(cs.GetBuffer());
	return S_OK;
}


/**
	GetStylusPosition
	Takes [out] pointers/refs as parameters. The current cartesian XYZ, 
	read raw from the device is returned put into the pointers. It is 
	expected that the caller will call this with a timer occuring less than 
	devices frequency of 60Hz.
	*/
HRESULT CCPolhemusStatusBarActiveXControlCtrl::GetStylusPosition(FLOAT* _x, FLOAT* _y, FLOAT* _z)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	HRESULT retVal = S_FALSE;

	*_x =  0;
	*_y =  0;
	*_z =  0;

	if( bIsPolhemusConnected )
	{
		PBYTE pBuf;
		DWORD dwSize;

		if( polhemus.LastPnoPtr(pBuf,dwSize) ) // retrieval successful?
		{ 
			UINT i = 16; // 8 byte header, 4 byte frame counter, 4 byte stylus button

			// load the passed variables for the caller
			*_x = *( (FLOAT*) &(pBuf[i]) );
			i += sizeof(FLOAT);
			*_y = *( (FLOAT*) &(pBuf[i]) );
			i += sizeof(FLOAT);
			*_z = *( (FLOAT*) &(pBuf[i]) );

			retVal = S_OK;
		}
		else
		{
			// failed LastPnoPtr()
			CString cs;
			cs.Append(polhemus.GetLastResultStr());
			cs.Append("\n");
			TRACE(cs.GetBuffer());
			retVal = S_FALSE;
		}
	}
	else
	{
		// failed IsPolhemusConnected()
		retVal = S_FALSE;
	}
	
	return retVal;
}

// Fires the STYLUSBTN event sending the event listener
// the three floats of the stylus' current cartesian
// position
void CCPolhemusStatusBarActiveXControlCtrl::WM_CPOLHEMUS_STYLUSBTN_DATA(FLOAT x, FLOAT y, FLOAT z)
{
	FireEvent(eventidWM_CPOLHEMUS_STYLUSBTN_DATA, EVENT_PARAM(VTS_R4 VTS_R4 VTS_R4), x, y, z);
}



// CCPolhemusStatusBarActiveXControlCtrl::OnOcmCommand - Handle command messages
LRESULT CCPolhemusStatusBarActiveXControlCtrl::OnOcmCommand(WPARAM wParam, LPARAM lParam)
{
#ifdef _WIN32
	WORD wNotifyCode = HIWORD(wParam);
#else
	WORD wNotifyCode = HIWORD(lParam);
#endif

	return S_OK;
}


void CCPolhemusStatusBarActiveXControlCtrl::OnTimer(UINT_PTR nIDEvent)
{
	// every time the status timer goes off
	// see if we are still connected
	// if we are not teardown (probably redundantly)
	// otherwise use the autoconnect setting
	if( nIDEvent == up_PolhemusStatusTimer ){
		// retrieve and set our connection status
		BOOL wasConnected = bIsPolhemusConnected;
		bIsPolhemusConnected = IsPolhemusConnected();
		m_IsConnected = ( bIsPolhemusConnected == FALSE ? 0 : 1 );
		if( bIsPolhemusConnected )
		{
			// Sweet, polhemus is connected
			TRACE("At timer status check Polhemus is connected. Sweet.\n");
		}
		else
		{
			// crap, polhemus is not connected

			// if we were just connected then clean up
			if( wasConnected == TRUE )
			{
				TearDownPolhemus();
			}

			// if autoconnect is set then try to connect
			if( m_AutoReconnectPolhemus )
			{
				ConnectPolhemus();
			}
		}
	}

	COleControl::OnTimer(nIDEvent);
}

void CCPolhemusStatusBarActiveXControlCtrl::DisconnectPolhemus(void)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your dispatch handler code here
	TearDownPolhemus();
}




void CCPolhemusStatusBarActiveXControlCtrl::OnAutoReconnectPolhemusChanged(void)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your property handler code here

	SetModifiedFlag();
}

void CCPolhemusStatusBarActiveXControlCtrl::OnIsConnectedChanged(void)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your property handler code here

	SetModifiedFlag();
}
