/*-----------------------------------------------------------------*/
/*! 
  \file scscpfileclient.c
  \brief SCSCP client over file or socket
  \author  M. Gastineau 
           Astronomie et Systemes Dynamiques, IMCCE, CNRS, Observatoire de Paris. 

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

   \bug M. GASTINEAU 20/04/09 : update for SCSCP 1.3
   \bug M. GASTINEAU 06/12/11 : fix bug in SCSCP_sc_readline
*/
/*-----------------------------------------------------------------*/

/*-----------------------------------------------------------------*/
/* 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 <sys/types.h>
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#endif
#include <errno.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdarg.h>
#include <ctype.h>
#define __SCSCP_WITHIN_SCSCP
#include "scscp.h"
#include "scscpinternal.h"
#include "scscpfileclient.h"
#include "scscpfileserver.h"
#include "scscpprocedurecall.h"
#include "scscpdebug.h"
#include "scscpomdoc.h"
#include "scscpbinary.h"
#include "scscpxmlparser.h"

/*-----------------------------------------------------------------*/
/* private defines */
/*-----------------------------------------------------------------*/
/*! convert xmlchar to char */
#define xmlChar2char(x) ((char*)(x))

/*-----------------------------------------------------------------*/
/* private types */
/*-----------------------------------------------------------------*/
/*! parameter for hook excute functions */
typedef struct {
 const char *cdname;
 const char *symbolname;
 int (*callbackwriteargs)(SCSCP_io* stream, void *param, SCSCP_status* status);
 void *param;
} t_paramexecutehook;

/*! stream context */
typedef struct  {
 SCSCP_fileio*  file;      /*!< socket stream */
 char *buffer;             /*!< last read buffer */
 int lastbufvalid;         /*!< lastbuf est valide */
 SCSCP_status status;      /*!< status of the connection */
 int   mathfind;           /*!< processing instruction found */
 char *pmathfind;          /*! location of the processing instruction */
 int   firstcall;          /*!< =1 if first call */
 SCSCP_encodingtype  m_recvencodingtype; /*!< type of encoding */
} t_controlpipe;

/*-----------------------------------------------------------------*/
/* private functions */
/*-----------------------------------------------------------------*/
static int SCSCP_sc_callsendstr_callback(SCSCP_io* client, void *param, SCSCP_status* status);
static int SCSCP_sc_clearallowedversions(SCSCP_Fileclient* client, SCSCP_status* localstatus);
static int SCSCP_readstream_post(t_controlpipe* ctx, int retval);
static int SCSCP_readstream_pre(t_controlpipe* ctx, char* buff, int len);
static int SCSCP_readstream_XML(t_controlpipe* ctx, char* buff, int len);
static int SCSCP_readstream_Binary(t_controlpipe* ctx, char* buff, int len);

/*-----------------------------------------------------------------*/
/*! connect to a server at the specified port
    return 1 on success
    on error, geterror() will give the error message
 @param client (inout) SCSCP client
    @param servername (in) name of the server
    @param port (in) port on the server of the SCSCP server
    @param status (out) status
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_connectsocket(SCSCP_Fileclient* client, const char *servername, int port, SCSCP_status* status)
{
  int bres  = 1;
  static int binitsocket=1;
  const char *supportedversion;
  struct hostent * hp;
  int fd;
  SCSCP_debugprint("SCSCP_sc_connectsocket( %s, %d) - entree\n",servername,port);
  
  client->m_recvquit = 0;
  client->m_bufferInputValid = 0;
    
  if (binitsocket && !SCSCP_initsocket())
  {
   SCSCP_debugprint("SCSCP_sc_connectsocket() - SCSCP_initsocket failed\n");
   SCSCP_status_seterror(status, SCSCP_STATUS_ERRNO);
   bres = 0;
  }
  
  hp = gethostbyname(servername);
  if ( hp == NULL ) 
  {
   SCSCP_debugprint("SCSCP_sc_connectsocket() - gethostbyname failed\n");
   SCSCP_status_seterror(status, SCSCP_STATUS_ERRNO);
   bres = 0;
  }
  /* creer une socket pour se connecter au serveur */
  else if ((fd = socket(AF_INET, SOCK_STREAM, 0))==INVALID_SOCKET)
  {
   SCSCP_debugprint("SCSCP_sc_connectsocket() - socket failed\n");
   SCSCP_status_seterror(status, SCSCP_STATUS_ERRNO);
   bres = 0;
  }
  else
  {
   struct sockaddr_in adresseServeur;
   adresseServeur.sin_family = AF_INET;
   adresseServeur.sin_port = htons(port);/* host to network short */
   memcpy(&adresseServeur.sin_addr.s_addr, hp->h_addr, hp->h_length);

   SCSCP_debugprint("SCSCP_sc_connectsocket() - socket sucess fd=%d\n", fd);
   SCSCP_debugprint("SCSCP_sc_connectsocket() - server address : %s\n", inet_ntoa(adresseServeur.sin_addr));
   /* se connecter au serveur */
   if (connect(fd, (struct sockaddr *)&adresseServeur, sizeof(adresseServeur))== SOCKET_ERROR ) 
   {
    SCSCP_debugprint("SCSCP_sc_connectsocket() - connect failed\n");
    SCSCP_status_seterror(status, SCSCP_STATUS_ERRNO);
    bres = 0;
    closesocket(fd);  
    fd = -1; 
   }
   else
   {
    SCSCP_sc_setsocket(client, fd);
    bres = SCSCP_sc_readCIM(client, &supportedversion, status);
    if (bres) bres = SCSCP_sc_writeCIMVersion(client, supportedversion);
    if (bres) bres = SCSCP_sc_readCIMVersion(client, status);
    if (!bres) SCSCP_sc_closesocket(client);
   }
  }
  binitsocket = 0;
  SCSCP_debugprint("SCSCP_sc_connectsocket(this=%p, fd = %d) - returns %d\n", client, fd, bres);
  return bres;
}


