/* $Header$ */
/*
	OMNIS Studio - Generic Worker object
	Copyright (C) Omnis Software Ltd 2017

FILENAME:	dgenobj.cpp

DESCRIPTION:	Implements a Generic Worker Object

DETAILS:	
	
*/
/* Changes
Date				Edit		Fault				Description
Nov-16													gra created
*/		

#include <extcomp.he> 
#include "dgenobj.hi"
#include <dmconst.he>

//Objects provided by this component
ECOobject GenObjects[] = 
{
	cObject_WorkerObj, 2000, EXTD_OBJFLAG_WORKER, cRESWorkerGroup
};

//Worker object method parameters
ECOparam WorkerParamInit[] =
{	
	2350, fftRow, 0, 0
};

ECOparam WorkerParamCompleted[] =
{	
	2352, fftRow, 0, 0
};

//Worker Object properties
ECOproperty	WorkerProps[] =
{
	cWorkerState, cWorkerState, fftConstant, EXTD_FLAG_EDITRONLY, 0, 0, 0, \
	cWorkerErrorMsg, cWorkerErrorMsg, fftCharacter, EXTD_FLAG_EDITRONLY, 0, 0, 0, \
	cWorkerErrorCode, cWorkerErrorCode, fftInteger, EXTD_FLAG_EDITRONLY, 0, 0, 0, \
	cWorkerCancelIfRunning, cWorkerCancelIfRunning, fftBoolean, 0, 0, 0, 0, \
	cWorkerThreadCount, cWorkerThreadCount, fftInteger, EXTD_FLAG_EDITRONLY, 0, 0, 0, \
	cWorkerWaitForComplete, cWorkerWaitForComplete, fftBoolean, 0, 0, 0, 0
};

//Worker Object methods
ECOmethodEvent WorkerFuncs[] =
{
	cWorkerInit, cWorkerInit, fftBoolean, 1, WorkerParamInit, 0, 0, \
	cWorkerRun, cWorkerRun, fftBoolean, 0, 0, 0, 0, \
	cWorkerStart, cWorkerStart, fftBoolean, 0, 0, 0, 0, \
	cWorkerCancel, cWorkerCancel, fftNone, 0, 0, 0, 0, \
	cWorkerCompleted, cWorkerCompleted, fftBoolean, 1, WorkerParamCompleted, 0, 0, \
	cWorkerCancelled, cWorkerCancelled, fftBoolean, 0, 0, 0, 0
};


int WorkerDelegate::mThreadCount = 0; //Initialise static member

