/*--------------------------------------------------------------------------*
 * File Name:	ThreadSubObj.cpp											*
 * Purpose:		Demostrate realtime update on a graph						*
 * Creation:	April 04, 2000												*
 * Copyright (c) 2000 Microcal Software, Inc.								*
 *																			*
 * Modification Log:														*
 *--------------------------------------------------------------------------*/      

#include "ThreadSubObj.h"


int random_int(int iMin, int iMax);
static UINT GenericThreadRun(LPVOID lpParam);


//---------------------------------------------------------------------------
// Property Map:
//
// The Property Map is for declaring the properties of your LabTalk object.
//
// A property is mapped to Get and Set function.  These functions allow
// you to do error checking or any necessary conversions.  A read-only
// property can be declared by using the _GET macro.
//
// MOCA_PROP_INT( <GetFunction>,<SetFunction>,<PropertyNameStr> )
// MOCA_PROP_REAL( <GetFunction>,<SetFunction>,<PropertyNameStr> )
// MOCA_PROP_STR( <GetFunction>,<SetFunction>,<PropertyNameStr> )
// MOCA_PROP_INT_GET( <GetFunction>,<PropertyNameStr> )
// MOCA_PROP_REAL_GET( <GetFunction>,<PropertyNameStr> )
// MOCA_PROP_STR_GET( <GetFunction>,<PropertyNameStr> )
//
// A Simple Property is mapped to a data member.  There are no Get/Set
// functions for a Simple Property.  MOCA will take care of the assignment.
// A simple read-only property can be declared by using the _GET macro.
//
// MOCA_SIMPLE_PROP_INT( <DataMember>,<PropertyNameStr> )
// MOCA_SIMPLE_PROP_REAL( <DataMember>,<PropertyNameStr> )
// MOCA_SIMPLE_PROP_STR( <DataMember>,<PropertyNameStr> )
// MOCA_SIMPLE_PROP_INT_GET( <DataMember>,<PropertyNameStr> )
// MOCA_SIMPLE_PROP_REAL_GET( <DataMember>,<PropertyNameStr> )
// MOCA_SIMPLE_PROP_STR_GET( <DataMember>,<PropertyNameStr> )
//---------------------------------------------------------------------------
MOCA_BEGIN_PROP_MAP(CThreadSubObj, CMOCAObjBase)
	MOCA_PROP_INT(GetPriority, SetPriority, "Priority")
	MOCA_SIMPLE_PROP_INT_GET(m_iStatus, "Status")
	MOCA_PROP_STR_GET(GetStatusStr, "StatusDescrip")
MOCA_END_PROP_MAP(CThreadSubObj, CMOCAObjBase)


//---------------------------------------------------------------------------
// Method Map:
//
// INPORTANT: To have a Method Map or a SubObject Map, you must
// have a property map.
//
// MOCA_METHOD( <MemberFunction>,<MethodNameStr> )
//
// <MemberFunction> must be declared as "BOOL foo(double &, CStringArray &);"
// The double is used for storing LabTalk's return value.
// The CStringArray contains the arguments passed from LabTalk.
//---------------------------------------------------------------------------
MOCA_BEGIN_METH_MAP(CThreadSubObj, CMOCAObjBase)
	MOCA_METH_ENTRY(Meth_Start, "Start")	
	MOCA_METH_ENTRY(Meth_Suspend, "Suspend")	
	MOCA_METH_ENTRY(Meth_Resume, "Resume")	
	MOCA_METH_ENTRY(Meth_Kill, "Kill")	
MOCA_END_METH_MAP(CThreadSubObj, CMOCAObjBase)

//---------------------------------------------------------------------------
CThreadSubObj::CThreadSubObj()
{
	m_iNumPoints = 150;									// obj.NumPoints
	m_iPriority = DOTHREAD_PRIORITY_NORMAL;				// obj.Thread.Priority
	m_iStatus = DOTHREAD_STATUS_NOT_PRESENT;			// obj.Thread.Status

	m_pThread = NULL;
	m_bKill = FALSE;
}

CThreadSubObj::~CThreadSubObj()
{
}

//---------------------------------------------------------------------------
BOOL CThreadSubObj::GetPriority(int &iValue)
{
	iValue = m_iPriority;
	return TRUE;
}