/*-----------------------------------------------------------------*/
/*! close the connection to the server 
  @param client (inout) SCSCP client
*/
/*-----------------------------------------------------------------*/
void SCSCP_sc_closesocket(SCSCP_Fileclient* client)
{
 SCSCP_debugprint("SCSCP_sc_closesocket() - enter\n");
 if (SCSCP_sc_getsocket(client)!=-1) 
 { 
     if (client->m_recvquit==0)
     {
         const char *msg="<?scscp quit ?>\n";
         SCSCP_fileio_write(&client->m_OutStream, msg, strlen(msg)*sizeof(char));
         SCSCP_fileio_flush(&client->m_OutStream); 
     }
  closesocket(SCSCP_sc_getsocket(client));  
  SCSCP_sc_setsocket(client, -1); 
 }
 SCSCP_debugprint("SCSCP_sc_closesocket() - exit\n");
}

/*-----------------------------------------------------------------*/
/*! read a line from the input stream
  and return 1 on success
 @param client (inout) SCSCP client
 @param buffer (out) buffer to fill
 @param lenbuffer (in) size of buffer
 @param status (inout) error code
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_readline(SCSCP_Fileclient* client, char *buffer, int lenbuffer, SCSCP_status *status)
{
    int bres = 1;
    int j=-1;
    int found = 0;
    SCSCP_debugprint("SCSCP_sc_readline() - enter\n");
    
    SCSCP_debugprint("valid buffer: %d '%s'\n", client->m_bufferInputValid, client->m_bufferInput);
    /*read from buffers */
    if (client->m_bufferInputValid>0)
    {
     do 
     {
      j++;
      buffer[j] = client->m_bufferInput[j];
     } while (j<client->m_bufferInputValid && buffer[j]!='\n' && j<lenbuffer);
     if (j<lenbuffer) 
     {
      if (j<client->m_bufferInputValid)
      {
       client->m_bufferInputValid -=(j+1);
       memmove(client->m_bufferInput, client->m_bufferInput+j+1, client->m_bufferInputValid+1 /* +1 to keep '\0'*/);
      }
      else
      {
       client->m_bufferInputValid=0;
       client->m_bufferInput[0]='\0'; 
      }
      found = 1;
     }
    }
    SCSCP_debugprint("after buffer: %d '%s'\n", client->m_bufferInputValid, client->m_bufferInput);
    
    /* read from socket */
    if (j<lenbuffer && !found)
    {
     do
     {
      j++;
      bres=(SCSCP_fileio_read(&client->m_InStream, buffer+j, sizeof(char))!=SOCKET_ERROR);
     } while (bres && buffer[j]!='\n' && j<lenbuffer);
    }
    if (j==lenbuffer) bres = 0;
    if (!bres)
    {
     SCSCP_status_seterror(status, SCSCP_STATUS_RECVQUIT);
    }
    else
    {
     buffer[j] = '\0';
     SCSCP_debugprint("SCSCP_sc_readline - read '%s' j=%d\n", buffer, j);
    }
    
    SCSCP_debugprint("SCSCP_sc_readline - returns %d\n", bres);
    return bres;
}

/*-----------------------------------------------------------------*/
/*! read the Connection information Message from the server
  and return 1 on success
  @param client (inout) SCSCP client
  @param supportedversion (out) supported version
  @param status (inout) status 
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_readCIM(SCSCP_Fileclient* client, const char **supportedversion,SCSCP_status* status)
{
    int bres;
    char c[SCSCP_PI_MAXLENBUFFER];
    char *scscp_versions;
    char *lasts;
    
    SCSCP_debugprint("SCSCP_Fileclient::ReadCIM() - entree\n");
 
     bres = SCSCP_sc_readline(client,c, SCSCP_PI_MAXLENBUFFER, status);
     *supportedversion = NULL;
     if (bres)
     {
      scscp_versions = SCSCP_findPIstring(c,"scscp_versions");
      if (scscp_versions)
      {
         char *vers;
         for (vers=strtok_r(scscp_versions, " ", &lasts); vers && *supportedversion==NULL; vers= strtok_r(NULL, " ", &lasts))
         {
          size_t j;
          for ( j=0; j<client->m_allowedversionscount && *supportedversion==NULL; j++)
          {
            if (strcmp(vers,client->m_allowedversions[j])==0) *supportedversion = client->m_allowedversions[j];
          }
         }
         /* aucune version supporter => on donne la + grande */
         if (*supportedversion==NULL) *supportedversion=client->m_allowedversions[0];
      }
      else
      {
       bres = 0;
       SCSCP_status_seterror(status, SCSCP_STATUS_VERSIONNEGOTIATIONFAILED);
      }
    }
    
    SCSCP_debugprint("SCSCP_Fileclient::ReadCIM - exit and return %d\n", bres);
    return bres;
}