//	##################################################################
//	PURPOSE:	Main entry point for interface object
//	RETURN:		Status - qtrue/qfalse
//	DETAIL:		See worker.def
extern "C" LRESULT OMNISWNDPROC WorkerObjProc(HWND hwnd, UINT Msg,WPARAM wParam,LPARAM lParam,EXTCompInfo* eci)
{
	 // Initialize callback tables - THIS MUST BE DONE 
	ECOsetupCallbacks(hwnd, eci);		
	switch (Msg)
	{
		case ECM_OBJCONSTRUCT:	//This is a message informing you to create a new object			
		{
			tqfWorkerObjCont* object = (tqfWorkerObjCont*)ECOfindNVObject( eci->mOmnisInstance, lParam );
			if ( !object )
			{
				tqfWorkerObj* newObj = new tqfWorkerObj((qobjinst)lParam, eci); 
				tqfWorkerObjCont* container = new tqfWorkerObjCont((qobjinst)lParam, newObj);
				ECOinsertNVObject(eci->mOmnisInstance, lParam, (void*)container);
				return qtrue;
			}
			return qfalse;
		}     
		case ECM_OBJDESTRUCT:	//This is a message informing you to delete the object				
		{
			if ( wParam==ECM_WPARAM_OBJINFO )
			{	
				void* object = ECOremoveNVObject( eci->mOmnisInstance, lParam );
				tqfWorkerObjCont* GenObj = (tqfWorkerObjCont*)object;
				delete GenObj;
			}	
			return qtrue;
		}
		//ECM_OBJECT_COPY: this message is called when an object needs to be copied by Omnis
		case ECM_OBJECT_COPY:
		{
			objCopyInfo* copyInfo = (objCopyInfo*)lParam;

			LPARAM source = reinterpret_cast<LPARAM>(ECOgetNVObject((qobjinst)copyInfo->mSourceObject));
			tqfWorkerObj* src = (tqfWorkerObj*)ECOfindNVObject(eci->mOmnisInstance, source);
			if (src)
			{
				LPARAM destination = reinterpret_cast<LPARAM>(ECOgetNVObject((qobjinst)copyInfo->mDestinationObject));
				tqfWorkerObj* dest = (tqfWorkerObj*)ECOfindNVObject(eci->mOmnisInstance, destination);
				if (!dest)
				{
					dest = (tqfWorkerObj*)src->dup((qobjinst)copyInfo->mDestinationObject, eci);
					ECOinsertNVObject(eci->mOmnisInstance, copyInfo->mDestinationObject, dest);
				}
				else
					dest->copy(src);
			}
		}
		case ECM_CONNECT:
		{
			return ECOmakeConnectFlags(EXT_FLAG_LOADED|EXT_FLAG_ALWAYS_USABLE|EXT_FLAG_REMAINLOADED|EXT_FLAG_NVOBJECTS);
		}
		case ECM_DISCONNECT:
		{
			return qtrue;
		}
		case ECM_GETCOMPLIBINFO:
		{
			return ECOreturnCompInfo(gInstLib, eci, cResLibName, 0);
		}
		case ECM_GETVERSION:
		{
			return ECOreturnVersion(gInstLib);
		}
		case ECM_GETPROPNAME:
		{
			return ECOreturnProperties( gInstLib, eci, &WorkerProps[0], tqfWorkerObj::propCount() );
		}
		case ECM_GETOBJECT:
		{
			return ECOreturnObjects(gInstLib,eci, &GenObjects[0], sizeof(GenObjects)/sizeof(GenObjects[0])); 
		}
		case ECM_GETMETHODNAME:
		{
			return ECOreturnMethods(gInstLib, eci, &WorkerFuncs[0], tqfWorkerObj::methodCount());
			break;	
		}
		case ECM_PROPERTYCANASSIGN:  	// Object: Is the property assignable (ie readonly?)
		case ECM_SETPROPERTY: 			// Object: Assignment to a property
		case ECM_GETPROPERTY:			// Object: Retrieve value from property
		{
			void* object = (void*)ECOfindNVObject( eci->mOmnisInstance, lParam );
			if ( object )
			{ 
				tqfWorkerObjCont* GenSess = (tqfWorkerObjCont*)object;
				if ( GenSess->mObject )
					return GenSess->mObject->propertySupport( Msg, wParam, lParam, eci );
			}
			return 0L;
		}
		case ECM_METHODCALL:
		{
			void* object = (void*)ECOfindNVObject( eci->mOmnisInstance, lParam );
			if ( object )
			{ 
				tqfWorkerObjCont* GenObj = (tqfWorkerObjCont*)object;
				if ( GenObj->mObject )
					return GenObj->mObject->methodCall(eci);
			}
			return qfalse;
		}                                                                 
	}

	// As a final result this must ALWAYS be called. It handles all other messages that this component decides to ignore.
	return WNDdefWindowProc(hwnd, Msg, wParam, lParam, eci); 
}

//	##################################################################
//	PURPOSE:	Object container constructor
//	RETURN:		None
//	DETAIL:		
tqfWorkerObjCont::tqfWorkerObjCont(qobjinst pObjPtr, tqfWorkerObj *pNewObject)
{
	mObjPtr = pObjPtr;
	mObject = pNewObject;
}

//	##################################################################
//	PURPOSE:	Oject container constructor from existing object
//	RETURN:		None
//	DETAIL:		Reference count of object instance is incremented
tqfWorkerObjCont::tqfWorkerObjCont(qobjinst pObjPtr, tqfWorkerObjCont* pObjectContainer)
{
	mObjPtr = pObjPtr;
	mObject = pObjectContainer->mObject;
	if (mObject)
		mObject->addRef();
}

