//-----------------------------------------------------------------------------
// Copyright (c) 1994,1995 Southeastern Universities Research Association,
//                         Continuous Electron Beam Accelerator Facility
//
// This software was developed under a United States Government license
// described in the NOTICE file included as part of this distribution.
//
//-----------------------------------------------------------------------------
//
// Description:
//      Implementation of rnsRequesObject Class
//
// Author:  Jie Chen
//
// Revision History:
//   rnsRequestObject.cc,v
// Revision 1.7  1998/04/01  15:32:12  chen
// fix transaction done flag
//
// Revision 1.6  1998/03/19  18:31:43  chen
// add monitorEntry capability
//
// Revision 1.5  1998/02/09  13:35:47  chen
// duplicate cdev data in the monitor object
//
// Revision 1.4  1998/02/06  15:11:25  chen
// add connection management for name server
//
// Revision 1.3  1998/02/05  15:59:19  akers
// Ongoing development
//
// Revision 1.2  1998/01/30  18:31:37  akers
// Ongoing development
//
// Revision 1.1  1998/01/22  17:07:51  akers
// Addition of new NameServer
//
//
//
#include <cdevErrCode.h>
#include <cdevCommon.h>
#include <rnsMonitorObj.h>
#include <cdevClock.h>

#include "rnsService.h"
#include "rnsRequestObject.h"
#include "rnsQueryCollector.h"
#include <rsvcClient.h>

char* rnsRequestObject::stags_[] = {"name", "domain"};
int   rnsRequestObject::numstags_ = 2;

#ifdef _WIN32
inline int strcasecmp ( const char * string1, const char * string2 )
	{
	return stricmp(string1, string2);
	}
#endif

// *****************************************************************************
// * The localQuerySynchronizer class has been added to allow me to make
// * synchronous requests to query the NameServer.
// *****************************************************************************
class localQuerySynchronizer
{
public:
	static int          cbDone;
	static long         cbIdent;
	static cdevData     cbData;
	static cdevCallback getCallbackObj ( void )
		{
		cbData.remove();
		cbDone = 0;
		cbIdent++;
		cdevCallback cbObj(callback, (void *)cbIdent);
		return cbObj;
		}
	static void callback ( int /*status*/, void * userarg, cdevRequestObject &/*reqObj*/, cdevData &data)
		{
		if(((long)userarg)==cbIdent)
			{
			cbDone = 1;
			cbData = data;
			}
		}
};

int          localQuerySynchronizer::cbDone  = 0;
long         localQuerySynchronizer::cbIdent = 0;
cdevData     localQuerySynchronizer::cbData;

rnsRequestObject::rnsRequestObject (char* device, char* msg,
				   rnsService* svc,
				   cdevSystem& system)
:cdevRequestObject (device, msg, system), client_ (0)
{
#ifdef _TRACE_OBJECTS
  printf ("              Create rnsRequestObject Class Object\n");
#endif
  client_ = &(svc->client_);

  // convert msg to integer action
  rnsRequestObject::findAction (msg, action_);
}

rnsRequestObject::~rnsRequestObject (void)
{
#ifdef _TRACE_OBJECTS
  printf ("              Delete rnsRequestObject Class Object\n");
#endif
}

int
rnsRequestObject::getState (void)
{
  if (client_->connected ())
    return CDEV_STATE_CONNECTED;
  return CDEV_STATE_NOTCONNECTED;
}

int
rnsRequestObject::sendCallback (cdevData& out, cdevCallback& cbk)
{
  return sendCallback (&out, cbk);
}


