/******************************************************************************
 * PROJECT: New Millennium, DS1
 *          IPC (Interprocess Communication) Package
 *
 * (c) Copyright 1996 Reid Simmons.  All rights reserved.
 *
 * FILE: queryResponse.c
 *
 * ABSTRACT: Implement several variations of query/response functions for
 *           the IPC, using (modified) X_IPC library.
 *
 * REVISION HISTORY
 *
 * $Log: queryResponse.c,v $
 * Revision 1.1.1.1  2004/10/15 14:33:16  tomkol
 * Initial Import
 *
 * Revision 1.4  2003/04/20 02:28:13  nickr
 * Upgraded to IPC 3.7.6.
 * Reversed meaning of central -s to be default silent,
 * -s turns silent off.
 *
 * Revision 2.4  2002/01/03 20:52:16  reids
 * Version of IPC now supports multiple threads (Caveat: Currently only
 *   tested for Linux).
 * Also some minor changes to support Java version of IPC.
 *
 * Revision 2.3  2001/01/12 15:53:45  reids
 * Added IPC_delayResponse to enable responding to a query outside of the
 *   message handler.
 *
 * Revision 2.2  2000/07/03 17:03:28  hersh
 * Removed all instances of "tca" in symbols and messages, plus changed
 * the names of all other symbols which conflicted with TCA.  This new
 * version of IPC should be able to interoperate TCA fully.  Client
 * programs can now link to both tca and ipc.
 *
 * Revision 2.1.1.1  1999/11/23 19:07:34  reids
 * Putting IPC Version 2.9.0 under local (CMU) CVS control.
 *
 * Revision 1.3.2.6  1997/01/27 20:09:55  udo
 * ipc_2_6_006 to r3_Dev merge
 *
 * Revision 1.3.2.4  1997/01/11 01:21:19  udo
 * ipc 2.6.002 to r3_dev merge
 *
 * Revision 1.3.2.3.6.1  1996/12/24 14:41:45  reids
 * Merge the C and Lisp IPC libraries (both C and Lisp modules can now
 *   link with the same libipc.a).
 * Moved the Lisp-specific code to ipcLisp.c: Cleaner design and it will
 *   not be linked into C modules this way.
 *
 * Revision 1.3.2.3  1996/10/22 18:49:45  reids
 * Point-to-point broadcast messages.
 *
 * Revision 1.3.2.2  1996/10/18 18:10:22  reids
 * Better error checking and handling.
 *
 * Revision 1.3.2.1  1996/10/02 20:58:40  reids
 * Changes to support LISPWORKS.
 *
 * Revision 1.3  1996/05/24 20:01:43  rouquett
 * swapped include order between ipc.h globalM.h for solaris compilation
 *
 * Revision 1.2  1996/04/24 19:13:52  reids
 * Changes to support vxworks version.
 *
 * Revision 1.1  1996/03/03 04:36:22  reids
 * First release of IPC files.  Corresponds to IPC Specifiction 2.2, except
 * that IPC_readData is not yet implemented.  Also contains "cover" functions
 * for the xipc interface.
 *
 ****************************************************************/

#include "ipc.h"
#include "globalM.h"
#include "ipcPriv.h"

/****************************************************************
 *                FORWARD DECLARATIONS
 ****************************************************************/

static void queryReplyHandler (MSG_INSTANCE msgRef, BYTE_ARRAY callData,
			       void *clientData);
static BOOLEAN testQueryReplyData(QUERY_REPLY_PTR queryReply,
				  QUERY_NOTIFICATION_PTR element);