//	##################################################################
//	PURPOSE:	Object container destructor
//	RETURN:		None
//	DETAIL:		Reference count of object is decremented
tqfWorkerObjCont::~tqfWorkerObjCont()
{
	if (mObject)
	{
		mObject->releaseRef();
		mObject = NULL;
	}
}

//	##################################################################
//	PURPOSE:	Copy reference to object
//	RETURN:		None
//	DETAIL:		Reference count of object instance is incremented
void tqfWorkerObjCont::setObject(qobjinst pObjPtr, tqfWorkerObjCont* pSource)
{
	if (pSource->mObject)
		pSource->mObject->addRef();
	if (mObject)
	{
		mObject->releaseRef();
		mObject = NULL;
	}
	mObjPtr = pObjPtr;
	mObject = pSource->mObject;
}

//	##################################################################
//	PURPOSE:	Derived Worker object constructor
//	RETURN:		None
//	DETAIL:		
tqfWorkerObj::tqfWorkerObj(qobjinst ptr, EXTCompInfo *pEci)
{	
	//Initialise session object properties
	mRefCount = 0;
	mObjPtr = ptr;
	mCurrCompInfo = pEci;

	mDelegate = new WorkerDelegate(ptr, pEci); //create a delegate object (background worker)
}

//	##################################################################
//	PURPOSE:	Worker Object destructor
//	RETURN:		None
//	DETAIL:		Free environment and type table
tqfWorkerObj::~tqfWorkerObj()
{
	delete mDelegate;
}

//##################################################################
//PURPOSE: Duplicates a client worker object into a new object
//DETAIL:
tqfWorkerObj* tqfWorkerObj::dup(qobjinst objinst, EXTCompInfo* pEci)
{
	tqfWorkerObj* theCopy = new tqfWorkerObj(objinst, pEci); //create new object
	theCopy->copy(this); //copy contents
	return theCopy;      //return to caller
}

//##################################################################
//PURPOSE: Copy pObj objects contents into this objects contents
//DETAIL:
void tqfWorkerObj::copy(tqfWorkerObj* pObj)
{
	qobjinst inst = mObjPtr;	// Saves object instance
	*this = *pObj;				// Invokes Copy Constructor
	mObjPtr = inst;				// Restores saved object instance 
}

//	##################################################################
//	PURPOSE:	Increment reference count of object instance
//	RETURN:		None
//	DETAIL:		
void tqfWorkerObj::addRef()
{
	mRefCount++;
}

//	##################################################################
//	PURPOSE:	Decrement reference count of object instance
//	RETURN:		None
//	DETAIL:		
void tqfWorkerObj::releaseRef()
{
	mRefCount--;
	if (!mRefCount)
	{
		delete this;
	}
}

//	##################################################################
//	PURPOSE:	Get count of methods provided by the session object
//	RETURN:		Method count
//	DETAIL:		
qshort tqfWorkerObj::methodCount()
{
	return sizeof(WorkerFuncs) / sizeof(WorkerFuncs[0]);
}

//	##################################################################
//	PURPOSE:	Get count of properties provided by the session object
//	RETURN:		Property count
//	DETAIL:		
qshort tqfWorkerObj::propCount()
{
	return sizeof(WorkerProps)/sizeof(WorkerProps[0]);
}

