/***************************************************************************
 *                                                                         *
 *   CVTAPI.CPP                                                            *
 *                                                                         *
 *   Copyright(c) 1996 Galacticomm, Inc.  All Rights Reserved.             *
 *                                                                         *
 *   This is the generic API for data element conversion and alignment.    *
 *                                                                         *
 *                                            - I. Minkin 12/21/95         *
 *                                                                         *
 ***************************************************************************/

#include "gcommlib.h"

CHAR *cvttmp=NULL;                 // global buffer for conversions
INT cvtbufsiz=0;                   // current size of conversion buffer

                                   // implicit inputs to CVTFUNCs
ULONG cvtType;                     //   full field type (inc'ing sub-types)
ULONG cvtFlags;                    //   internal conversion flags (below)
INT cvtChan;                       //   channel number (or CHAN_NUL)

struct flddef charFDA[]={
     {CVTFLD_CHAR,1,0,NULL},
     {CVTFLD_END ,0,0,NULL}
};

struct flddef charsFDA[]={
     {CVTFLD_CHAR,0,0,NULL},
     {CVTFLD_END ,0,0,NULL}
};

struct flddef shortFDA[]={
     {CVTFLD_SHORT,1,0,NULL},
     {CVTFLD_END  ,0,0,NULL}
};

struct flddef shortsFDA[]={
     {CVTFLD_SHORT,0,0,NULL},
     {CVTFLD_END  ,0,0,NULL}
};

struct flddef longFDA[]={
     {CVTFLD_LONG,1,0,NULL},
     {CVTFLD_END ,0,0,NULL}
};

struct flddef longsFDA[]={
     {CVTFLD_LONG,0,0,NULL},
     {CVTFLD_END ,0,0,NULL}
};

struct flddef floatFDA[]={
     {CVTFLD_FLOAT,1,0,NULL},
     {CVTFLD_END  ,0,0,NULL}
};

struct flddef floatsFDA[]={
     {CVTFLD_FLOAT,0,0,NULL},
     {CVTFLD_END  ,0,0,NULL}
};

struct flddef doubleFDA[]={
     {CVTFLD_DOUBLE,1,0,NULL},
     {CVTFLD_END   ,0,0,NULL}
};

struct flddef doublesFDA[]={
     {CVTFLD_DOUBLE,0,0,NULL},
     {CVTFLD_END   ,0,0,NULL}
};

struct flddef rtextFDA[]={
     {CVTFLD_RTEXT,0,0,NULL},
     {CVTFLD_END  ,0,0,NULL}
};

struct flddef zvbstgFDA[]={
     {CVTFLD_ZVBSTG,0,0,NULL},
     {CVTFLD_END   ,0,0,NULL}
};


struct flddef rzvbstgFDA[]={
     {CVTFLD_RZVBSTG,0,0,NULL},
     {CVTFLD_END   ,0,0,NULL}
};

static VOID cvt_char(VOID *,VOID *,INT *,INT *,INT,size_t);
static VOID cvt_short(VOID *,VOID *,INT *,INT *,INT,size_t);
static VOID cvt_long(VOID *,VOID *,INT *,INT *,INT,size_t);
static VOID cvt_float(VOID *,VOID *,INT *,INT *,INT,size_t);
static VOID cvt_double(VOID *,VOID *,INT *,INT *,INT,size_t);
static INT cvtp2u(VOID *,VOID *,INT,struct flddef *,ULONG,INT,INT *,UINT *,GBOOL *);
static INT cvtu2p(VOID *,VOID *,INT,struct flddef *,ULONG,INT,INT *,UINT *);

struct tagCvtInfo {
     CVTFUNC cvtfunc;
     size_t size;
};

static struct tagCvtInfo cvtInfo[MAXCVT]={
     { NULL,       0             },
     { cvt_char,   sizeof(CHAR)  },
     { cvt_short,  sizeof(SHORT) },
     { cvt_long,   sizeof(LONG)  },
     { cvt_float,  sizeof(FLOAT) },
     { cvt_double, sizeof(DOUBLE)},
     { cvt_char,   sizeof(CHAR)  },
     { cvt_double, sizeof(DOUBLE)},
     { cvt_short,  sizeof(SHORT) },
     { cvt_short,  sizeof(SHORT) },
     { NULL,       0             }
};


