/*-----------------------------------------------------------------*/
/*! 
  \file scscpcomputationxx.cpp
  \brief C++ computation
  \author  M. Gastineau 
           Astronomie et Systemes Dynamiques, IMCCE, CNRS, Observatoire de Paris. 

   Copyright, 2009,2010,2012, M. Gastineau, IMCCE-CNRS
   email of the author : gastineau@imcce.fr
*/
/*-----------------------------------------------------------------*/

/*-----------------------------------------------------------------*/
/* License  of this file :
 This file is "dual-licensed", you have to choose one  of the two licenses 
 below to apply on this file.
 
    CeCILL-C
    	The CeCILL-C license is close to the GNU LGPL.
    	( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html )
 
 or CeCILL v2.0
      The CeCILL license is compatible with the GNU GPL.
      ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
 

This library is governed by the CeCILL-C or the CeCILL license under 
French law and abiding by the rules of distribution of free software.  
You can  use, modify and/ or redistribute the software under the terms 
of the CeCILL-C or CeCILL license as circulated by CEA, CNRS and INRIA  
at the following URL "http://www.cecill.info". 

As a counterpart to the access to the source code and  rights to copy,
modify and redistribute granted by the license, users are provided only
with a limited warranty  and the software's author,  the holder of the
economic rights,  and the successive licensors  have only  limited
liability. 

In this respect, the user's attention is drawn to the risks associated
with loading,  using,  modifying and/or developing or reproducing the
software by the user in light of its specific status of free software,
that may mean  that it is complicated to manipulate,  and  that  also
therefore means  that it is reserved for developers  and  experienced
professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their
requirements in conditions enabling the security of their systems and/or 
data to be ensured and,  more generally, to use and operate it in the 
same conditions as regards security. 

The fact that you are presently reading this means that you have had
knowledge of the CeCILL-C or CeCILL license and that you accept its terms.
*/
/*-----------------------------------------------------------------*/

#if HAVE_CONFIG_H
#include "scscpconfig.h"
#endif /*HAVE_CONFIG_H*/
#if HAVE_WINDOWS_H
#include <windows.h>
#endif /*HAVE_CONFIG_H*/

#include <cstdio>
#if HAVE_STRING_H
#include <cstring>
#endif

#define __SCSCP_WITHIN_SCSCP
#include "scscp.h"
#include "scscpinternal.h"
#include "scscpfileclient.h"
#include "scscpfileserver.h"
#include "scscpprocedurecall.h"
#include "scscpprocedurecompleted.h"
#include "scscpdebug.h"
#include "scscpomdoc.h"
#include "scscpbinary.h"
#include "scscpxmlparser.h"
#include "scscpxx.h"