//	##################################################################
//	PURPOSE:	Respond to requests from the Extcomp Interface 
//				re; properties supported by this obejct
//	RETURN:		Status - qtrue/qfalse
//	DETAIL:	
qlong tqfWorkerObj::propertySupport(LPARAM pMessage, WPARAM wParam, LPARAM lParam, EXTCompInfo* eci)
{
	qbool rtnStatus = qfalse;
	mCurrCompInfo = eci;	// cr1110
	switch (pMessage)
	{
		case ECM_PROPERTYCANASSIGN:
		{
			switch (ECOgetId(eci))
			{
				case cWorkerCancelIfRunning:
				case cWorkerWaitForComplete:
				{
					rtnStatus = qtrue;
					break;
				}
				default:	break;
			}
			break;
		}
		case ECM_SETPROPERTY:
		{
			// get the externals parameter
			EXTParamInfo* param = ECOfindParamNum(eci, 1);
			if (param)
			{
				// get the fldval from the parameter.
				EXTfldval fval((qfldval)param->mData);
				// fldvals are containers that store data. All values in and out of OMNIS are passes in fldvals.
				// switch on property id
				switch (ECOgetId(eci))
				{
					case cWorkerCancelIfRunning:
					{
						rtnStatus = qtrue;
						mDelegate->setCancelIfRunning(fval.getBool() == preBoolTrue);
						break;
					}
					case cWorkerWaitForComplete:
					{
						rtnStatus = qtrue;
						mDelegate->setWaitForComplete(fval.getBool() == preBoolTrue);
						break;
					}
					default:	break;
				}
			}
			break;
		}
		case ECM_GETPROPERTY:
		{
			rtnStatus = qfalse;										
			EXTfldval fval;

			// switch on property id
			switch (ECOgetId(eci))
			{
				case cWorkerState:
				{
					rtnStatus = qtrue;
					fval.setConstant(preWorkerStateF, preWorkerStateL, mDelegate->getState());
					break;
				}
				case cWorkerErrorMsg: 
				{
					rtnStatus = qtrue;
					mDelegate->getErrorText(fval);
					break;
				}
				case cWorkerErrorCode:
				{
					rtnStatus = qtrue;
					fval.setConstant(preWorkerErrorsF, preWorkerErrorsL, mDelegate->getErrorCode()); 
					break;
				}
				case cWorkerCancelIfRunning:
				{
					rtnStatus = qtrue;
					fval.setBool(mDelegate->getCancelIfRunning() ? preBoolTrue : preBoolFalse);
					break;
				}
				case cWorkerWaitForComplete:
				{
					rtnStatus = qtrue;
					fval.setBool(mDelegate->getWaitForComplete() ? preBoolTrue : preBoolFalse);
					break;
				}
				case cWorkerThreadCount:
				{
					rtnStatus = qtrue;
					fval.setLong(mDelegate->getThreadCount());
					break;
				}
				default:	break;
			}
			// to send it back, we add a parameter which OMNIS will check on return.
			if (rtnStatus == qtrue)										
				ECOaddParam(eci, &fval);
			break;
		}
		default:
		{
		}
	} //switch
	return (qlong)rtnStatus;		// no property found or message was wrong	
}

//	##################################################################
//	PURPOSE:	Process method call requests from the Extcomp Interface
//	DETAIL:
qbool tqfWorkerObj::methodCall(EXTCompInfo* pEci)
{
	qlong funcId = ECOgetId(pEci);
	qbool rtnCode = qfalse; EXTfldval rtnVal;
	mCurrCompInfo = pEci;	

	switch (funcId)
	{
		case cWorkerInit:
		{
			rtnCode = methodInit(pEci);
			break;
		}
		case cWorkerRun:
		{
			rtnCode = methodRun(pEci);
			break;
		}
		case cWorkerStart:
		{
			rtnCode = methodStart(pEci);
			break;
		}
		case cWorkerCancel:
		{
			rtnCode = methodCancel(pEci);
			break;
		}
		default:	 //An invalid method id was passed
			break;
	}

	rtnVal.setBool(rtnCode ? preBoolTrue : preBoolFalse);
	ECOaddParam(pEci, &rtnVal);
	return rtnCode;
}


//##################################################################
//PURPOSE: Client worker object initialisation method
//DETAIL:
qbool	tqfWorkerObj::methodInit(EXTCompInfo *pEci)
{
	qbool rtnStatus = mDelegate->init(pEci); 
	if (mDelegate->isOrphaned() == qtrue)
	{ //Create new worker delegate object to replace the orphaned one
		mDelegate = mDelegate->dup(mObjPtr, pEci);
		rtnStatus = mDelegate->init(pEci);
	}
	return rtnStatus;
}