INT                                //   returns size of converted dst
cvtData(                           // convert data from buffer to buffer
VOID *src,                         //   source data (one format)
VOID *dst,                         //   destination data (maybe dif format)
INT srclen,                        //   length of source data
struct flddef *fdef,               //   field definition array to use
INT srcff,                         //   format flags for src (from)
INT dstff,                         //   format flags for dst (to)
INT userNum)                       //   user number
{                                  //   (returns 0 if source too short)
     ULONG flags=0;

     if (((srcff&CVTBIGEND) && !(dstff&CVTBIGEND))
      || (!(srcff&CVTBIGEND) && (dstff&CVTBIGEND))) {
          flags|=CVTFLAG_SWAPBYTES;
     }
     if ((srcff&CVTISCLIENT) && !(dstff&CVTISCLIENT)) {
          flags|=CVTFLAG_C2SCVT;
     }
     else if (!(srcff&CVTISCLIENT) && (dstff&CVTISCLIENT)) {
          flags|=CVTFLAG_S2CCVT;
     }
     if (((srcff&CVTPACKED) && (dstff&CVTPACKED))      // both packed
      || (!(srcff&CVTPACKED) && !(dstff&CVTPACKED))) { // or both unpacked
          if (flags&CVTFLAG_SWAPBYTES) {
               if (srcff&CVTPACKED) {
                    // catastro("cvtData: can't byteswap packed to packed!");
               }
               // catastro("cvtData: can't byteswap unpacked to unpacked!");
          }
          memmove(dst,src,srclen);
          return(srclen);
     }
     if (srcff&CVTPACKED) {                                // packed -> unpacked
          return(cvtp2u(src,dst,srclen,fdef,flags,userNum,NULL,NULL,NULL));
     }                                                     // unpacked -> packed
     return(cvtu2p(src,dst,srclen,fdef,flags,userNum,NULL,NULL));
}

INT                                //   returns number of bytes converted
cvtDataIP(                         // convert data in-place
VOID *buf,                         //   buffer (source and destination)
INT inlen,                         //   length of buffer IN
INT outmax,                        //   max length of buffer OUT
struct flddef *fdef,               //   field definition array to use
INT inff,                          //   format flags for buffer IN (from)
INT outff,                         //   format flags for buffer OUT (to)
INT userNum)                       //   user number
{                                  //   (returns 0 if source too short)
     INT retval;
     ULONG flags=0;

     if (((inff&CVTBIGEND) && !(outff&CVTBIGEND))
      || (!(inff&CVTBIGEND) && (outff&CVTBIGEND))) {
          flags|=CVTFLAG_SWAPBYTES;
     }
     if ((inff&CVTISCLIENT) && !(outff&CVTISCLIENT)) {
          flags|=CVTFLAG_C2SCVT;
     }
     else if (!(inff&CVTISCLIENT) && (outff&CVTISCLIENT)) {
          flags|=CVTFLAG_S2CCVT;
     }
     if (((inff&CVTPACKED) && (outff&CVTPACKED))       // both packed
      || (!(inff&CVTPACKED) && !(outff&CVTPACKED))) {  // or both unpacked
          if (flags&CVTFLAG_SWAPBYTES) {
               if (inff&CVTPACKED) {
                    // catastro("cvtDataIP: can't byteswap packed to packed!");
               }
               // catastro("cvtDataIP: can't byteswap unpacked to unpacked!");
          }
          return(inlen);
     }
     if (cvttmp == NULL) {
          cvttmp=(CHAR *)alcmem(cvtbufsiz=CVTBUFSIZ);
     }
     if (outmax > cvtbufsiz) {
          cvttmp=(CHAR *)alcrsz(cvttmp,cvtbufsiz,outmax);
          cvtbufsiz=outmax;
     }
     retval=(inff&CVTPACKED) ? cvtp2u(buf,cvttmp,inlen,fdef,flags,userNum,
                                      NULL,NULL,NULL)
                             : cvtu2p(buf,cvttmp,inlen,fdef,flags,userNum,
                                      NULL,NULL);
     if (retval != 0) {
          memmove(buf,cvttmp,__min(outmax,retval));
     }
     return(retval);
}