namespace SCSCP
{

#include "scscpstream.h"

/*-----------------------------------------------------------------*/
/*! Default constructor
*/
/*-----------------------------------------------------------------*/
ProcedureCall::ProcedureCall(Client& session) throw (Exception) 
     : m_client(&session.m_client), m_clientsession(&session), m_serversession(NULL), 
       m_postream(NULL), m_punfostream(NULL), m_punfistream(NULL)
{
 m_status = SCSCP_STATUS_INITIALIZER;
 if (!SCSCP_co_init(&m_calloptions, &m_status))  throw Exception(&m_status);
 if (!SCSCP_ro_init(&m_returnoptions, &m_status))  throw Exception(&m_status); 
}

/*-----------------------------------------------------------------*/
/*! Default constructor
*/
/*-----------------------------------------------------------------*/
ProcedureCall::ProcedureCall(IncomingClient& session) throw (Exception) 
     : m_client(&session.m_client), m_clientsession(NULL), m_serversession(&session), 
       m_postream(NULL), m_punfostream(NULL), m_punfistream(NULL)
{
 m_status = SCSCP_STATUS_INITIALIZER;
 if (!SCSCP_co_init(&m_calloptions, &m_status))  throw Exception(&m_status);
 if (!SCSCP_ro_init(&m_returnoptions, &m_status))  throw Exception(&m_status); 
}


/*-----------------------------------------------------------------*/
/*! destructor
*/
/*-----------------------------------------------------------------*/
ProcedureCall::~ProcedureCall()
{
 SCSCP_debugprint("ProcedureCall::~ProcedureCall() -enter\n");
 SCSCP_co_clear(&m_calloptions, &m_status);
 SCSCP_ro_clear(&m_returnoptions, &m_status);
 SCSCP_status_clear(&m_status);
 if (m_postream) { delete m_postream;  m_postream = NULL; }
 if (m_punfostream) { delete m_punfostream;  m_punfostream = NULL; }
 if (m_punfistream) { delete m_punfistream;  m_punfistream = NULL; }
 SCSCP_debugprint("ProcedureCall::~ProcedureCall() - return\n");
}

/*-----------------------------------------------------------------*/
/*! set the runtime limit
   @param p_time (in) runtime limit
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_runtimelimit(size_t p_time)
{
 return  SCSCP_co_set_runtimelimit(&m_calloptions, p_time, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the runtime limit
  if the runtime limit is unknown => it returns 0
   @param p_time (out) runtime limit
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_runtimelimit(size_t& p_time)
{
 return  SCSCP_co_get_runtimelimit(&m_calloptions, &p_time, &m_status);
}


/*-----------------------------------------------------------------*/
/*! set the minimal memory
   @param memsize (in) minimal memory required
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_minmemory(size_t memsize)
{
 return SCSCP_co_set_minmemory(&m_calloptions, memsize, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the minimal memory
  if the minimal memory is unknown => it returns 0 
   @param memsize (out) minimal memory required
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_minmemory(size_t& memsize)
{
 return SCSCP_co_get_minmemory(&m_calloptions, &memsize, &m_status);
}

/*-----------------------------------------------------------------*/
/*! set the maximal memory
   @param options (inout) options
   @param memsize (in) maximal memory required
   @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_maxmemory(size_t memsize)
{
 return SCSCP_co_set_maxmemory(&m_calloptions, memsize, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the maximal memory
  if the minimal memory is unknown => it returns 0 
   @param memsize (out) maximal memory required
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_maxmemory(size_t& memsize)
{
 return SCSCP_co_get_maxmemory(&m_calloptions, &memsize, &m_status);
}

/*-----------------------------------------------------------------*/
/*! set the debug level
   @param debuglevel (in) debug level
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_debuglevel(int debuglevel)
{
 return SCSCP_co_set_debuglevel(&m_calloptions, debuglevel, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the debug level
  if the minimal memory is unknown => it returns 0 
   @param debuglevel (out) debug level
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_debuglevel(int& debuglevel)
{
 return SCSCP_co_get_debuglevel(&m_calloptions, &debuglevel, &m_status);
}

/*-----------------------------------------------------------------*/
/*! set the current encoding for the OpenMath objects  
 @param encodingtype (in) new encoding (XML or binary)
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_encodingtype(SCSCP_encodingtype encodingtype)
{
 return SCSCP_sc_set_encodingtype(m_client, encodingtype, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the current encoding for the OpenMath objects    
 @param encodingtype (out) current encoding (XML or binary)
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_encodingtype(SCSCP_encodingtype& encoding)
{
 return SCSCP_sc_get_encodingtype(m_client, &encoding, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the return type of the procedure call    
 @param returntype (out) return type
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_returntype(SCSCP_option_return& returntype)
{
 return SCSCP_co_get_returntype(&m_calloptions, &returntype, &m_status);
}


/*-----------------------------------------------------------------*/
/*! get the call id    
 @param callid (out) string of the call id
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_callid(const char*& callid)
{
 return SCSCP_co_get_callid(&m_calloptions, &callid, &m_status);
}


/*-----------------------------------------------------------------*/
/*! set the runtime usage
   @param time (in) time usage
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_runtime(size_t p_time)
{
 return SCSCP_ro_set_runtime(&m_returnoptions, p_time, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the runtime usage
  if the runtime usage is unknown => it returns 0 
   @param time (out) time usage. can't be NULL
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_runtime(size_t& p_time)
{
 return SCSCP_ro_get_runtime(&m_returnoptions, &p_time, &m_status);
}

/*-----------------------------------------------------------------*/
/*! set the memory usage
   @param options (inout) options
   @param memsize (in) memory usage
   @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_memory(size_t mem)
{
 return SCSCP_ro_set_memory(&m_returnoptions, mem, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the memory usage
  if the runtime usage is unknown => it returns 0 
   @param memsize (out) memory usage. can't be NULL
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_memory(size_t& mem)
{
 return SCSCP_ro_get_memory(&m_returnoptions, &mem, &m_status);
}

/*-----------------------------------------------------------------*/
/*! set the information message
   @param buffer (in) string buffer
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::set_message(const char *buffer)
{
 return SCSCP_ro_set_message(&m_returnoptions, buffer, &m_status);
}

/*-----------------------------------------------------------------*/
/*! get the information message
  if the runtime usage is unknown => it returns 0 
   @param buffer (out) string buffer
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::get_message(const char *& buffer)
{
 return SCSCP_ro_get_message(&m_returnoptions, &buffer, &m_status);
}

/*-----------------------------------------------------------------*/
/*! flush the output stream
*/
/*-----------------------------------------------------------------*/
void ProcedureCall::flushostream()  
{
if (m_postream) { m_postream->flush(); delete m_postream;  m_postream = NULL; }
 if (m_punfostream) { delete m_punfostream;  m_punfostream = NULL; }
}

/*-----------------------------------------------------------------*/
/*! create an output unformatted stream
*/
/*-----------------------------------------------------------------*/
Ounfstream* ProcedureCall::createounfstream() throw()  
{
 m_punfostream = new Ounfstream(m_client, &m_status);
 return m_punfostream;
}

/*-----------------------------------------------------------------*/
/*! create an output unformatted stream
*/
/*-----------------------------------------------------------------*/
Ofmtstream* ProcedureCall::createofmtstream() throw()  
{
 m_postream = new Ofmtstream(m_client, &m_status);
 return m_postream;
}

/*-----------------------------------------------------------------*/
/*! create an input unformatted stream
 @param node (inout) root node of the stream
*/
/*-----------------------------------------------------------------*/
Iunfstream* ProcedureCall::createiunfstream(SCSCP_xmlnodeptr node) throw()  
{
 m_punfistream = new Iunfstream( node, &m_status);
 return m_punfistream;
}

/*-----------------------------------------------------------------*/
/*! send a information message
 @param buffer (inout) message
*/
/*-----------------------------------------------------------------*/
int ProcedureCall::sendinfomessage(const char *buffer)  
{
 return SCSCP_sc_infomessagesend(get_client(),buffer,&m_status);
}

/*-----------------------------------------------------------------*/
/*! report error if res=0 and raise an exception
 @param res (in) value of the previous call
*/
/*-----------------------------------------------------------------*/
void ProcedureCall::report_error(int res) throw(Exception)
{
 if (!res)
 {
  if (SCSCP_status_is(&m_status)==SCSCP_STATUS_RECVQUIT) 
  {
   if (m_serversession) m_serversession->set_status(&m_status);
   if (m_clientsession) m_clientsession->set_status(&m_status);
  }
  throw Exception(&m_status);
 }
}
 
/*-----------------------------------------------------------------*/
/*! Default constructor
*/
/*-----------------------------------------------------------------*/
ClientComputation::ClientComputation(Client& session) throw (Exception) 
     : ProcedureCall(session)
{
}

/*-----------------------------------------------------------------*/
/*! destructor
*/
/*-----------------------------------------------------------------*/
ClientComputation::~ClientComputation()
{
}

/*-----------------------------------------------------------------*/
/*! perform a procedure call : send an openmath buffer to the server    
 @param openmathbuffer (in) openmath buffer
 @param lenbuffer (in) number of valid bytes in openmathbuffer
 @param returntype (in) return type of the call
*/
/*-----------------------------------------------------------------*/
int ClientComputation::send(const char *openmathbuffer, size_t lenbuffer, SCSCP_option_return returntype)   throw (Exception) 
{
 int res;
 res = SCSCP_co_set_returntype(get_calloptions(),returntype, get_status());
 if (res) res = SCSCP_sc_callsendstr(get_client(), get_calloptions(), openmathbuffer, get_status());
 report_error(res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! wait and receive an answer of a  procedure call from the server 
 @param openmathbuffer (out) openmath result
 @param lenbuffer (out) number of valid bytes in openmathbuffer
 @param msgtype (out) type of the answer
*/
/*-----------------------------------------------------------------*/
int ClientComputation::recv(SCSCP_msgtype& msgtype, char*& openmathbuffer, size_t& lenbuffer)   throw (Exception) 
{
 char *cdnameerr;
 char *symbolnameerr;
 char *msg;
 int res;
 res = SCSCP_sc_callrecvheader(get_client(), get_returnoptions(), &msgtype, get_status());
 if (res)
 {
  switch(msgtype)
  {
   case SCSCP_msgtype_ProcedureCompleted : 
           res = SCSCP_sc_callrecvcompleted(get_client() , &openmathbuffer, get_status()); 
           lenbuffer = ::strlen(openmathbuffer);
           break;
           
   case SCSCP_msgtype_ProcedureTerminated : 
           res = SCSCP_sc_callrecvterminated(get_client(), &cdnameerr, &symbolnameerr, &msg, get_status());
           lenbuffer = 0;
           if (res) SCSCP_status_setexecfailed(get_status(), cdnameerr, symbolnameerr, msg);
           res = 0;
           break;
           
   default : openmathbuffer = NULL; lenbuffer=0; break;
  }
 }
 report_error(res);

 return res;
}

/*-----------------------------------------------------------------*/
/*! perform a procedure call : send an openmath buffer to the server  
 It returns a stream. The application will write Openmath expression in the stream.
 .e.g: stream<<"<OMI>1</OMI>";
 @param returntype (in) return type of the call
 @param pstream (out) pointer to a stream
*/
/*-----------------------------------------------------------------*/
int ClientComputation::send(SCSCP_option_return returntype, std::ostream*& pstream)  throw (Exception) 
{
 int res = SCSCP_co_set_returntype(get_calloptions(),returntype, get_status());
 if (res) res = SCSCP_procedurecall_write_start(get_client() , *get_calloptions(), get_status());
 if (res)
 {
   pstream = createofmtstream();
 }
 else 
 {
   pstream = NULL;
   report_error(res);
 }
 return res;
}

/*-----------------------------------------------------------------*/
/*! perform a procedure call : unformatted output to the server  
 It returns a stream. The application will write baisc objects (int, str, OMobject) in the stream.
 .e.g: stream<<OMS("scscp2","store_session")<<30 ;
 @param returntype (in) return type of the call
 @param pstream (out) pointer to a stream
*/
/*-----------------------------------------------------------------*/
int  ClientComputation::send(SCSCP_option_return returntype, Ounfstream*& pstream)  throw (Exception) 
{
 int res = SCSCP_co_set_returntype(get_calloptions(),returntype, get_status());
 if (res) res = SCSCP_procedurecall_write_start(get_client() , *get_calloptions(),get_status());
 if (res)
 {
   pstream = createounfstream();
 }
 else 
 {
  pstream = NULL;
  report_error(res);
 }
 return res;
}

/*-----------------------------------------------------------------*/
/*! discard the procedure call to the server (after send...formattedstream)
*/
/*-----------------------------------------------------------------*/
int ClientComputation::discard()  throw (Exception) 
{
 int res;
 flushostream();
 res = SCSCP_procedurecall_write_cancel(get_client(), get_status());
 report_error(res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! complete the procedure call to the server (after send...formattedstream)
*/
/*-----------------------------------------------------------------*/
int ClientComputation::finish()  throw (Exception) 
{
 int res;
 flushostream();
 res = SCSCP_procedurecall_write_finish(get_client() , get_status());
 report_error(res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! wait and receive an answer of a  procedure call from the server 
 return a stream for the output
 @param msgtype (out) type of the answer
*/
/*-----------------------------------------------------------------*/
int ClientComputation::recv(SCSCP_msgtype& msgtype, Iunfstream*& pstream)   throw (Exception) 
{
 char *cdnameerr;
 char *symbolnameerr;
 char *msg;
 SCSCP_xmlnodeptr node;
 int res;
 pstream=NULL;
 
 res = SCSCP_sc_callrecvheader(get_client(), get_returnoptions(), &msgtype, get_status());
 if (res)
 {
  switch(msgtype)
  {
   case SCSCP_msgtype_ProcedureCompleted : 
           node = SCSCP_sc_getxmlnode(get_client(), get_status());  
           if (node)
           {
            pstream = createiunfstream(node);
           }
           break;
           
   case SCSCP_msgtype_ProcedureTerminated : 
           res = SCSCP_sc_callrecvterminated(get_client(), &cdnameerr, &symbolnameerr, &msg, get_status());
           if (res) SCSCP_status_setexecfailed(get_status(), cdnameerr, symbolnameerr, msg);
           res = 0;
           break;
           
   default :  break;
  }
 }
 report_error(res);

 return res;
}

/*-----------------------------------------------------------------*/
/*! send an interruption of the call
*/
/*-----------------------------------------------------------------*/
int ClientComputation::interrupt() 
{
 int res =0 ;
 const char *callid;
 get_callid(callid);
 if (callid!=NULL)
 {
  res = SCSCP_sc_callsendinterrupt(get_client(), callid, get_status());
 }
 return res;
}

/*-----------------------------------------------------------------*/
/*! Default constructor
*/
/*-----------------------------------------------------------------*/
ServerComputation::ServerComputation(IncomingClient& session) throw (Exception) 
     : ProcedureCall(session)
{
}

/*-----------------------------------------------------------------*/
/*! destructor
*/
/*-----------------------------------------------------------------*/
ServerComputation::~ServerComputation()
{
}
   
/*-----------------------------------------------------------------*/
/*! wait and receive an command from the client 
 @param openmathbuffer (out) openmath result
 @param lenbuffer (out) number of valid bytes in openmathbuffer
 @param msgtype (out) type of the answer
*/
/*-----------------------------------------------------------------*/
int ServerComputation::recv(SCSCP_msgtype& msgtype, char*& openmathbuffer, size_t& lenbuffer)  throw (Exception) 
{
 int res;
 res = SCSCP_ss_callrecvstr(get_client(), get_calloptions(), &msgtype, &openmathbuffer, get_status());
 if (res)
 {
  lenbuffer = ::strlen(openmathbuffer);
 }
 else 
 {
  report_error(res);
 }

 return res;
}

/*-----------------------------------------------------------------*/
/*! send a completed message using an openmath buffer to the client    
 @param openmathbuffer (in) openmath buffer
 @param lenbuffer (in) number of valid bytes in openmathbuffer
*/
/*-----------------------------------------------------------------*/
int ServerComputation::sendcompleted(const char* openmathbuffer, size_t lenbuffer)    throw (Exception) 
{
 int res;
 res = SCSCP_ss_sendcompletedstr(get_client(), get_returnoptions(), openmathbuffer, get_status());
 report_error(res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! send a terminated message using an openmath buffer to the client    
 return 0 on error
 @param openmathbuffer (in) openmath buffer
 @param lenbuffer (in) number of valid bytes in openmathbuffer
*/
/*-----------------------------------------------------------------*/
int ServerComputation::sendterminated(const char * cdname, const char * symbolname, const char * message)    throw (Exception) 
{
 int res;
 res = SCSCP_ss_sendterminatedstr(get_client(), get_returnoptions(), cdname, symbolname, message, get_status());
 report_error(res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! wait and receive a command from the client
 return a stream for the output
 return 0 on error
 @param msgtype (out) type of the answer
 @param stream (out) pointer to a stream
        mustn't be deleted 
*/
/*-----------------------------------------------------------------*/
int ServerComputation::recv(SCSCP_msgtype& msgtype, Iunfstream*& stream)   throw (Exception) 
{
 SCSCP_xmlnodeptr node;
 int res;
 
 SCSCP_debugprint("ServerComputation::recv()  - enter\n");
 res = SCSCP_ss_callrecvheader(get_client(), get_calloptions(), &msgtype, get_status());
 if (res)
 {
  switch(msgtype)
  {
   case SCSCP_msgtype_ProcedureCall : 
           node = SCSCP_ss_getxmlnode(get_client(), get_status());  
           if (node)
           {
            stream = createiunfstream(node);
           }
           else stream = NULL;
           break;
           
   case SCSCP_msgtype_Interrupt : 
           stream = NULL;
           break;
           
   default : res=0; break;
  }
  if (res)
  {
   const char *callid;
   get_callid(callid);
   res = SCSCP_ro_set_callid(get_returnoptions(), callid, get_status());
  }
 }
 report_error(res);

 SCSCP_debugprint("ServerComputation::recv()  - returns %d\n",res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! send a completed message using a stream to the client (direct output OpenMath)
 .e.g: stream<<"<OMI>1</OMI>";
 return 0 on error
 @param returntype (in) return type of the call
*/
/*-----------------------------------------------------------------*/
int ServerComputation::sendcompleted(std::ostream*& stream)  throw (Exception) 
{
 int res;
 stream = NULL;
 res = SCSCP_procedurecompleted_write_start(get_client() , *get_returnoptions(), get_status());
 if (res)
 {
   stream = createofmtstream();
 }
 else
 {
  report_error(res);
 }
 return res;
}

/*-----------------------------------------------------------------*/
/*! send a completed message using a stream to the client (format to OpenMath) 
 It returns a stream. The application will write baisc objects (int, str, OMobject) in the stream.
 .e.g: stream<<OMS("scscp2","store_session")<<30 ;
 return 0 on error
 @param returntype (in) return type of the call
*/
/*-----------------------------------------------------------------*/
int ServerComputation::sendcompleted(Ounfstream*& stream)  throw (Exception) 
{
 int res;
 stream = NULL;
 res = SCSCP_procedurecompleted_write_start(get_client() , *get_returnoptions(),get_status());
 if (res)
 {
   stream = createounfstream();
 }
 else
 {
  report_error(res);
 }
 return res;
}

/*-----------------------------------------------------------------*/
/*! complete the completed or terminated message to the client (after sendcompleted)
*/
/*-----------------------------------------------------------------*/
int ServerComputation::finish()  throw (Exception) 
{
 int res;
 flushostream();
 res = SCSCP_procedurecompleted_write_finish(get_client() , get_status());
 report_error(res);
 return res;
}


} /*namespace SCSCP*/