/*-----------------------------------------------------------------*/
/*! read the Connection information Message Version from the server
  and return 1 on success
  @param client (inout) SCSCP client
  @param status (inout) status 
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_readCIMVersion(SCSCP_Fileclient* client, SCSCP_status* status)
{
    int bres;
    char c[SCSCP_PI_MAXLENBUFFER];
    char *scscp_versions;
    
    SCSCP_debugprint("SCSCP_sc_readCIMVersion() - enter\n");
 
     bres = SCSCP_sc_readline(client,c,  SCSCP_PI_MAXLENBUFFER, status);
     if (bres)
     {
      scscp_versions = SCSCP_findPIstring(c,"version");
      if (scscp_versions==NULL) 
      {
       bres = 0;
       SCSCP_status_seterror(status, SCSCP_STATUS_VERSIONNEGOTIATIONFAILED);
      }
    }
    
    SCSCP_debugprint("SCSCP_sc_readCIMVersion - exit and return %d\n", bres);
    return bres;
}

/*-----------------------------------------------------------------*/
/*! write the SCSCP version of the client to the server
  and return 1 on success
 @param client (inout) SCSCP client
 @param supportedversion (in) supported version
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_writeCIMVersion(SCSCP_Fileclient* client, const char *supportedversion)
{
    int bres;
     const char *msg1="<?scscp version=\"";
     const char *msg2="\" ?>\n";
     
    SCSCP_debugprint("SCSCP_sc_writeCIMVersion() - enter\n");
 
    bres =  SCSCP_fileio_write(&client->m_OutStream,msg1, strlen(msg1)*sizeof(char));
    if (bres) bres = SCSCP_fileio_write(&client->m_OutStream,supportedversion, strlen(supportedversion)*sizeof(char));
    if (bres) bres = SCSCP_fileio_write(&client->m_OutStream,msg2, strlen(msg2)*sizeof(char));
    if (bres) bres = SCSCP_fileio_flush(&client->m_OutStream);
     
    SCSCP_debugprint("SCSCP_sc_writeCIMVersion - exit and return %d\n", bres);
    return bres;
}

/*-----------------------------------------------------------------*/
/*! attach the specified socket to the client
    @param client (inout) SCSCP client
    @param socketid (in) socket to be attached
*/
/*-----------------------------------------------------------------*/
void SCSCP_sc_setsocket(SCSCP_Fileclient* client,int socketid)
{
 SCSCP_fileio_setfile(&client->m_OutStream,socketid, 1);
 SCSCP_fileio_setfile(&client->m_InStream, socketid, 1);
}

/*-----------------------------------------------------------------*/
/*! return the socket associated to the client
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_getsocket(SCSCP_Fileclient* client)
{
 return SCSCP_fileio_getfile(&client->m_OutStream);
}

/*-----------------------------------------------------------------*/
/*! checks if SCSCP client is null and returns 0 on error
  @param client (in) object to be checked
  @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_checkNULL(SCSCP_socketclient* client, SCSCP_status* status)
{
   int res = 1;
   
   if (client==NULL || *client==NULL)
   {
     SCSCP_status_seterror(status,  SCSCP_STATUS_CLIENTOBJECTNULL);
     res = 0;
   }
   return res;
}

/*-----------------------------------------------------------------*/
/*! initialize the object client and set status if an error occurs 
 @param client (inout) object to be initialized
 @param status (inout) status error
 xparam ...    (in) allowed version to be negotiated
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_init(SCSCP_socketclient* client, SCSCP_status* status, ...)
{
   va_list versions;
   int res = 1;

   SCSCP_debugprint("SCSCP_sc_init(%p,%d) - enter\n", client,SCSCP_status_is(status));
   
   if (client==NULL)
   {
     SCSCP_status_seterror(status,  SCSCP_STATUS_CLIENTOBJECTNULL);
     res = 0;
   }
   else
   {
    SCSCP_Fileclient* pclient;
    *client = pclient = ( SCSCP_Fileclient*)SCSCP_malloc(sizeof(SCSCP_Fileclient), status);
    if (pclient==NULL) 
    {
     res = 0;
    }
    else
    {
     /* count elements */
     size_t  counter = 0;
     va_list listcount;
     
     
     va_start(listcount, status);
     while (va_arg(listcount, const char*)!=NULL) counter++;
     va_end(listcount);

     pclient->m_sendencodingtype = SCSCP_encodingtype_XML;
     pclient->m_recvencodingtype = SCSCP_encodingtype_XML;
     pclient->m_doc =NULL;
     SCSCP_sc_setsocket(pclient, -1);
     pclient->m_bufferInput = (char*)SCSCP_malloc(sizeof(char)*(SCSCP_PI_MAXLENBUFFER+1), status );
     pclient->m_bufferInputValid = 0;
     pclient->m_recvquit = 0;
     
     /* duplicate the elements of versions */
     pclient->m_allowedversions = (SCSCP_string*)SCSCP_malloc(sizeof(SCSCP_string)*counter, status);
     res = (pclient->m_allowedversions!=NULL &&  pclient->m_bufferInput!=NULL);
     if (res)
     {
      size_t cpt;
      va_start(versions, status);
      for( cpt=0; cpt<counter; cpt++)
      {
       pclient->m_allowedversions[cpt] = SCSCP_strdup(va_arg(versions, const char*), status);
       res = (pclient->m_allowedversions[cpt]!=NULL);
      }
      pclient->m_allowedversionscount = counter;
      va_end(versions);
     }
     
    }
   }
   SCSCP_debugprint("SCSCP_sc_init(%p, %d) - return %d\n", *client,  SCSCP_status_is(status), res);
   return res;
}



/*-----------------------------------------------------------------*/
/*! clear the allowed version to be negiotiated to the client 
 @param client (inout) object to be cleared
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
static int SCSCP_sc_clearallowedversions(SCSCP_Fileclient* client, SCSCP_status* status)
{
   int res = 1;
   
   SCSCP_debugprint("SCSCP_sc_clearallowedversions() - enter\n");
   if (client==NULL)
   {
     SCSCP_status_seterror(status,  SCSCP_STATUS_CLIENTOBJECTNULL);
     res = 0;
   }
   else
   {
    /* clear the elements of versions */
    if (client->m_allowedversions!=NULL)
    {
     size_t cpt;
     for(cpt=0; cpt<client->m_allowedversionscount; cpt++)
     {
      free(client->m_allowedversions[cpt]);
     }
     free(client->m_allowedversions);
     client->m_allowedversions = NULL;
     client->m_allowedversionscount = 0;
    }
   }
   SCSCP_debugprint("SCSCP_sc_clearallowedversions() - returns %d\n", res);
   return res;
}