//##################################################################
//PURPOSE: Client worker object run method
//DETAIL: Runs the worker task on the main thread
qbool tqfWorkerObj::methodRun(EXTCompInfo *pEci)
{
	qbool rtnStatus = qfalse;
	do {
		rtnStatus = mDelegate->run(pEci);
		mDelegate->pushWorkerCallbackFromRunMethod();	
	} while (0);

	return rtnStatus;
}

//##################################################################
//PURPOSE: Client worker object start method
//DETAIL: Runs the worker task on a background thread
qbool tqfWorkerObj::methodStart(EXTCompInfo *pEci)
{
	return mDelegate->start(pEci);  
}

//##################################################################
//PURPOSE: Client worker object cancel method
//DETAIL:
qbool tqfWorkerObj::methodCancel(EXTCompInfo *pEci)
{
	mDelegate->cancel();
	return qtrue;
}

// Worker Delegate Class //////////////////////////////////////////////
//	##################################################################
//	PURPOSE:	Overidden worker delegate constructor
//	RETURN:		None
//	DETAIL:		
WorkerDelegate::WorkerDelegate(qobjinst objptr, EXTCompInfo* pEci) 
{
	mObjPtr = objptr;
	mEci = pEci;
}

//	##################################################################
//	PURPOSE:	Overidden worker delegate destructor
//	RETURN:		None
//	DETAIL:		Dispose of delegate's resources
WorkerDelegate::~WorkerDelegate()
{
}

//	##################################################################
//	PURPOSE:	Overidden worker delegate copy constructor
//	RETURN:		New delegate instance
//	DETAIL:   
WorkerDelegate* WorkerDelegate::dup(qobjinst objinst, EXTCompInfo* pEci)
{
	WorkerDelegate* theCopy = new WorkerDelegate(objinst, pEci); //create new object
	theCopy->setWaitForComplete(mWaitForComplete);
	theCopy->setCancelIfRunning(mCancelIfRunning);
	return theCopy;      //return to caller
}

//##################################################################
//PURPOSE:  Derived Worker delegate class initialisation
//DETAIL:	Called on the main thread.
//			Note that the sample component performs no additional initialisation
eWorkerError WorkerDelegate::dInit(EXTCompInfo* pEci, EXTqlist* pParams) 
{
	eWorkerError errorCode = kWorkerNoError;
	return errorCode; 
}