INT                                //   returns 0 if ok, otherwise error #
cvtRegister(                       // register a new conversion function
ULONG cvtID,                       //   id you wish to register
CVTFUNC cvtFunction,               //   function to register
size_t elementSize,                //   size of element
GBOOL fOverride)                   //   override existing function flag
{
     USHORT id;

     id=(USHORT)(cvtID&0xFFFF);
     if (id > MAXCVT) {
          return(CVTOOR);
     }
     else if (cvtFunction == NULL || elementSize == 0) {
          return(CVTNUL);
     }
     else if (cvtInfo[id].cvtfunc != NULL && !fOverride) {
          return(CVTXST);
     }
     else {
          cvtInfo[id].cvtfunc=cvtFunction;
          cvtInfo[id].size=elementSize;
     }
     return(CVTOK);
}

LONG                               //   returns LONG value aligned properly
lngval(                            // align a LONG value properly
VOID *ptr)                         //   pointer to source buffer
{
     LONG l;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&l;

     p[0]=base[0];
     p[1]=base[1];
     p[2]=base[2];
     p[3]=base[3];
     return(l);
}

ULONG                              //   rets ULONG value aligned properly
ulngval(                           // align a ULONG value properly
VOID *ptr)                         //   pointer to source buffer
{
     ULONG ul;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&ul;

     p[0]=base[0];
     p[1]=base[1];
     p[2]=base[2];
     p[3]=base[3];
     return(ul);
}

SHORT                              //   returns SHORT value aligned proper
shtval(                            // align a SHORT value properly
VOID *ptr)                         //   pointer to source buffer
{
     SHORT s;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&s;

     p[0]=base[0];
     p[1]=base[1];
     return(s);
}

USHORT                             //   returns USHORT value aligned proper
ushtval(                           // align a USHORT value properly
VOID *ptr)                         //   pointer to source buffer
{
     USHORT s;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&s;

     p[0]=base[0];
     p[1]=base[1];
     return(s);
}

VOID
shtset(                            // sets the offset in a buff to value
VOID *ptr,                         //   pointer to buffer
SHORT value)                       //   value to put into buffer
{
     SHORT i=value;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&i;

     base[0]=p[0];
     base[1]=p[1];
}

VOID
ushtset(                           // sets the offset in a buff to value
VOID *ptr,                         //   pointer to buffer
USHORT value)                      //   value to put into buffer
{
     USHORT i=value;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&i;

     base[0]=p[0];
     base[1]=p[1];
}

VOID
lngset(                            // sets the offset in a buff to value
VOID *ptr,                         //   pointer to buffer
LONG value)                        //   value to put into buffer
{
     LONG i=value;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&i;

     base[0]=p[0];
     base[1]=p[1];
     base[2]=p[2];
     base[3]=p[3];
}

VOID
ulngset(                           // sets the offset in a buff to value
VOID *ptr,                         //   pointer to buffer
ULONG value)                       //   value to put into buffer
{
     ULONG i=value;
     CHAR *base=(CHAR *)ptr;
     CHAR *p=(CHAR *)&i;

     base[0]=p[0];
     base[1]=p[1];
     base[2]=p[2];
     base[3]=p[3];
}