/*-----------------------------------------------------------------*/
/*! clear the object client previously initialized by SCSCP_sc_init  
 @param client (inout) object to be deleted
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_clear(SCSCP_socketclient* client, SCSCP_status* status)
{
   int res;
   SCSCP_debugprint("SCSCP_sc_clear(%p,%p) - enter\n", client, status);
   
   res = SCSCP_sc_checkNULL(client, status);
   if (!res) return res;
   res = SCSCP_sc_clearallowedversions(*client, status);
   SCSCP_sc_closesocket(*client);
   SCSCP_sc_freexmldoc(*client);
   free((*client)->m_bufferInput);
   free (*client);
   
   SCSCP_debugprint("SCSCP_sc_clear(%d) - return %d\n", SCSCP_status_is(status), res);
   return res;
}

/*-----------------------------------------------------------------*/
/*! connect the client to a server on the specified host and port  
 @param client (inout) scscp client
 @param machine (in) computer name or IP address
 @param port (in) port on the remote computer
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_connect(SCSCP_socketclient* client, const char *machine, int port, SCSCP_status* status)
{
   int res;
   
   SCSCP_debugprint("SCSCP_sc_connect(%p, %s, %d, %p) - enter\n", client, machine, port, status);
   
   res = SCSCP_sc_checkNULL(client, status);
   if (!res) return res;
   
   res = SCSCP_sc_connectsocket(*client, machine, port,status);
   if (!res && SCSCP_status_is(status)==SCSCP_STATUS_OK) SCSCP_status_seterror(status,SCSCP_STATUS_ERRNO);

   SCSCP_debugprint("SCSCP_sc_connect(%d) - return %d\n", SCSCP_status_is(status), res);
   return res;
}
 
/*-----------------------------------------------------------------*/
/*! close the connection of the client to a server 
   previously opened by SCSCP_sc_connect  
 @param client (inout) scscp client
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_close(SCSCP_socketclient* client, SCSCP_status* status)
{
   int res = SCSCP_sc_checkNULL(client, status);
   if (res)  SCSCP_sc_closesocket(*client);
   return res;
}

/*-----------------------------------------------------------------*/
/*! set that we receive the quit message on the socket of the client 
 @param client (inout) SCSCP client
 */
/*-----------------------------------------------------------------*/
void SCSCP_sc_setrecvquit(SCSCP_Fileclient* client)
{
    client->m_recvquit = 1;
}

/*-----------------------------------------------------------------*/
/*! set the current encoding for the OpenMath objects 
 @param client (inout) scscp client
 @param encodingtype (in) new encoding
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_set_encodingtype(SCSCP_socketclient* client, SCSCP_encodingtype encodingtype, SCSCP_status* status)
{
   int res = SCSCP_sc_checkNULL(client, status);
   if (res)  (*client)->m_sendencodingtype = encodingtype;
   return res;
}

/*-----------------------------------------------------------------*/
/*! get the current encoding for the OpenMath objects 
 @param client (inout) scscp client
 @param encodingtype (out) new encoding
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_get_encodingtype(SCSCP_socketclient* client, SCSCP_encodingtype* encodingtype, SCSCP_status* status)
{
   int res = SCSCP_sc_checkNULL(client, status);
   if (res)  *encodingtype = (*client)->m_sendencodingtype;
   return res;
}

/*-----------------------------------------------------------------*/
/*! write the openmath buffer to the scscp server
 @param stream (inout) scscp stream
 @param param (in) openmath expression (must be a const char *)
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
static int SCSCP_sc_callsendstr_callback(SCSCP_io* stream, void *param, SCSCP_status* status)
{
  const char *openmathbuffer = (const char*)param;
  return SCSCP_io_write(stream, openmathbuffer, status);
}




/*-----------------------------------------------------------------*/
/*! send an openmath string to the SCSCP server with some options 
    for a procedure call
 @param client (inout) scscp client
 @param options (in) options of the call
 @param openmathbuffer (in) openmath expression
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_callsendstr(SCSCP_socketclient* client, SCSCP_calloptions* options, 
                          const char *openmathbuffer, SCSCP_status* status)
{
   return SCSCP_sc_callsendhook(client, options, SCSCP_sc_callsendstr_callback, (void*)openmathbuffer, status);
}

/*-----------------------------------------------------------------*/
/*! send a procedure call to the SCSCP server with some options 
    The parameter of the procedure call are written 
    by the callback function "callbackwriteargs"
 @param client (inout) scscp client
 @param options (in) options of the call
 @param callbackwriteargs (in) callback function 
             to write the parameters of the procedure call
 @param param (inout) opaque data given to callbackwriteargs
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_callsendhook(SCSCP_socketclient* client, SCSCP_calloptions* options, 
                          int (*callbackwriteargs)(SCSCP_io* stream, void *param, SCSCP_status* status),
                          void *param, SCSCP_status* status)
{
   int res = SCSCP_sc_checkNULL(client, status);
   if (res) res = SCSCP_procedurecall_write(client, options==NULL?NULL:*options, callbackwriteargs, param, status);
   return res;
}

/*-----------------------------------------------------------------*/
/*! the client sends the "information" message to the server. 
 @param client (inout) scscp client
 @param messagebuffer (in) information 
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_infomessagesend (SCSCP_socketclient* client, const char * messagebuffer, SCSCP_status* status)
{
   int res = SCSCP_sc_checkNULL(client, status);
   if (res) 
   {
     res = SCSCP_io_writef(client, status, "<?scscp info=\"", messagebuffer,"\" ?>\n", NULL);
     if (res) res = SCSCP_io_flush(client, status);
   }
   return res;
}

/*-----------------------------------------------------------------*/
/*! the client sends the "interrupt" message to the server with the call id. 
 @param client (inout) scscp client
 @param call_id (in) call ID of the canceled procedure  
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_callsendinterrupt(SCSCP_socketclient* client, const char* call_id, SCSCP_status* status)
{
   int res = SCSCP_sc_checkNULL(client, status);
   if (res) 
   {
     res = SCSCP_io_writef(client, status, "<?scscp terminate call_id=\"", call_id,"\" ?>\n", NULL);
     if (res) res = SCSCP_io_flush(client, status);
   }
   return res;
}

/*-----------------------------------------------------------------*/
/*! write the expression : 
   @param stream (inout) stream session
   @param status (inout) error type
   @param param (in) name of the remote object
*/
/*-----------------------------------------------------------------*/
static int SCSCP_sc_executehookwrite(SCSCP_io* stream, void *param, SCSCP_status *status)
{
 int res;
 t_paramexecutehook *hookparam = (t_paramexecutehook*)param;
 res = SCSCP_io_writebeginOMA(stream, NULL, status);
 if (res) res = SCSCP_io_writeOMS(stream, hookparam->cdname, hookparam->symbolname, NULL, status);
 if (res && hookparam->callbackwriteargs!=NULL)  res = hookparam->callbackwriteargs(stream, hookparam->param, status);
 if (res) res = SCSCP_io_writeendOMA(stream, status);
 return res;
}


