#ifndef _XDR_CLASS_H_
#define _XDR_CLASS_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined (ultrix) || defined (sgi) || defined (solaris)
#include <rpc/types.h>
#endif

// Under TGV Multinet and VMS, before you can #include rpc/xdr.h (below),
// rpc/types.h must be included, so that bool_t is defined and sys/types.h is
// #included.
//
// Under TGV Multinet and VMS, if you want sys/types.h, you need to have
// types.h already pulled in because sys/types.h makes it look like types.h
// is loaded.  Then when types.h does get loaded, it is ignored because it
// looks like it is already loaded.
//
// rpc/types.h defines bool_t,               required by rpc/xdr.h
// rpc/types.h #includes sys/types.h
// sys/types.h defines caddr_t,              required by rpc/xdr.h and
//             defines u_int,u_short         required by netinet/in.h
// --Mr. Daniel Van Olst at SLAC vanolst@slc.slac.stanford.edu

#ifdef __VMS
#ifdef _TGV_MULTINET
#include <types.h>
#include <rpc/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#endif
#include <rpc/xdr.h>
#endif

#ifdef _WIN32
#include "xdr.h"
#else
#include <rpc/rpc.h>
#include <netinet/in.h>
#endif
#include <cdevTypes.h>

static bool_t xdr_timestamp( XDR * xdrs, cdev_TS_STAMP * ts) {
  if ( xdr_u_long (xdrs, &ts->secPastEpoch) == 0) return 0;
  return (xdr_u_long (xdrs, &ts->nsec)); 
}

// *****************************************************************************
// * XDR_Size_of :
// * Functions used to determine the size of a data element once translated
// * and placed into an XDR stream.
// *****************************************************************************
#if !defined( NO_TEMPLATES )
	template <class T> inline unsigned XDR_Sizeof( T t ) { 
		return (RNDUP(sizeof(t))); }