int
rnsRequestObject::sendCallback (cdevData* out, cdevCallback& userCallback)
{
  if (deferExecution (out, &userCallback))
    return CDEV_SUCCESS;

  int status = CDEV_SUCCESS;
  cdevTranObj* xobj = 0;
  rnsMonitorObj* mobj = 0;
  rnsService* nservice = (rnsService *)service_;

  // copy userCallback into the following callback, which will free 
  // the user from worrying about cdevCallback object
  // note: cdevCallback's new operator is an overloaded operator
  cdevCallback* callback = 0;

  if (client_->connected ()) {
    switch (action_) {
    case rnsRequestObject::GET:
      {
	if (out == 0)
	  return CDEV_INVALIDARG;

	callback = new cdevCallback (userCallback);
	xobj = new cdevTranObj (&system_, this, 0, callback);

	rsvcData outdata;
	if (rnsRequestObject::dataCdevToRsvc (*out, outdata) != CDEV_SUCCESS)
	  status = CDEV_INVALIDARG;
	else {
	  if (client_->getValue (_RSVC_CDEV_SERVERS, outdata,
				 &(rnsRequestObject::getCallback),
				 (void *)xobj) != CDEV_SUCCESS)
	    status = CDEV_IOFAILED;
	}
	if (status != CDEV_SUCCESS) 
	  delete xobj;
      }
    break;
    case rnsRequestObject::QUERY:
      {
	if (out == 0)
	  return CDEV_INVALIDARG;

	callback = (cdevCallback *)new rnsQueryCallback(userCallback);
	xobj = new cdevTranObj (&system_, this, 0, callback);
	mobj = new rnsMonitorObj  (*xobj);

	char qmsg[1024];
	if (out->get ("queryMsg", qmsg, sizeof (qmsg)) != CDEV_SUCCESS)
	  status = CDEV_INVALIDARG;
	else {
	  mobj->out_ = new cdevData (*out);
	  if (client_->query (_RSVC_CDEV_SERVERS, qmsg,
			      &(rnsRequestObject::queryCallback),
			      (void *)mobj) != CDEV_SUCCESS)
	    status = CDEV_IOFAILED;
	  else
	    nservice->addMonitorObject (mobj);
	}
	if (status != CDEV_SUCCESS) {
	  delete xobj;
	  delete mobj;
	}
      }
    break;
    case rnsRequestObject::MONITORON:
      {
	if (out == 0) 
	  return CDEV_INVALIDARG;

	if (nservice->hasCallback (userCallback)) 
	  return CDEV_INVALIDARG;

	callback = new cdevCallback (userCallback);
	xobj = new cdevTranObj (&system_, this, 0, callback);
	mobj = new rnsMonitorObj  (*xobj);


	rsvcData outdata;
	if (rnsRequestObject::dataCdevToRsvc (*out, outdata) != CDEV_SUCCESS) 
	  status = CDEV_INVALIDARG;
	else {
	  mobj->out_ = new cdevData (*out);
	  if (client_->monitorValue (_RSVC_CDEV_SERVERS, outdata,
				     &(rnsRequestObject::monitorCallback),
				     (void *)mobj) != CDEV_SUCCESS)
	    status = CDEV_IOFAILED;
	  else
	    nservice->addMonitorObject (mobj);
	}
	if (status != CDEV_SUCCESS) {
	  delete xobj;
	  delete mobj;
	}
      }
    break;
    case rnsRequestObject::MONITOROFF:
      {
	if (out == 0)
	  return CDEV_INVALIDARG;

	rnsMonitorObj* mobj = 0;
	rsvcData outdata;
	if (rnsRequestObject::dataCdevToRsvc (*out, outdata) != CDEV_SUCCESS)
	  status = CDEV_INVALIDARG;
	else {
	  if ((mobj = nservice->hasCallback (userCallback)) == 0) {
	    printf ("this callback function is not registered\n");
	    status = CDEV_INVALIDARG;
	  }
	  else 
	    status = monitorOff (outdata, mobj);
	}
      }
    break;
    case rnsRequestObject::MONITORENTRY:
      {
	if (nservice->hasCallback (userCallback)) 
	  return CDEV_INVALIDARG;

	callback = new cdevCallback (userCallback);
	xobj = new cdevTranObj (&system_, this, 0, callback);
	mobj = new rnsMonitorObj  (*xobj);

	rsvcData empty;
	if (client_->monitorIncomingEntries (_RSVC_CDEV_SERVERS, empty,
				     &(rnsRequestObject::entryCallback),
				     (void *)mobj) != CDEV_SUCCESS)
	  status = CDEV_IOFAILED;
	else
	  nservice->addMonitorObject (mobj);

	if (status != CDEV_SUCCESS) {
	  delete xobj;
	  delete mobj;
	}
      }
    break;
    case rnsRequestObject::MONITORENTRYOFF:
      {
	rnsMonitorObj* mobj = 0;
	if ((mobj = nservice->hasCallback (userCallback)) == 0) {
#ifdef _CDEV_DEBUG
	  printf ("this callback function is not registered\n");
#endif
	  status = CDEV_INVALIDARG;
	}
	else 
	  status = monitorEntryOff (mobj);
      }
    break;
    default:
      status = CDEV_INVALIDARG;
      break;
    }
  }
  else
    status = CDEV_NOTCONNECTED;

  return status;
}