BOOL CThreadSubObj::SetPriority(int iValue)
{
	if( iValue >= 0 && iValue <= DOTHREAD_PRIORITY_MAXIMUM )
	{
		m_iPriority = iValue;
		return TRUE;
	}
	return FALSE;
}

//---------------------------------------------------------------------------
// CThreadSubObj::GetStatusStr
//
//---------------------------------------------------------------------------
BOOL CThreadSubObj::GetStatusStr(LPSTR lpstr)
{
	switch( m_iStatus )
	{
	case DOTHREAD_STATUS_RUNNING:
		lstrcpy(lpstr, "Running");
		break;
	case DOTHREAD_STATUS_SUSPENDED:
		lstrcpy(lpstr, "Suspended");
		break;
	case DOTHREAD_STATUS_NOT_PRESENT:
		lstrcpy(lpstr, "Not Present");
		break;
	default:
		lstrcpy(lpstr, "Unknown");
		break;
	}
	return TRUE;
}

//---------------------------------------------------------------------------
// CThreadSubObj::Meth_Start
//
// LabTalk will return 1 on success and 0 on failure.
//---------------------------------------------------------------------------
BOOL CThreadSubObj::Meth_Start(double &dValue, CStringArray &strArgArray)
{
	if( strArgArray.GetSize() == 0 ) // method expects no arguments
	{
		if( IS_THREAD_STARTED(m_iStatus) )
		{
			dValue = 1.0; // already started, return success to LabTalk
		}
		else
		{
			static const int l_nPriority[] = 
			{
				THREAD_PRIORITY_IDLE,
				THREAD_PRIORITY_LOWEST,
				THREAD_PRIORITY_BELOW_NORMAL,
				THREAD_PRIORITY_NORMAL,
				THREAD_PRIORITY_ABOVE_NORMAL,
				THREAD_PRIORITY_HIGHEST,		// computer might freeze with this priority
				THREAD_PRIORITY_TIME_CRITICAL,	// computer might freeze with this priority
			};

			ASSERT(NULL == m_pThread);
			ASSERT(sizeof(l_nPriority)/sizeof(l_nPriority[0]) == DOTHREAD_PRIORITY_MAXIMUM + 1);
			
			// Convert LabTalk priority value to Windows API priority value
			int iPriority = l_nPriority[m_iPriority];

			// We are passing the pointer to the method object
			// as lpParam, so that we can use it inside 'GenericThreadRun'.
			m_pThread = AfxBeginThread(GenericThreadRun, this, iPriority, 0);

			if( m_pThread )
			{
				m_iStatus = DOTHREAD_STATUS_RUNNING;
				dValue = 1.0;
			}
			else
				dValue = 0.0;
		}
		return TRUE;
	}
	return FALSE;
}

//---------------------------------------------------------------------------
// CThreadSubObj::Meth_Kill
//
// LabTalk will return 1 on success and 0 on failure.
//---------------------------------------------------------------------------
BOOL CThreadSubObj::Meth_Kill(double &dValue, CStringArray &strArgArray)
{
	if( strArgArray.GetSize() == 0 ) // method expects no arguments
	{
		if( IS_THREAD_STARTED(m_iStatus) )
		{
			ASSERT_VALID(m_pThread);

			// If the thread is not suspended then suspend it
			if( !IS_THREAD_SUSPENDED(m_iStatus) )
				VERIFY(0 == m_pThread->SuspendThread());

			// We do not set 'm_iStatus' here.  It will be set by the thread.
			// Here we simply set the Kill flag.
			m_bKill = TRUE;

			// Resume thread and allow it to end gracefully
			VERIFY(1 == m_pThread->ResumeThread());
		}

		dValue = 1.0; // return success to LabTalk
		return TRUE;
	}
	return FALSE;
}

//---------------------------------------------------------------------------
// CThreadSubObj::Meth_Suspend
//
// LabTalk will return 1 on success and 0 on failure.
//---------------------------------------------------------------------------
BOOL CThreadSubObj::Meth_Suspend(double &dValue, CStringArray &strArgArray)
{
	if( strArgArray.GetSize() == 0 )
	{
		if( IS_THREAD_RUNNING(m_iStatus) )
		{
			ASSERT_VALID(m_pThread);
			
			// Check the return value (must be 0 if the thread was running)
			VERIFY(0 == m_pThread->SuspendThread());
			
			// Update status property
			m_iStatus = DOTHREAD_STATUS_SUSPENDED;
		}

		dValue = 1.0; // return success to LabTalk
		return TRUE;
	}
	return FALSE;
}