static INT                         //   returns size of aligned structure
cvtp2u(                            // convert pack data to unpacked structure
VOID *src,                         //   source structure (packed)
VOID *dst,                         //   dest. structure(unpacked & swapped)
INT srclen,                        //   length of source data
struct flddef *fdef,               //   field definition array
ULONG flags,                       //   flags to "fine tune" conversion
INT userNum,                       //   user number
INT *pNumBytesProc,                //   number of bytes processed
UINT *pMaxElementSize,             //   max size of structure's element
GBOOL *pfVariableLength)           //   is struct contains var. length part
{                                  //   (returns 0 if source too short)
     INT numElements;
     INT numBytesIn=0;
     INT numBytesOut=0;
     INT numBytes;
     INT srcUsed,dstUsed;
     size_t maxElementSize=0;
     INT i;
     INT structSize;
     size_t tmpElementSize=0;
     CHAR *pSrc=(CHAR *)src;
     CHAR *pDst=(CHAR *)dst;
     GBOOL fVarLength=FALSE;
     ULONG type;

     for (i=0,type=fdef[i].type&0xFFFF ; fdef[i].type != CVTFLD_END
        ; type=fdef[++i].type&0xFFFF) {
          if (srclen < 0 || (srclen == 0 && fdef[i].nelem != 0)) {
               return(0);
          }
          numBytesOut=fdef[i].offset;
          if (type != CVTFLD_STRUCT) {
               maxElementSize=__max(maxElementSize,cvtInfo[(USHORT)type].size);
               if ((numElements=fdef[i].nelem) == 0) {
                    numElements=srclen/cvtInfo[(USHORT)type].size;
                    fVarLength=TRUE;
               }
               cvtType=fdef[i].type;
               cvtFlags=flags;
               cvtChan=userNum;
               (*cvtInfo[(USHORT)type].cvtfunc)(&pSrc[numBytesIn],
                                                &pDst[numBytesOut],
                                                &srcUsed,&dstUsed,
                                                numElements,
                                                cvtInfo[(USHORT)type].size);
               if (srcUsed > srclen) {
                    return(0);
               }
               srclen-=srcUsed;
               numBytesOut+=dstUsed;
               numBytesIn+=srcUsed;
          }
          else {
               if (fdef[i].substruct == NULL) {
                    // catastro("cvtp2u: NULL substructure definition");
               }
               if ((numElements=fdef[i].nelem) == 0) {
                    numElements=-1;
                    fVarLength=TRUE;
               }
               for ( ; numElements != 0 && srclen > 0 ; numElements--) {
                    tmpElementSize=0;
                    structSize=cvtp2u(&pSrc[numBytesIn],
                                      &pDst[numBytesOut],
                                      srclen,
                                      fdef[i].substruct,
                                      flags,
                                      userNum,
                                      &numBytes,
                                      &tmpElementSize,
                                      &fVarLength);
                    if (structSize == 0) {
                         return(0);
                    }
                    srclen-=numBytes;
                    numBytesOut+=structSize;
                    numBytesIn+=numBytes;
                    maxElementSize=__max(maxElementSize,tmpElementSize);
               }
          }
     }
     if (pNumBytesProc != NULL) {
          *pNumBytesProc=numBytesIn;
     }
     if (pMaxElementSize != NULL) {
          *pMaxElementSize=maxElementSize;
     }
     if (pfVariableLength != NULL) {
          *pfVariableLength=fVarLength;
     }
     maxElementSize=__min(maxElementSize,MAXPADDING);
     // if structure has a variable length portion we do not add padding bytes
     // to the end so we do not confuse the hell out of people who expect
     // "return size" - "variable size" == "fixed length size"
     if (!fVarLength && maxElementSize != 0) {
          numBytesOut+=(maxElementSize-numBytesOut%maxElementSize)
                                                               %maxElementSize;
     }
     return(numBytesOut);
}