int
rnsRequestObject::sendMonitor (rnsMonitorObj* mobj)
{
  rsvcData outdata;
  int      status = CDEV_SUCCESS;

  if (rnsRequestObject::dataCdevToRsvc (*mobj->out_, outdata)!= CDEV_SUCCESS) {
    mobj->started_ = 1;
    status = CDEV_INVALIDARG;
  }
  else {
    if ((status = client_->monitorValue (_RSVC_CDEV_SERVERS, outdata,
					 &(rnsRequestObject::monitorCallback),
					 (void *)mobj)) != CDEV_SUCCESS)
      mobj->started_ = 1;
  }
  return status;
}  

int
rnsRequestObject::sendEntryMonitor (rnsMonitorObj* mobj)
{
  int      status = CDEV_SUCCESS;
  rsvcData empty;

  if ((status = client_->monitorIncomingEntries (_RSVC_CDEV_SERVERS, empty,
				       &(rnsRequestObject::entryCallback),
				       (void *)mobj)) != CDEV_SUCCESS)
    mobj->started_ = 1;
  return status;
}  

void
rnsRequestObject::getCallback (int status, void* arg, rsvcData* data)
{
  cdevTranObj* xobj = (cdevTranObj *)arg;
  

  // if a callback has been discarded, do not do anything
  if (xobj->isTrash ()) {
    delete xobj;
    return;
  }

  cdevCallback*      cbk = xobj->userCallback_;
  cdevRequestObject* obj = xobj->reqObj_;
  cdevData*          rda = xobj->resultData_;
  cdevData           result;

  if (rda) // send no block
    rda->remove ();
  else
    rda = &result;

  if (status == RSVC_SUCCESS)
    rnsRequestObject::dataRsvcToCdev (*data, *rda);

  if (cbk)
    cbk->fireCallback (status, cbk->userarg (), *obj, result, 0);

  delete xobj;
}

void
rnsRequestObject::queryCallback (int status, void* arg, rsvcData* data)
{
  rnsMonitorObj* mobj = (rnsMonitorObj *)arg;

  cdevCallback*      cbk = mobj->userCallback_;
  cdevRequestObject* obj = mobj->reqObj_;
  cdevData           result;
  int                cbk_finished = 1;

  // first time this callback is called
  if (mobj->tobj_) {
    delete mobj->tobj_;
    mobj->tobj_ = 0;
  }

  if (status == RSVC_ERROR || status == RSVC_NOTFOUND) 
    cbk->fireCallback (status, cbk->userarg (), *obj, result, 0);
  else if (status == RSVC_INCOMPLETE) {
    rnsRequestObject::dataRsvcToCdev (*data, result);
    cbk->fireCallback (status, cbk->userarg (), *obj, result, 1);
    cbk_finished = 0;
  }
  else if (status == RSVC_QUERYMSG_ERR) 
    cbk->fireCallback (CDEV_MSG_ERR, cbk->userarg (), *obj, result, 0);
  else {
    rnsRequestObject::dataRsvcToCdev (*data, result);
    cbk->fireCallback (status, cbk->userarg (), *obj, result, 0);
  }
  if (cbk_finished) {
    rnsRequestObject* nsreq = (rnsRequestObject *)obj;
    nsreq->removeMonitorObject (mobj);
  }
}

void
rnsRequestObject::monitorCallback (int status, void* arg, rsvcData* data)
{
  rnsMonitorObj* mobj = (rnsMonitorObj *)arg;

  cdevCallback*      cbk = mobj->userCallback_;
  cdevRequestObject* obj = mobj->reqObj_;
  cdevData           result;
  int                cbk_finished = 1;

  // first time this callback is called
  if (mobj->tobj_) {
    delete mobj->tobj_;
    mobj->tobj_ = 0;
  }

  if (status == RSVC_SUCCESS) {
    rnsRequestObject::dataRsvcToCdev (*data, result);
    cbk->fireCallback (status, cbk->userarg (), *obj, result, 1);
    cbk_finished = 0;
    if (!mobj->started_)
      mobj->started_ = 1;
  }
  else if (status == RSVC_CBK_FINISHED) {
    rnsRequestObject::dataRsvcToCdev (*data, result);
    if (mobj->started_)
      cbk->fireCallback (status, cbk->userarg (), *obj, result, 0);
  }
  else {
    if (mobj->started_)
      cbk->fireCallback (status, cbk->userarg (), *obj, result, 0);
  }

  if (cbk_finished && mobj->started_) {
    rnsRequestObject* nsreq = (rnsRequestObject *)obj;
    nsreq->removeMonitorObject (mobj);
  }
}