/*-----------------------------------------------------------------*/
/*! send a procedure call to the server with symbolname and cdname and wait for the answer. 
 @param client (inout) scscp client
 @param returntype (in) return type
 @param cdname (inout) name of the CD
 @param symbolname (inout) name of the symbol
 @param callbackwriteargs (in) function to write arguments
 @param paramwrite (inout) opaque object given to callbackwriteargs
 @param node (out) xml node of the result
 @param status (inout) error code
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_executehookxmlnode(SCSCP_socketclient* client, SCSCP_option_return returntype, const char *cdname, const char* symbolname,
                              int (*callbackwriteargs)(SCSCP_io* stream, void *param, SCSCP_status* status), void *param, 
                              SCSCP_xmlnodeptr* node,
                              SCSCP_status* status)
{
 int res, res1;
 SCSCP_calloptions options;
 SCSCP_msgtype msgtype;
 char *cdnameerr;
 char *symbolnameerr;
 char *msg;
 
 t_paramexecutehook hookparam={cdname, symbolname, callbackwriteargs, param};
 
 SCSCP_debugprint("SCSCP_sc_executehookxmlnode(%p, %d, %s, %s, %p, %p, %p, %p)  - enter\n", client, (int)returntype, cdname, symbolname, callbackwriteargs , param, node, status);
 
 res1 = res = SCSCP_co_init(&options, status);
 if (res) res = SCSCP_co_set_returntype(&options, returntype,status);
 
 if (res) res = SCSCP_sc_callsendhook(client,&options, SCSCP_sc_executehookwrite, &hookparam, status);
 if (res) res = SCSCP_sc_callrecvheader(client, SCSCP_RETURNOPTIONS_IGNORE, &msgtype, status);
 if (res)
 {
  switch(msgtype)
  {
   case SCSCP_msgtype_ProcedureCompleted : 
           *node = SCSCP_sc_getxmlnode(client, status); 
           break;
           
   case SCSCP_msgtype_ProcedureTerminated : 
           *node = NULL;
           if (res) res = SCSCP_sc_callrecvterminated(client, &cdnameerr, &symbolnameerr, &msg, status);
           SCSCP_status_setexecfailed(status, cdnameerr, symbolnameerr, msg);
           res = 0;
           break;
           
   default : *node = NULL; break;
  }
 }

 if (res1) SCSCP_co_clear(&options, status);
 
 SCSCP_debugprint("SCSCP_sc_executehookxmlnode( %p, %d)  - returns %d\n", *node, SCSCP_status_is(status), res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! send a procedure call to the server with symbolname and cdname and wait for the answer. 
 @param client (inout) scscp client
 @param returntype (in) return type
 @param cdname (inout) name of the CD
 @param symbolname (inout) name of the symbol
 @param callbackwriteargs (in) function to write arguments
 @param paramwrite (inout) opaque object given to callbackwriteargs
 @param openmathbuffer (out) openmath result
 @param status (inout) error code
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_executehookstr(SCSCP_socketclient* client, SCSCP_option_return returntype, const char *cdname, const char* symbolname,
                              int (*callbackwriteargs)(SCSCP_io* stream, void *param, SCSCP_status* status), void *param, 
                              char ** openmathbuffer,
                              SCSCP_status* status)
{
 int res, res1;
 SCSCP_calloptions options;
 SCSCP_msgtype msgtype;
 char *cdnameerr;
 char *symbolnameerr;
 char *msg;
 
 t_paramexecutehook hookparam={cdname, symbolname, callbackwriteargs, param};
 
 SCSCP_debugprint("SCSCP_sc_executehookstr(%p, %d, %s, %s, %p, %p, %p, %p)  - enter\n", client, (int)returntype, cdname, symbolname, callbackwriteargs , param, openmathbuffer, status);
 
 *openmathbuffer = NULL;
 res1 = res = SCSCP_co_init(&options, status);
 if (res) res = SCSCP_co_set_returntype(&options, returntype,status);
 
 if (res) res = SCSCP_sc_callsendhook(client,&options, SCSCP_sc_executehookwrite, &hookparam, status);
 if (res) res = SCSCP_sc_callrecvheader(client, SCSCP_RETURNOPTIONS_IGNORE, &msgtype, status);
 if (res)
 {
  switch(msgtype)
  {
   case SCSCP_msgtype_ProcedureCompleted : 
           res = SCSCP_sc_callrecvcompleted(client,openmathbuffer, status); 
           break;
           
   case SCSCP_msgtype_ProcedureTerminated : 
           if (res) res = SCSCP_sc_callrecvterminated(client, &cdnameerr, &symbolnameerr, &msg, status);
           SCSCP_status_setexecfailed(status, cdnameerr, symbolnameerr, msg);
           res = 0;
           break;
           
   default : *openmathbuffer = NULL; break;
  }
 }

 if (res1) SCSCP_co_clear(&options, status);
 
 SCSCP_debugprint("SCSCP_sc_executehook( %p, %d)  - returns %d\n", *openmathbuffer, SCSCP_status_is(status), res);
 return res;
}

/*-----------------------------------------------------------------*/
/*! return the first node in the current location of the openmath message
 @param client (inout) scscp client
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
SCSCP_xmlnodeptr SCSCP_sc_getxmlnode(SCSCP_socketclient *client, SCSCP_status* status)
{
 int res = SCSCP_sc_checkNULL(client, status);
 return res?(*client)->m_iter:NULL;
}

/*-----------------------------------------------------------------*/
/*! \internal return the address of the first node in the current location of the openmath message
 @param client (inout) scscp client
 @param status (inout) status error
*/
/*-----------------------------------------------------------------*/
SCSCP_xmlnodeptr* SCSCP_sc_getxmlnodeptr(SCSCP_socketclient *client, SCSCP_status* status)
{
 int res = SCSCP_sc_checkNULL(client, status);
 return res?&((*client)->m_iter):NULL;
}