static INT                         //   returns size of packed structure
cvtu2p(                            // convert unpacked struct to pack data
VOID *src,                         //   source structure (unpacked)    
VOID *dst,                         //   destination buffer             
INT srclen,                        //   length of source data          
struct flddef *fdef,               //   field definition array         
ULONG flags,                       //   flags to "fine tune" conversion
INT userNum,                       //   user number                    
INT *pNumBytesProc,                //   number of bytes processed      
UINT *pMaxElementSize)             //   max size of structure's element
{                                  //   (returns 0 if source too short)
     INT numElements;
     INT numBytesIn=0;
     INT numBytesOut=0;
     INT numBytes;
     INT srcUsed,dstUsed;
     size_t maxElementSize=0;
     INT i;
     INT structSize;
     size_t tmpElementSize=0;
     INT tmpsrclen;
     CHAR *pSrc=(CHAR *)src;
     CHAR *pDst=(CHAR *)dst;
     ULONG type;

     for (i=0,type=fdef[i].type&0xFFFF ; fdef[i].type != CVTFLD_END
        ; type=fdef[++i].type&0xFFFF) {
          numBytesIn=fdef[i].offset;
          tmpsrclen=srclen-numBytesIn;
          if (tmpsrclen < 0 || (tmpsrclen == 0 && fdef[i].nelem != 0)) {
               return(0);
          }
          if (type != CVTFLD_STRUCT) {
               maxElementSize=__max(maxElementSize,cvtInfo[(USHORT)type].size);
               if ((numElements=fdef[i].nelem) == 0) {
                    numElements=(srclen-fdef[i].offset)
                                                  /cvtInfo[(USHORT)type].size;
               }
               cvtType=fdef[i].type;
               cvtFlags=flags;
               cvtChan=userNum;
               (*cvtInfo[(USHORT)type].cvtfunc)(&pSrc[numBytesIn],
                                                &pDst[numBytesOut],
                                                &srcUsed,&dstUsed,
                                                numElements,
                                                cvtInfo[(USHORT)type].size);
               if (srcUsed > tmpsrclen) {
                    return(0);
               }
               numBytesOut+=dstUsed;
               numBytesIn+=srcUsed;
          }
          else {
               if (fdef[i].substruct == NULL) {
                    // catastro("cvtu2p: NULL substructure definition");
               }
               if ((numElements=fdef[i].nelem) == 0) {
                    numElements=-1;
               }
               for ( ; numElements != 0 && tmpsrclen > 0 ; numElements--) {
                    tmpElementSize=0;
                    numBytes=cvtu2p(&pSrc[numBytesIn],
                                    &pDst[numBytesOut],
                                    tmpsrclen,
                                    fdef[i].substruct,
                                    flags,
                                    userNum,
                                    &structSize,
                                    &tmpElementSize);
                    if (numBytes == 0) {
                         return(0);
                    }
                    tmpsrclen-=structSize;
                    numBytesOut+=numBytes;
                    numBytesIn+=structSize;
                    maxElementSize=__max(maxElementSize,tmpElementSize);
               }
          }
     }
     maxElementSize=__min(maxElementSize,MAXPADDING);
     if (maxElementSize != 0) {
          numBytesIn+=(maxElementSize-numBytesIn%maxElementSize)%maxElementSize;
     }
     if (pNumBytesProc != NULL) {
          *pNumBytesProc=numBytesIn;
     }
     if (pMaxElementSize != NULL) {
          *pMaxElementSize=maxElementSize;
     }
     return(numBytesOut);
}

static VOID
cvt_short(                         // convert type 'short' to native form 
VOID *src,                         //   raw little-endian data            
VOID *dst,                         //   cooked native-endian data         
INT *srcUsed,                      //   output: source bytes used         
INT *dstUsed,                      //   output: destination bytes used    
INT nelem,                         //   number of source elements         
size_t size)                       //   size of element                   
{
     INT i;
     CHAR *sp,*dp,ch1;

     if (cvtFlags&CVTFLAG_SWAPBYTES) {
          sp=(CHAR *)src;
          dp=(CHAR *)dst;
          for(i=0 ; i < nelem ; i++) {
               ch1=*sp++;
               *dp++=*sp++;
               *dp++=ch1;
          }
     }
     else {
          memmove(dst,src,nelem*size);
     }
     *srcUsed=*dstUsed=nelem*size;
}