void
rnsRequestObject::entryCallback (int status, void* arg, rsvcData* data)
{
  rnsMonitorObj* mobj = (rnsMonitorObj *)arg;

  cdevCallback*      cbk = mobj->userCallback_;
  cdevRequestObject* obj = mobj->reqObj_;
  cdevData           result;
  int                cbk_finished = 1;

  // first time this callback is called
  if (mobj->tobj_) {
    delete mobj->tobj_;
    mobj->tobj_ = 0;
  }

  if (status == RSVC_SUCCESS || status == RSVC_INCOMPLETE) {
    rnsRequestObject::dataRsvcToCdev (*data, result);
    cbk->fireCallback (RSVC_SUCCESS, cbk->userarg (), *obj, result, 1);
    cbk_finished = 0;
    if (!mobj->started_)
      mobj->started_ = 1;
  }
  else if (status == RSVC_CBK_FINISHED) {
    rnsRequestObject::dataRsvcToCdev (*data, result);
    if (mobj->started_)
      cbk->fireCallback (status, cbk->userarg (), *obj, result, 0);
  }
  else {
    if (mobj->started_)
      cbk->fireCallback (status, cbk->userarg (), *obj, result, 1);
  }

  if (cbk_finished && mobj->started_) {
    rnsRequestObject* nsreq = (rnsRequestObject *)obj;
    nsreq->removeMonitorObject (mobj);
  }
}

int
rnsRequestObject::monitorOff (rsvcData& data, rnsMonitorObj* mobj)
{
  int status;
  status = client_->monitorOffValue (_RSVC_CDEV_SERVERS, data,
				     &(rnsRequestObject::monitorCallback),
				     (void *)mobj);
  return status;
}

int
rnsRequestObject::monitorEntryOff (rnsMonitorObj* mobj)
{
  int status;
  rsvcData empty;
  status = client_->monitorOffIncomingEntries (_RSVC_CDEV_SERVERS, empty,
				     &(rnsRequestObject::entryCallback),
				     (void *)mobj);
  return status;
}

int
rnsRequestObject::sendNoBlock (cdevData& out, cdevData& result)
{
  return sendNoBlock (&out, &result);
}

int
rnsRequestObject::sendNoBlock (cdevData* out, cdevData& result)
{
  return sendNoBlock (out, &result);
}

int
rnsRequestObject::sendNoBlock (cdevData& out, cdevData* result)
{
  return sendNoBlock (&out, result);
}

int
rnsRequestObject::sendNoBlock (cdevData* out, cdevData* result)
{
  if (deferExecution (out, result))
    return CDEV_SUCCESS;

  int status = CDEV_SUCCESS;
  cdevTranObj* xobj = 0;
  rnsService* nservice = (rnsService *)service_;

  if (client_->connected ()) {
    switch (action_) {
    case rnsRequestObject::GET:
      {
	if (out == 0 || result == 0)
	  return CDEV_INVALIDARG;

	xobj = new cdevTranObj (&system_, this, result, 0);

	rsvcData outdata;
	if (rnsRequestObject::dataCdevToRsvc (*out, outdata) != CDEV_SUCCESS)
	  status = CDEV_INVALIDARG;
	else {
	  if (client_->getValue (_RSVC_CDEV_SERVERS, outdata,
				 &(rnsRequestObject::getCallback),
				 (void *)xobj) != CDEV_SUCCESS)
	    status = CDEV_IOFAILED;
	}
	if (status != CDEV_SUCCESS) 
	  delete xobj;
      }
    break;
    default:
      status = CDEV_INVALIDARG;
      break;
    }
  }
  else
    status = CDEV_NOTCONNECTED;

  return status;
}  

int
rnsRequestObject::send (cdevData& out, cdevData& result)
{
  return send (&out, &result);
}

int
rnsRequestObject::send (cdevData* out, cdevData& result)
{
  return send (out, &result);
}

int
rnsRequestObject::send (cdevData& out, cdevData* result)
{
  return send (&out, result);
}