/*----------------------------------------------------------------------------------------------*/
/*!  return node and its children as a raw string 
  return NULL  on error
  @param client (in) client. mustn't be NULL 
  @param node (in) current valid node. mustn't be NULL 
  @param status (in) error code
*/
/*----------------------------------------------------------------------------------------------*/
char* SCSCP_sc_getxmlnoderawstring(SCSCP_socketclient* client, SCSCP_xmlnodeptr node, SCSCP_status *status)
{
 char* str = NULL;
 int res = SCSCP_sc_checkNULL(client, status);
#if SCSCP_PARSERTYPE==SCSCP_PARSERDOM
 xmlBufferPtr buffer;
#endif

 SCSCP_debugprint("SCSCP_sc_getxmlnoderawstring(%p,%p,%p) - enter\n",client, node, status);
 if (res==0) return str;
 
#if SCSCP_PARSERTYPE==SCSCP_PARSERDOM
 buffer = xmlBufferCreate();
 if (xmlNodeDump(buffer, (*client)->m_doc, node, 1,1)!=-1)
 {
    const xmlChar *strxml = xmlBufferContent(buffer);
    if (strxml!=NULL)
    {
     int len = xmlStrlen(strxml)+1;
     str = (char*)SCSCP_malloc(sizeof(char)*len, status);
     if (str!=NULL)
     {
      strcpy(str, xmlChar2char(strxml));
     }
    }
    else
    {
     SCSCP_status_seterror(status, SCSCP_STATUS_NOMEM);
    }
 }
 else
 {
  SCSCP_status_seterror(status, SCSCP_STATUS_NOMEM);
 }
 
#elif SCSCP_PARSERTYPE==SCSCP_PARSERSAX
 str = SCSCP_omnode_getrawstring(node, status);
#endif
 SCSCP_debugprint(" SCSCP_sc_getxmlnoderawstring() - return '%s'\n", str);
 return str;
}

/*-----------------------------------------------------------------*/
/*! free the xml document
*/
/*-----------------------------------------------------------------*/
void SCSCP_sc_freexmldoc(SCSCP_Fileclient* client)
{
 if (client->m_doc) SCSCP_xmldoc_free(client->m_doc);
 client->m_doc = NULL;
}