static VOID
cvt_double(                        // convert type 'double' to native form 
VOID *src,                         //   raw little-endian data             
VOID *dst,                         //   cooked native-endian data          
INT *srcUsed,                      //   output: source bytes used          
INT *dstUsed,                      //   output: destination bytes used     
INT nelem,                         //   number of source elements          
size_t size)                       //   size of element                    
{
     INT i;
     CHAR *sp,*dp;
     CHAR ch1,ch2,ch3,ch4,ch5,ch6,ch7,ch8;

     if (cvtFlags&CVTFLAG_SWAPBYTES) {
          sp=(CHAR *)src;
          dp=(CHAR *)dst;
          for(i=0 ; i < nelem ; i++) {
               ch1=*sp++;
               ch2=*sp++;
               ch3=*sp++;
               ch4=*sp++;
               ch5=*sp++;
               ch6=*sp++;
               ch7=*sp++;
               ch8=*sp++;
               *dp++=ch8;
               *dp++=ch7;
               *dp++=ch6;
               *dp++=ch5;
               *dp++=ch4;
               *dp++=ch3;
               *dp++=ch2;
               *dp++=ch1;
          }
     }
     else {
          memmove(dst,src,nelem*size);
     }
     *srcUsed=*dstUsed=nelem*size;
}

static VOID
cvt_char(                          // convert type 'char' to native format 
VOID *src,                         //   raw little-endian data             
VOID *dst,                         //   cooked native-endian data          
INT *srcUsed,                      //   output: source bytes used          
INT *dstUsed,                      //   output: destination bytes used     
INT nelem,                         //   number of source elements          
size_t size)                       //   size of element                    
{
     GBOOL fCSConversion;
     INT csiz;

     fCSConversion=(cvtFlags&CVTFLAG_C2SCVT)
                || (cvtFlags&CVTFLAG_S2CCVT) ? TRUE : FALSE;
     if (fCSConversion && cvtType&CVTSUBFLD_ZVBSTG) {
          csiz=nelem*size;
          if (csiz > 0) {
               assert(src != dst);
               if (cvtFlags&CVTFLAG_C2SCVT) {
                    b2ccpy((CHAR *)dst,(CHAR *)src,csiz);
                    *srcUsed=csiz-1;
                    *dstUsed=csiz;
               }
               else {
                    c2bcpy((CHAR *)dst,(CHAR *)src,csiz-1);
                    *srcUsed=csiz;
                    *dstUsed=csiz-1;
               }
          }
          else {
               *srcUsed=*dstUsed=0;
          }
     }
     else {
          memmove(dst,src,nelem*size);
          *srcUsed=*dstUsed=nelem*size;
     }
//     if (fCSConversion && cvtChan != CHAN_NUL && cvtType&CVTSUBFLD_RTEXT) {
          /* this is where readable text conversion would happen */
          /*  (C2SCVT vs. S2CCVT used with userNum should be it) */
//     }
}

static VOID
cvt_long(                          // convert type 'long' to native form   
VOID *src,                         //   raw little-endian data             
VOID *dst,                         //   cooked native-endian data          
INT *srcUsed,                      //   output: source bytes used          
INT *dstUsed,                      //   output: destination bytes used     
INT nelem,                         //   number of source elements          
size_t size)                       //   size of element                    
{
     INT i;
     CHAR *sp,*dp;
     CHAR ch1,ch2,ch3,ch4;

     if (cvtFlags&CVTFLAG_SWAPBYTES) {
          sp=(CHAR *)src;
          dp=(CHAR *)dst;
          for(i=0 ; i < nelem ; i++) {
               ch1=*sp++;
               ch2=*sp++;
               ch3=*sp++;
               ch4=*sp++;
               *dp++=ch4;
               *dp++=ch3;
               *dp++=ch2;
               *dp++=ch1;
          }
     }
     else {
          memmove(dst,src,nelem*size);
     }
     *srcUsed=*dstUsed=nelem*size;
}

static VOID
cvt_float(                         // convert type 'float' to native form  
VOID *src,                         //   raw little-endian data             
VOID *dst,                         //   cooked native-endian data          
INT *srcUsed,                      //   output: source bytes used          
INT *dstUsed,                      //   output: destination bytes used     
INT nelem,                         //   number of source elements          
size_t size)                       //   size of element                    
{
     cvt_long(src,dst,srcUsed,dstUsed,nelem,size);
}