int
rnsRequestObject::send (cdevData* out, cdevData* result)
{
  int status = CDEV_SUCCESS;
  rnsService* nservice = (rnsService *)service_;

  if (client_->connected ()) {
    switch (action_) {
    case rnsRequestObject::GET:
      if (out == 0 || result == 0)
	status = CDEV_INVALIDARG;
      else {
	cdevGroup grp (2, system_);
	grp.start ();
	sendNoBlock (out, result);
	grp.end ();
	grp.pend (4.0);
	if (!grp.allFinished ())
	  status = CDEV_TIMEOUT;
      }
      break;
    case rnsRequestObject::QUERY:
    if(out==0 || result==0)
    	status = CDEV_INVALIDARG;
    else {
	cdevCallback cb = localQuerySynchronizer::getCallbackObj();
	if((status=sendCallback(out, cb))==CDEV_SUCCESS)
		{
		cdevTimeValue t(5.0);
		cdevClock     timer;
		timer.schedule(NULL, t);
		
		while(!localQuerySynchronizer::cbDone && !timer.expired()) nservice->poll();
	    	
	    	if(!localQuerySynchronizer::cbDone) status = CDEV_TIMEOUT;
	    	else *result = localQuerySynchronizer::cbData;
    		}
    	}
    break;
    default:
      status = CDEV_INVALIDARG;
      break;
    }
  }
  else
    status = CDEV_NOTCONNECTED;
  
  return status;
}

int
rnsRequestObject::dataCdevToRsvc (cdevData& data, rsvcData& res)
{
  char val[1024];
  for (int i = 0; i < rnsRequestObject::numstags_; i++) {
    if (data.get (rnsRequestObject::stags_[i], val, sizeof (val)) 
	!= CDEV_SUCCESS)
      return CDEV_ERROR;
    
    res.insert (rnsRequestObject::stags_[i], val);
  }
  return CDEV_SUCCESS;
}


int
rnsRequestObject::dataRsvcToCdev (rsvcData& data, cdevData& res)
{
  rsvcDataIterator ite (&data);
  int     type;
  int     tag;
  size_t  numelems;
  size_t  dim;
  char*   ctag;

  for (ite.init (); !ite ; ++ite) {
    ctag = (char *)ite.tag ();
    type = data.getType (ctag);
    data.getElems (ctag, &numelems);
    data.getDim (ctag, &dim);

    if (cdevData::tagC2I (ctag, &tag) != CDEV_SUCCESS || dim > 1)
      continue;

    switch (type) {
    case RSVC_BYTE:
    case RSVC_INT16:
    case RSVC_UINT16:
    case RSVC_INT32:
    case RSVC_UINT32:
      {
	if (dim == 0) {
	  int val;
	  data.get (ctag, &val);
	  res.insert (tag, val);
	}
	else {
	  int* val = new int[numelems];
	  data.get (ctag, val);
	  res.insert (tag, val, numelems);
	  delete []val;
	}
      }
    break;
    case RSVC_FLOAT:
      {
	if (dim == 0) {
	  float val;
	  data.get (ctag, &val);
	  res.insert (tag, val);
	}
	else {
	  float* val = new float[numelems];
	  data.get (ctag, val);
	  res.insert (tag, val, numelems);
	  delete []val;
	}
      }
    break;
    case RSVC_DOUBLE:
      {
	if (dim == 0) {
	  double val;
	  data.get (ctag, &val);
	  res.insert (tag, val);
	}
	else {
	  double* val = new double[numelems];
	  data.get (ctag, val);
	  res.insert (tag, val, numelems);
	  delete []val;
	}
      }
    break;
    case RSVC_STRING:
      {
	if (dim == 0) {
	  char val[1024];
	  data.get (ctag, val, sizeof (val));
	  res.insert (tag, val);
	}
	else {
	  char** val = new char*[numelems];
	  data.get (ctag, val);
	  res.insert (tag, val, numelems);
	}
      }
    break;
    default:
      break;
    }
  }
  return CDEV_SUCCESS;
}

int
rnsRequestObject::removeMonitorObject (rnsMonitorObj* mobj)
{
  rnsService *nservice = (rnsService *)service_;

  nservice->removeMonitorObject (mobj);
  delete mobj;

  return CDEV_SUCCESS;
}

int
rnsRequestObject::findAction (char* msg, int& action)
{
  if (strcasecmp (msg, "query") == 0)
    action = rnsRequestObject::QUERY;
  else if (strcasecmp (msg, "get") == 0)
    action = rnsRequestObject::GET;
  else if (strcasecmp (msg, "monitorOn") == 0)
    action = rnsRequestObject::MONITORON;
  else if (strcasecmp (msg, "monitorOff") == 0)
    action = rnsRequestObject::MONITOROFF;
  else if (strcasecmp (msg, "monitorEntry") == 0)
    action = rnsRequestObject::MONITORENTRY;
  else if (strcasecmp (msg, "monitorEntryOff") == 0)
    action = rnsRequestObject::MONITORENTRYOFF;
  else
    action = rnsRequestObject::UNKNOWN;

  return CDEV_SUCCESS;
}