//##################################################################
//PURPOSE: Client worker delegate run method
//DETAIL: Performs the actual 'work' of the worker object
//		  Return value is ignored when running on a background thread
worker::eWorkerState WorkerDelegate::dRun()
{
	eWorkerError errorCode = kWorkerNoError;
	mCancelled = qfalse;
	str255 colName, progressMethodName(QTEXT("$progress")); //interim callback method
	EXTfldval colVal, errVal;
	EXTqlist *allResults = NULL, *allErrors = NULL, *returnRow = NULL;

	mThreadCount++; //this thread just started
  
	allErrors = new EXTqlist(listVlen); //this list will store any errors generated along the way
	colName = str255(QTEXT("ErrorCode"));  allErrors->addCol(fftConstant, dpDefault, 64, &colName);
	colName = str255(QTEXT("ErrorMsg"));  allErrors->addCol(fftCharacter, dpDefault, 255, &colName);
	colName = str255(QTEXT("QueryNum"));	allErrors->addCol(fftInteger, 0, 0, &colName); //query that generated the error

	allResults = new EXTqlist(listVlen); //this list will store results generated by each work item
	colName = str255(QTEXT("Result"));	allResults->addCol(fftList, 0, 0, &colName); //the result set (list) 
	colName = str255(QTEXT("QueryNum"));	allResults->addCol(fftInteger, 0, 0, &colName); //query that generated the result

	do {
		if (getState() != worker::kStateRunning) break; //sanity check- state should be set to "running" before dRun() is called

		qlong numQueries, queryNum = 0; 
		EXTqlist *workList = NULL;
		EXTfldval fvalWork, fvalWorkText;  str255 colNameWork(QTEXT("work")), colNameQuery(QTEXT("query"));
		if (extractParameter(colNameWork, fvalWork))
		{	//a list of work items was supplied
			workList = fvalWork.getList(qfalse); //no copy
			numQueries = workList->rowCnt();
		}
		else if (extractParameter(colNameQuery, fvalWorkText)) 
		{	//a single work item was supplied
			numQueries = 1; 
		}
		else
		{	//no 'work' or 'query' parameter was supplied
			errorCode = kWorkerMissingParams;  break;
		}

		do { //repeat for each query
			errorCode = kWorkerNoError;
			if (mCancelled)	break;
			if (++queryNum > numQueries) break; //finished

			if (workList)
			{	//Get the next work item
				workList->getColVal(queryNum, 1, fftCharacter, dpFcharacter, fvalWorkText);
			}

			EXTqlist *thisResult = new EXTqlist(listVlen);
			colName = str255(QTEXT("Output data")); //add a single column
			thisResult->addCol(fftCharacter, dpFcharacter, 255, &colName);

			if (mOrphaned && mCancelIfRunning) //=> stop processing when detached from interface object
				break; 

			int rowNum = thisResult->insertRow();
			str255 textStr = fvalWorkText.getChar();
			//process the data (strip the comment delimiters)
			textStr.deleet(1, 2);

			//add the result column
			thisResult->getColValRef(rowNum, 1, colVal, qtrue); //(will alter)
			colVal.setChar(textStr);

			rowNum = allResults->insertRow();
			allResults->getColValRef(rowNum, 1, colVal, qtrue); //(will alter)
			colVal.setList(thisResult, qtrue); //transfer ownership (to allResults)

			allResults->getColValRef(rowNum, 2, colVal, qtrue);	colVal.setLong(queryNum); //the query number  

			if (!mOrphaned) //a detached delegate should not send callbacks
			{
				returnRow = getResultRow(qfalse); //do not take ownership
				returnRow->clear(listVlen);
				colName = str255(QTEXT("Progress"));
				returnRow->addCol(fftInteger, dpDefault, 0, &colName);
				returnRow->insertRow(); //insert a single row (and column)
				returnRow->getColValRef(1, 1, colVal, qtrue);
				colVal.setLong((queryNum * 100) / numQueries);
				pushWorkerCallback((ECOpushWorkerCallback_Ptr)_params[3], qtrue, &progressMethodName);
			}

			ECOsleep(400); //pause for effect

		} while (1); //repeat for multiple queries

	} while (0); //whole thing

	//add a sample error message
	int errRow = allErrors->insertRow(); str255 errStr(QTEXT("To err is human, to forgive divine"));
	allErrors->getColValRef(errRow, 1, colVal, qtrue);
	colVal.setConstant(preDAMUnknownError);
	allErrors->getColValRef(errRow, 2, colVal, qtrue);
	colVal.setChar(errStr);
	allErrors->getColValRef(errRow, 3, colVal, qtrue);
	colVal.setLong(123);

	//Create the result row (all results for this work list)
	returnRow = getResultRow(qfalse); //do not take ownership
	returnRow->clear(listVlen);
	colName = str255(QTEXT("Results")); //add results column (list)
	int resultsCol = returnRow->addCol(fftList, 0, 0, &colName);
	colName = str255(QTEXT("Errors")); //add errors column (list)
	int errorsCol = returnRow->addCol(fftList, 0, 0, &colName);

	returnRow->insertRow();
	returnRow->getColValRef(1, resultsCol, colVal, qtrue); //willalter
	colVal.setList(allResults, qtrue); //transfer ownership (to the row)
	returnRow->getColValRef(1, errorsCol, colVal, qtrue);
	colVal.setList(allErrors, qtrue); //transfer ownership (to the row)

	mThreadCount--; //this thread is about to exit

	return (mCancelled ? worker::kStateCancelled : worker::kStateComplete);
}

//##################################################################
//PURPOSE: Client worker delegate cancel method
//DETAIL:
void WorkerDelegate::dCancel()
{
	mCancelled = qtrue;
}


/* EOF */