/*-----------------------------------------------------------------------*/
/*!  read data from socket and put contents to buff of size len 
 return -1 on error or if <?scscp end ?> was foudn before
 @param ctx (inout) stream context
 @param buff (inout) buffer
 @param len (in) size of the  buffer
*/
/*-----------------------------------------------------------------------*/
static int SCSCP_readstream_pre(t_controlpipe* ctx, char* buff, int len)
{
    SCSCP_fileio*  file=ctx->file;
	const char *symbol = "<?scscp";
	const int lensymbol = strlen(symbol);
        int eof;
	int copylen=0;
	int retval;
       const char *pfind = NULL;
	    
    if (ctx->mathfind==1) return /*-1*/0;
    if (ctx->mathfind==0)
    {
     do
     {
      int copydata=0;
      int res = 0;
      
      if (copylen+ctx->lastbufvalid<len)
      { /* must read from socket */
        int lenread;
        /*compute the number of bytes to read */
        int nbbytestoread = len-(copylen+ctx->lastbufvalid);
        if (nbbytestoread>SCSCP_PI_MAXLENBUFFER-ctx->lastbufvalid) nbbytestoread = SCSCP_PI_MAXLENBUFFER-ctx->lastbufvalid;
        
        lenread = SCSCP_fileio_read(file, ctx->buffer+ctx->lastbufvalid, nbbytestoread);
        res = lenread;
        eof = (lenread<=0);
        if (!eof)
        { /* no error */
          /* try to find PI instruction : <?scscp */
          int lastlen = ctx->lastbufvalid;
          int lookstart  = (lastlen>lensymbol?lastlen-lensymbol:0);
          ctx->lastbufvalid+=lenread;
          ctx->buffer[ctx->lastbufvalid] = '\0';
          pfind = SCSCP_findstr(ctx->buffer+lookstart, symbol, ctx->lastbufvalid-lookstart);
          if (pfind!=NULL)
          {
           ctx->mathfind=1;
           ctx->pmathfind = (char *)pfind;
           res = pfind-ctx->buffer;
           copydata=1;
           if (res==0) eof=1;
          }
        }
      }
      else
      { /* must copy the data */
       copydata=1;
       res = len-copylen; 
      }
      
      if (copydata==1 && res>0)
      { /* copy from last buffer to buff */
        memcpy(buff+copylen, ctx->buffer, res);
        copylen += res;
        ctx->lastbufvalid -= res;
        if (pfind!=NULL) ctx->pmathfind -=res;
        if (ctx->lastbufvalid>0) memmove(ctx->buffer, ctx->buffer+res,ctx->lastbufvalid);
      }
     } while(eof==0 && copylen<len && ctx->mathfind==0);
    }
    retval = (eof==1)?-1:copylen;
    return retval;
}
    
/*-----------------------------------------------------------------------*/
/*! read an XML stream. This function is called by xmlReadIO.
 must be compatible with xmlInputReadCallback
 return the size of the valid buffer
 return -1 on error or if <?scscp end ?> was found before
 @param ctx (inout) stream context
 @param buff (inout) buffer
 @param len (in) size of the  buffer
*/
/*-----------------------------------------------------------------------*/
static int SCSCP_readstream_XML(t_controlpipe* ctx, char* buff, int len)
{
   int retval;

   SCSCP_debugprint("SCSCP_readstream_XML(%p,%p,%d) - enter\n", ctx, buff, len);

   /*read data from socket */
   retval = SCSCP_readstream_pre(ctx, buff, len);
   
   if (retval>0)
   {
    /*! check that the encoding isn't Binary */
    if (ctx->firstcall==1 && (buff[0]==24 || buff[0]==88)) 
    { 
      SCSCP_debugprint("find a binary encoding\n");
      ctx->m_recvencodingtype = SCSCP_encodingtype_Binary;
      if (ctx->lastbufvalid>0) memmove(ctx->buffer+len, ctx->buffer,ctx->lastbufvalid);
      ctx->lastbufvalid += retval;
      ctx->pmathfind +=retval;
      memcpy(ctx->buffer, buff, retval);
      SCSCP_readstream_post(ctx, 0);
      retval=-1;
    }
    else
    {
     ctx->firstcall = 0;
     retval = SCSCP_readstream_post(ctx, retval);
    }
  }
   
   if (retval>0) { SCSCP_debugprint("read from socket : '%.*s", retval, buff); }
   SCSCP_debugprint("SCSCP_readstream_XML() - return %d\n", retval);
   return retval;
}

/*-----------------------------------------------------------------------*/
/*! function called by SCSCP_binaryReadIO.
 must be compatible with xmlInputReadCallback
 return the size of the valid buffer
 return -1 on error or if <?scscp end ?> was found before
 @param ctx (inout) stream context
 @param buff (inout) buffer
 @param len (in) size of the  buffer
*/
/*-----------------------------------------------------------------------*/
static int SCSCP_readstream_Binary(t_controlpipe* ctx, char* buff, int len)
{
   int retval;
   
   SCSCP_debugprint("SCSCP_readstream_Binary() - enter(%p, %p, %d)  - enter\n", ctx, buff, len);
   
   /*on first call, we already have valid data */
    if (ctx->firstcall==1) 
    { 
      retval = (len<ctx->lastbufvalid)?len:ctx->lastbufvalid;
      memcpy(buff, ctx->buffer, retval);
      ctx->lastbufvalid -= retval;
      if (ctx->lastbufvalid>0) memmove(ctx->buffer, ctx->buffer+retval,ctx->lastbufvalid);
      ctx->firstcall = 0;
   }
   else
   {
    /*read data from socket */
    retval = SCSCP_readstream_pre(ctx, buff, len);
    if (retval>0) retval = SCSCP_readstream_post(ctx, retval);
   }
 
#if !defined(SCSCP_DISABLE_DEBUG)
   SCSCP_debugprint("buffer return :\n");
   {
    int j;
    for (j=0; j<retval; j++) SCSCP_debugprint("%02d (%c) ", (int)buff[j],  isalnum(buff[j])?buff[j]:'%');
   }
   
   SCSCP_debugprint("buffer return 'od -t dC' style : ");
   {
    int j;
    for (j=0; j<retval; j++) 
    {
     if (j%16==0)
     {
      SCSCP_debugprint("\n%07x    ",j);
     }
     SCSCP_debugprint("%3d ", (int)buff[j]);
    }
   }
#endif

   SCSCP_debugprint("\nSCSCP_readstream_Binary() - return  - read %d\n", retval);
   return retval;
}