IPC_RETURN_TYPE IPC_respond (MSG_INSTANCE msgInstance, const char *msgName,
			     unsigned int length, BYTE_ARRAY content)
{
  MSG_PTR msg;
  void *replyData;
  IPC_VARCONTENT_TYPE vc;

  if (!msgName || strlen(msgName) == 0) {
    RETURN_ERROR(IPC_Null_Argument);
  } else if (!msgInstance) {
    RETURN_ERROR(IPC_Null_Argument);
  } else if (!X_IPC_CONNECTED()) {
    RETURN_ERROR(IPC_Not_Connected);
  } else {
    msg = x_ipc_msgFind(msgName);
    if (!msg) {
      RETURN_ERROR(IPC_Message_Not_Defined);
    } else if (ipcDataToSend(msg->msgData->msgFormat, msgName, 
			     length, content, &replyData, &vc) != IPC_OK) {
      PASS_ON_ERROR();
    } else {
      return ipcReturnValue(x_ipc_sendResponse(msgInstance, msg,
					       (char *)replyData,
					       ReplyClass, NULL,
					       msgInstance->responseSd));
    }
  }
}

/* If the response (IPC_respond) happens outside of the handler, 
   must call this function from within the handler!! */
IPC_RETURN_TYPE IPC_delayResponse (MSG_INSTANCE msgInstance)
{
  if (!msgInstance) {
    RETURN_ERROR(IPC_Null_Argument);
  } else if (!msgInstance->msg) {
    RETURN_ERROR(IPC_Argument_Out_Of_Range);
  } else {
    msgInstance->responded = -1;
    return IPC_OK;
  }
}

IPC_RETURN_TYPE IPC_queryNotify (const char *msgName,
				 unsigned int length, BYTE_ARRAY content,
				 HANDLER_TYPE handler, void *clientData)
{
  MSG_PTR msg;
  void *queryData;
  IPC_VARCONTENT_TYPE vc;

  if (!msgName || strlen(msgName) == 0) {
    RETURN_ERROR(IPC_Null_Argument);
  } else if (!X_IPC_CONNECTED()) {
    RETURN_ERROR(IPC_Not_Connected);
  } else {
    msg = x_ipc_msgFind(msgName);
    if (msg == NULL) {
      RETURN_ERROR(IPC_Message_Not_Defined);
    } else if (ipcDataToSend(msg->msgData->msgFormat, msgName, 
			     length, content, &queryData, &vc) != IPC_OK) {
      PASS_ON_ERROR();
    } else {
      return ipcReturnValue(x_ipc_queryNotifySend(msg, msgName, queryData,
						  (REPLY_HANDLER_FN)handler, 
						  C_LANGUAGE, clientData));
    }
  }
}

IPC_RETURN_TYPE _IPC_queryResponse (const char *msgName, 
				    unsigned int length, BYTE_ARRAY content,
				    BYTE_ARRAY *replyHandle,
				    FORMATTER_PTR *replyFormatter,
				    unsigned int timeoutMsecs)
{
  unsigned long quitTime = WAITFOREVER, now;
  QUERY_REPLY_TYPE queryReplyData;
  QUERY_NOTIFICATION_PTR queryNotif;
  IPC_RETURN_TYPE retVal = IPC_OK;

  queryReplyData.handled = FALSE;
  queryReplyData.data = NULL;
  queryReplyData.formatter = (FORMATTER_PTR)NULL;

  if (timeoutMsecs != IPC_WAIT_FOREVER) {
    /* When to timeout */
    now = x_ipc_timeInMsecs();
    quitTime = timeoutMsecs + now;
  }

  /* Send the query, and set up a handler to catch the result and
     squirrel away the response data in "queryReplyData" */
  if (IPC_queryNotify(msgName, length, content, queryReplyHandler, 
		      &queryReplyData) == IPC_Error) {
    PASS_ON_ERROR();
  } else {
    /* Check if completed, because in threaded IPC, another thread could
       have handled the notification already */
    if (!queryReplyData.handled) {
      while ((retVal = IPC_listen(timeoutMsecs)) == IPC_OK &&
	     !queryReplyData.handled) {
	/* Handled a message, but not the one we are waiting for.
	   If still have time, reset the timeout and try again */
	if (timeoutMsecs != IPC_WAIT_FOREVER) {
	  now = x_ipc_timeInMsecs();
	  if (quitTime < now) {
	    retVal = IPC_Timeout;
	    break;
	  } else {
	    timeoutMsecs = quitTime - now;
	  }
	}
      }
    }
    if (retVal != IPC_OK) {
      /* Clean up */

      LOCK_CM_MUTEX;
      queryNotif = 
	((QUERY_NOTIFICATION_PTR)
	 x_ipc_listMemReturnItem((LIST_ITER_FN)testQueryReplyData,
				 (char *)&queryReplyData,
				 GET_C_GLOBAL(queryNotificationList)));
      x_ipc_listDeleteItem((void *)queryNotif,
			   GET_C_GLOBAL(queryNotificationList));
      UNLOCK_CM_MUTEX;
      x_ipcRefFree(queryNotif->ref);
      x_ipcFree((void *)queryNotif);
    }

#ifdef LISP
  LOCK_M_MUTEX;
  if (replyHandle == (void **)LISP_DATA_FLAG()) {
    (*GET_M_GLOBAL(lispQueryResponseGlobal))((char *)queryReplyData.data,
					     queryReplyData.formatter);
    UNLOCK_M_MUTEX;
  } else
#endif /* LISP */
    {
#ifdef LISP
      UNLOCK_M_MUTEX;
#endif /* LISP */
      *replyHandle = queryReplyData.data;
      if (replyFormatter) *replyFormatter = queryReplyData.formatter;
    }

    return retVal;
  }
}