//---------------------------------------------------------------------------
// CThreadSubObj::Meth_Resume
//
// LabTalk will return 1 on success and 0 on failure.
//---------------------------------------------------------------------------
BOOL CThreadSubObj::Meth_Resume(double &dValue, CStringArray &strArgArray)
{
	if( strArgArray.GetSize() == 0 )
	{
		if( IS_THREAD_SUSPENDED(m_iStatus) )
		{
			ASSERT_VALID(m_pThread);

			DWORD dw = m_pThread->ResumeThread();
			ASSERT(dw != 0); // should not happen here

			// ResumeThread returns the follwing:
			// 0 = thread was not suspended
			// 1 = thread was suspended, but now restarted
			// >1 = thread is still suspended
			// 0xFFFFFFFF = failure
			if( dw == 0xFFFFFFFF )
			{
				dValue = 0.0; // return failure to LabTalk
			}
			else
			{
				dValue = 1.0; // return success to LabTalk

				if( dw == 1 )
					m_iStatus = DOTHREAD_STATUS_RUNNING;
			}
		}

		return TRUE;
	}
	return FALSE;
}

//---------------------------------------------------------------------------
// CThreadSubObj::Run
//
// This is where the thread spends most of it's time.
//---------------------------------------------------------------------------
UINT CThreadSubObj::Run()
{
	ASSERT_VALID(m_pThread);

	MOINDEX		ii;
	double		dd;
	BOOL		bDone = FALSE;
	

	// Get number of points to update
	int iNumPoints = m_iNumPoints;

	// Update status property
	m_iStatus = DOTHREAD_STATUS_RUNNING;
	

	MoResultData dataY(m_strWksName, 2); // second column
	
	if( !dataY.IsValid() )
		goto finish_thread;
	
	// Make sure redraw is done in real time:
	dataY.set_redraw_mode(MoData::REALTIME_ON_IDLE | MoData::REDRAW_REALTIME);

	// Modata.iRange2 is the actual range already in wks
	// Modata.i2 is the active/selected range that math calculation should use
	// Loop forever (the thread will be killed from outside)
	do
	{
		MOINDEX FirstRow = dataY.i2();
		MOINDEX LastRow = FirstRow + iNumPoints;

		for( ii = FirstRow; ii <= LastRow; ii++ )
		{
			/// we only do SetValue if within range as otherwise
			/// SetValue will attemp to expand the COKData memory which is
			/// not recommended
			if( ii + 1 >= m_iMaxNumPoints)
			{
				m_bKill = TRUE;
				break;
			}

			// The following if/else is used to create some random data
			// based on the existing data.  In a "real" DLL this data would
			// come from another source such as a data aquisition board.
			dd = 0.0;
			if( random_int(0,1) )
			{
				dd += random_int(0,20);
				if( dd > 200 )
					dd = 200;
			}
			else
			{
				dd -= random_int(0,20);
				if( dd < 0 )
					dd = 0;
			}

			// Put the data into the Y column.
			dataY.SetValue(ii, dd);
		}

		// Force idle update
		dataY.UpdateRange();

		Sleep(500);	// 500 miliseconds

		// Check if user wants to kill the thread
		if ( m_bKill )
		{
			bDone = TRUE;
			break;
		}

	} while( !bDone );

finish_thread:

	// Clear the Kill flag.
	m_bKill = FALSE;

	// We do not delete the thread here.  MFC will delete it when it exits.
	m_pThread = NULL;

	// Update status property.
	m_iStatus = DOTHREAD_STATUS_NOT_PRESENT;

	return 0;
}

//---------------------------------------------------------------------------
// GenericThreadRun
//
// This is the entry point into the running action of the thread.
// The thread will terminate either when this function returns
// or when it is killed from outside.
//---------------------------------------------------------------------------
static UINT GenericThreadRun(LPVOID lpParam)
{
	CThreadSubObj *pDoThread = (CThreadSubObj*)lpParam;
	return pDoThread->Run(); 
}

//---------------------------------------------------------------------------
// random_int
//
//---------------------------------------------------------------------------
int random_int(int iMin, int iMax)
{
	int i = iMax - iMin + 1;

	double d;
	
	d = (double)RAND_MAX / (double)i;
	d = (double)rand() / d;

	return (iMin + (int)d);
}