#else
	inline unsigned XDR_Sizeof(char x)           { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(unsigned char x)  { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(short x)          { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(unsigned short x) { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(int x)            { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(unsigned int x)   { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(long x)           { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(unsigned long x)  { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(float x)          { return (RNDUP(sizeof(x))); }
	inline unsigned XDR_Sizeof(double x)         { return (RNDUP(sizeof(x))); }
       
#endif

inline unsigned XDR_Sizeof( char * s ) { 
	return (RNDUP(strlen(s)) + BYTES_PER_XDR_UNIT); }

inline unsigned XDR_Sizeof( char *, unsigned len ) { 
	return (RNDUP(len) + BYTES_PER_XDR_UNIT);  } 

inline unsigned XDR_Sizeof( void *, unsigned len ) { 
	return (RNDUP(len) + BYTES_PER_XDR_UNIT);  } 

inline unsigned XDR_Sizeof( cdev_TS_STAMP ts ) { 
        return (RNDUP(sizeof(ts)));  } 

typedef struct
	{
	unsigned bufLen;
	char   * buf;
	} XDR_DataBlock;

	
// *****************************************************************************
// * This is the XDR_Data class.  It allows the user to pass the data associated
// * with the XDR_Writer and XDR_Reader classes in a format that is not 
// * restricted to XDR read or XDR write.
// *****************************************************************************
class XDR_Data : public XDR_DataBlock
	{
	public:
		XDR_Data  ( void )
			{ buf = NULL; bufLen = 0; }

		XDR_Data  ( char * Buf, unsigned Len )
			{ buf = NULL; bufLen = 0; assign(Buf, Len); }
			 
		XDR_Data  ( XDR_Data & x )
			{ buf = NULL; bufLen = 0; assign(x.buf, x.bufLen); }
			
		~XDR_Data           ( void ) { deallocate(); }
		unsigned & size     ( void ) { return bufLen; }
		char *   & buffer   ( void ) { return buf; }
		int        ready    ( void ) { return (buf!=NULL && bufLen>0)?1:0; }
		
		void allocate ( unsigned Len ) 
			{
			if(Len==0) XDR_Data::deallocate();
			else if(Len!=bufLen || buf==NULL)
				{
				XDR_Data::deallocate(); 
				bufLen = Len;
				buf = new char[bufLen];
				XDR_Data::clear();
				}
			else XDR_Data::clear();
			}
		
		void deallocate ( void )
			{
			if(buf!=NULL)
				{
				delete buf;
				buf = NULL;
				}
			bufLen = 0;
			}				
		
		void assign ( char * Buf, unsigned Len)
			{
			XDR_Data::allocate(Len);
			memcpy(buf, Buf, bufLen);
			}		
		
		void clear ( void )
			{
			memset(buf, 0, bufLen);			
			}

		void attachData ( XDR_Data & data )
			{
			attachData(data.buf, data.bufLen);
			}
	
		void attachData ( char * Buf, unsigned BufLen )
			{
			XDR_Data::deallocate();
			buf    = Buf;
			bufLen = BufLen;
			}

		void detachData ( void )
			{
			buf    = 0;
			bufLen = 0;
			}
	};


// *****************************************************************************
// * This is the XDR_Base class.  It provides the basic mechanism for generating
// * character streams that contain XDR data storage.
// *****************************************************************************
class XDR_Base : public XDR_Data
	{
	protected:
		XDR               xdrs;
		const enum xdr_op op;
		
		XDR_Base          (enum xdr_op xop ) : XDR_Data(), op(xop) {}
		XDR_Base          (XDR_Data & xdrData, enum xdr_op xop) : op(xop)
			{
			allocate(xdrData.size());
			memcpy(buf, xdrData.buffer(), xdrData.size()); 
			xdr_setpos(xdr(), 0);
			}
		~XDR_Base         ( void )     { deallocate(); }		
			
	public:
		XDR  *   xdr      ( void )     { return &xdrs; } 
		unsigned position ( void )     { return ready()?xdr_getpos(&xdrs):0L; }
		
		void   allocate   ( unsigned Len) 
			{
			if(Len==0) XDR_Base::deallocate();
			else if(Len!=bufLen || buf==NULL) 
				{
				XDR_Base::deallocate();
				XDR_Data::allocate(Len);
				xdrmem_create(&xdrs, buf, bufLen, op);
				}
			else XDR_Data::clear();
			}
			
		void   deallocate ( void )
			{
			if(ready())
				{
				xdr_destroy(&xdrs);
				XDR_Data::deallocate();
				}
			}

		void   clear      ( void )
			{
			if(ready())
				{
				XDR_Data::clear();
				xdr_setpos(&xdrs, 0);
				}
			}

		void   attachData ( XDR_Data & data )
			{
			XDR_Base::attachData(data.buf, data.bufLen);
			}
			
		void   attachData ( char * Buf, unsigned BufLen )
			{
			XDR_Base::deallocate();
			XDR_Data::attachData(Buf, BufLen);
			xdrmem_create(&xdrs, buf, bufLen, op);
			}
			
		void   detachData ( void )
			{
			XDR_Data::detachData();
			XDR_Base::deallocate();
			}
	};
	


// *****************************************************************************
// * This is the XDR_Writer class.  It provides the mechanisms for creating and
// * managing an outgoing stream of XDR data.
// *****************************************************************************
class XDR_Writer : public XDR_Base
	{
	public:
		XDR_Writer (unsigned len = 0) : XDR_Base(XDR_ENCODE) { XDR_Base::allocate(len); }
		XDR_Writer (XDR_Data & xdrData ) : XDR_Base(xdrData, XDR_ENCODE) {}

		int put_char ( char x )            
			{ return ready()?xdr_char(&xdrs, &x):0; } 
		
		int put_u_char ( unsigned char x )
#ifndef sunos4
			{ return ready()?xdr_u_char(&xdrs, &x):0; } 
#else
			{ return ready()?xdr_u_char(&xdrs, (char *)&x):0; } 
#endif
		
		int put_int ( int x )		
			{ return ready()?xdr_int(&xdrs, &x):0; }
		
		int put_u_int ( unsigned int x )
			{ return ready()?xdr_u_int(&xdrs, &x):0; }
		
		int put_short ( short x )
			{ return ready()?xdr_short(&xdrs, &x):0; }

		int put_u_short( unsigned short x )
			{ return ready()?xdr_u_short(&xdrs, &x):0; }

		int put_long ( long x )
			{ return ready()?xdr_long(&xdrs, &x):0; }

		int put_u_long ( unsigned long x )
			{ return ready()?xdr_u_long(&xdrs, &x):0; }

		int put_float ( float x )
			{ return ready()?xdr_float(&xdrs, &x):0; }

		int put_double ( double x )
			{ return ready()?xdr_double(&xdrs, &x):0; }

		int put_bytes ( char ** bytes, unsigned int num )
			{ return ready()?
			  xdr_bytes(&xdrs, bytes, &num, size()-position()):
			  0; }
		
		int put_string ( char * str )
			{ return ready()?
			  xdr_string(&xdrs, &str, size()-position()):
			  0; }
		
		int put_opaque ( void * data, unsigned len )
			{ int status = 0;
			  if(ready()) 
			  	{
			  	status=xdr_u_int (&xdrs, &len);
			  	if(status) status=xdr_opaque(&xdrs, (char *)data, len);
				}
			  return status; 
			  }
			  
		int  put_timestamp ( cdev_TS_STAMP ts )
		        { return ready()?xdr_timestamp(&xdrs, &ts):0; }

		int put ( char x )           { return put_char(x); }
		int put ( unsigned char x )  { return put_u_char(x); }
		int put ( int x )            { return put_int(x); }
		int put ( unsigned int x )   { return put_u_int(x); }
		int put ( short x )          { return put_short(x); } 
		int put ( unsigned short x ) { return put_u_short(x); }
		int put ( long x )           { return put_long(x); }
		int put ( unsigned long x )  { return put_u_long(x); }
		int put ( float x )          { return put_float(x); }
		int put ( double x )         { return put_double(x); }
		int put ( char * str )       { return put_string(str); }
		int put ( void * x, unsigned len ) { return put_opaque(x, len); }
		int put ( cdev_TS_STAMP ts )       { return put_timestamp(ts); }
	};


// *****************************************************************************
// * This is the XDR_Reader class.  It provides the mechanisms for creating and
// * managing an incoming stream of XDR data.
// *****************************************************************************
class XDR_Reader : public XDR_Base
	{
	public:
		XDR_Reader (unsigned len = 0) : XDR_Base(XDR_DECODE) { XDR_Base::allocate(len); }
		XDR_Reader (XDR_Data & xdrData ) : XDR_Base(xdrData, XDR_DECODE) {}
			
		int get_char ( char & x )		
			{ return ready()?xdr_char(&xdrs, &x):0; } 
			
		int get_u_char ( unsigned char & x )	
#ifndef sunos4
			{ return ready()?xdr_u_char(&xdrs, &x):0; } 
#else
			{ return ready()?xdr_u_char(&xdrs, (char *)&x):0; } 
#endif
			
		int get_int ( int & x )		
			{ return ready()?xdr_int(&xdrs, &x):0; }
			
		int get_u_int ( unsigned int & x )	
			{ return ready()?xdr_u_int(&xdrs, &x):0; }
			
		int get_short ( short & x )		
			{ return ready()?xdr_short(&xdrs, &x):0; }
			
		int get_u_short ( unsigned short & x )	
			{ return ready()?xdr_u_short(&xdrs, &x):0; }
		
		int get_long ( long & x )		
			{ return ready()?xdr_long(&xdrs, &x):0; }
		
		int get_u_long ( unsigned long & x )	
			{ return ready()?xdr_u_long(&xdrs, &x):0; }
		
		int get_float ( float & x )		
			{ return ready()?xdr_float(&xdrs, &x):0; }
		
		int get_double ( double & x )		
			{ return ready()?xdr_double(&xdrs, &x):0; }
		
		int get_bytes ( char ** bytes, unsigned int & num, unsigned int max )
			{ return ready()?xdr_bytes(&xdrs, bytes, &num, max):0; }

		int get_string_len ( void )
			{ return ready()?ntohl(*(long *)((char *)buf+xdr_getpos(&xdrs))):0; }

		int get_string ( char ** str, unsigned int max = -1U )
			{ 
			int    result = 0;
			int    slen   = get_string_len()+1;
			char * s      = new char[slen];
			if(ready()) 
				{
				result = xdr_string(&xdrs, &s, slen); 
				if(str!=NULL)
					{
					if(max==-1U || *str==NULL)
						{
						*str = s;
						s = NULL;
						}
					else 
						{
						strncpy(*str, s, max);
						str[max-1] = 0;
						}
					}
				}
			if(s!=NULL) delete s;
			return result;
			}
		
		int get_opaque ( void ** data, unsigned int * len, unsigned int max = -1U)
			{
			int result = 0;
			
			if(ready())
				{
				unsigned int slen;
				char * s;
				
				if((result=xdr_u_int(&xdrs, &slen))!=0)
					{
					s = new char[slen];
					result=xdr_opaque(&xdrs, s, slen);
					}
				if(result!=0)
					{	
					if(*data==NULL || max==-1U)
						{
						*len  = slen;
						*data = s;
						s     = NULL;
						}
					else	{
						*len = (slen<max)?slen:max;
						memcpy(*data, s, *len);
						}
					}
				if(s!=NULL) delete s;
				}
			return result;
			}
		
		int get_timestamp ( cdev_TS_STAMP & ts )		
			{ return ready()?xdr_timestamp(&xdrs, &ts):0; }
			
		int get ( char & x )           { return get_char(x); }
		int get ( unsigned char & x )  { return get_u_char(x); }
		int get ( int & x )            { return get_int(x); }
		int get ( unsigned int & x )   { return get_u_int(x); }
		int get ( short & x )          { return get_short(x); } 
		int get ( unsigned short & x ) { return get_u_short(x); }
		int get ( long & x )           { return get_long(x); }
		int get ( unsigned long & x )  { return get_u_long(x); }
		int get ( float & x )          { return get_float(x); }
		int get ( double & x )         { return get_double(x); }
		int get ( char ** s, unsigned max = -1U) { return get_string(s, max); }	
		int get ( void ** x, unsigned *len, unsigned max = -1U) { return get_opaque(x, len, max); }
		int get ( cdev_TS_STAMP & ts )         { return get_timestamp(ts); }
	};


#endif // _XDR_CLASS_H_