/*-----------------------------------------------------------------------*/
/*! post-process if <?scscp was found 
 return -1 on error
 @param ctx (inout) stream context
 @param retval (in) default return value
*/
/*-----------------------------------------------------------------------*/
static int SCSCP_readstream_post(t_controlpipe* ctx, int retval)
{
    SCSCP_fileio*  file=ctx->file;
    const char *symbol = "<?scscp";
    const int lensymbol = strlen(symbol);
    int copylen;
    
   SCSCP_debugprint("SCSCP_readstream_post(%p, %d) - enter\n", ctx, retval);

    /* if <?scscp found => find which PI ? */
    if (ctx->mathfind==1)
    {
     const char *lastPI;
     int posbuf;
     int eof;
     ctx->buffer[ctx->lastbufvalid] = '\0';
     eof=0;
     SCSCP_debugprint("start PI processing :'%s'\n", ctx->pmathfind);
     /* find the character "?>" */
     while (!eof && (lastPI=SCSCP_findstr(ctx->pmathfind,"?>", ctx->lastbufvalid-(ctx->pmathfind-ctx->buffer)))==NULL)
     { /*read more data */
        int lenread;
        /*compute the number of bytes to read */
        int nbbytestoread = SCSCP_PI_MAXLENBUFFER-ctx->lastbufvalid;
        
        if (nbbytestoread==0) 
        {
         SCSCP_debugprint("too long PI \n");
         return -1;
        }
        lenread = SCSCP_fileio_read(file, ctx->buffer+ctx->lastbufvalid, nbbytestoread);
        eof = (lenread<=-1);
        if (!eof)
        {
          ctx->lastbufvalid+=lenread;
          ctx->buffer[ctx->lastbufvalid] = '\0';
        }
        else
        { 
         SCSCP_status_seterror(&ctx->status, SCSCP_STATUS_RECVCANCEL);
         SCSCP_debugprint("I/O error...\n");
         retval = -1;
        }
     }
     /* now : detect which PI */
     posbuf = lensymbol;
     while (isspace(ctx->pmathfind[posbuf])) posbuf++;
     
     SCSCP_debugprint("PI processing :'%s'\n", ctx->pmathfind+posbuf);
     if (strncmp(ctx->pmathfind+posbuf,"cancel", strlen("cancel"))==0)
           {
              SCSCP_debugprint("cancel received\n");
              SCSCP_status_seterror(&ctx->status, SCSCP_STATUS_RECVCANCEL);
              retval = -1;
           }
     if (strncmp(ctx->pmathfind+posbuf,"end", strlen("end"))==0)
           {
              SCSCP_debugprint("end received\n");
           }    
     /* now =>remove this PI */
     copylen =  ctx->lastbufvalid-((lastPI+2)-ctx->buffer);
     if (copylen>0) memmove(ctx->pmathfind, lastPI+2,copylen);
     SCSCP_debugprint("copylen=%d : %p %p %d\n",copylen, ctx->pmathfind, ctx->buffer, ctx->lastbufvalid);
     ctx->lastbufvalid=ctx->pmathfind-ctx->buffer+copylen;
     ctx->buffer[ctx->lastbufvalid] = '\0';
     SCSCP_debugprint("keep this buffer for future : %d '%s'\n",ctx->lastbufvalid, ctx->buffer);
    }
    
   SCSCP_debugprint("SCSCP_readstream_post() - return %d\n", retval);
   return retval;
}





/*-----------------------------------------------------------------*/
/*! read the xml document. return 1 on success
  @param status (inout) error code
*/
/*-----------------------------------------------------------------*/
int SCSCP_sc_readxmldoc(SCSCP_Fileclient *client, SCSCP_status* status)
{
  int res;
  t_controlpipe ctx;
  SCSCP_xmldocptr doc = NULL;
  
  SCSCP_debugprint("\nSCSCP_sc_readxmldoc() - enter\n");
  
  LIBXML_TEST_VERSION
  
  ctx.file = &client->m_InStream;
  ctx.status = SCSCP_STATUS_INITIALIZER;
  ctx.buffer = client->m_bufferInput;
  ctx.lastbufvalid = client->m_bufferInputValid;
  ctx.mathfind = 0;
  ctx.firstcall = 1;
  ctx.m_recvencodingtype = SCSCP_encodingtype_XML;
  
  client->m_recvencodingtype = SCSCP_encodingtype_XML;
  
#if SCSCP_PARSERTYPE==SCSCP_PARSERDOM  
  doc = xmlReadIO((xmlInputReadCallback)SCSCP_readstream_XML,NULL, &ctx, NULL, NULL, XML_PARSE_NOERROR|XML_PARSE_NOWARNING| XML_PARSE_NOBLANKS);
  
#elif SCSCP_PARSERTYPE==SCSCP_PARSERSAX 
  doc = SCSCP_DOMxmlReadIO((xmlInputReadCallback)SCSCP_readstream_XML, status, &ctx);
#else
#pragma error "unknown SCSCP_PARSERTYPE"
#endif  

  /* revert to binary encoding */
  if (doc==NULL && ctx.m_recvencodingtype==SCSCP_encodingtype_Binary)
  {
   client->m_recvencodingtype = SCSCP_encodingtype_Binary;
   doc = SCSCP_DOMbinaryReadIO((xmlInputReadCallback)SCSCP_readstream_Binary, &ctx);
  }
  client->m_bufferInputValid = ctx.lastbufvalid;
  if (SCSCP_status_is(status)==SCSCP_STATUS_OK) SCSCP_status_copy(status, &ctx.status);
  res = (doc!=NULL);
  if (res)
  {
   SCSCP_sc_freexmldoc(client);
   client->m_doc = doc;
  }
  else if (SCSCP_status_is(status)==SCSCP_STATUS_OK)
  {
   SCSCP_status_seterror(status, SCSCP_STATUS_OPENMATHNOTVALID);
  }
  
  SCSCP_debugprint("\nSCSCP_sc_readxmldoc(,%d) - returns %d\n", SCSCP_status_is(status), res);
  return res;
}