IPC_RETURN_TYPE IPC_queryResponse (const char *msgName, 
				   unsigned int length, BYTE_ARRAY content,
				   BYTE_ARRAY *replyHandle,
				   unsigned int timeoutMsecs)
{
  return _IPC_queryResponse(msgName, length, content, 
			    replyHandle, NULL, timeoutMsecs);
}

IPC_RETURN_TYPE IPC_respondVC (MSG_INSTANCE msgInstance, const char *msgName,
			       IPC_VARCONTENT_PTR varcontent)
{
  if (!varcontent) {
    RETURN_ERROR(IPC_Null_Argument);
  } else {
    return IPC_respond(msgInstance, msgName, 
		       varcontent->length, varcontent->content);
  }
}

IPC_RETURN_TYPE IPC_queryNotifyVC (const char *msgName,
				   IPC_VARCONTENT_PTR varcontent,
				   HANDLER_TYPE handler, 
				   void *clientData)
{
  if (!varcontent) {
    RETURN_ERROR(IPC_Null_Argument);
  } else {
    return IPC_queryNotify(msgName, varcontent->length, varcontent->content,
			   handler, clientData);
  }
}

IPC_RETURN_TYPE IPC_queryResponseVC (const char *msgName, 
				     IPC_VARCONTENT_PTR varcontent,
				     BYTE_ARRAY *replyHandle, 
				     unsigned int timeoutMsecs)
{
  if (!varcontent) {
    RETURN_ERROR(IPC_Null_Argument);
  } else {
    return _IPC_queryResponse(msgName, varcontent->length, varcontent->content,
			      replyHandle, NULL, timeoutMsecs);
  }
}

/****************************************************************
 *                INTERNAL FUNCTIONS 
 ****************************************************************/

/* When a response to the message comes in, set the fields of the
   queryReplyData to indicate that the message has been handled */

static void queryReplyHandler (MSG_INSTANCE msgRef, BYTE_ARRAY callData,
			       void *clientData)
{
  QUERY_REPLY_PTR queryReplyData = (QUERY_REPLY_PTR)clientData;

  queryReplyData->handled = TRUE;
  queryReplyData->data = callData;
  queryReplyData->formatter = IPC_msgInstanceFormatter(msgRef);
}

static BOOLEAN testQueryReplyData (QUERY_REPLY_PTR queryReply,
				   QUERY_NOTIFICATION_PTR element)
{
  return (queryReply == element->clientData);
}
