#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include "G3d_intern.h"

/*---------------------------------------------------------------------------*/

int G3d_isXdrNullNum(const void *num, int isFloat)
{
    static const char null_bytes[8] = {
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
    };

    return memcmp(num, null_bytes, isFloat ? 4 : 8) == 0;
}

/*---------------------------------------------------------------------------*/

int G3d_isXdrNullFloat(const float *f)
{
    return G3d_isXdrNullNum(f, 1);
}

/*---------------------------------------------------------------------------*/

int G3d_isXdrNullDouble(const double *d)
{
    return G3d_isXdrNullNum(d, 0);
}

/*---------------------------------------------------------------------------*/

void G3d_setXdrNullNum(void *num, int isFloat)
{
    static const char null_bytes[8] = {
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
    };

    memcpy(num, null_bytes, isFloat ? 4 : 8);
}

/*---------------------------------------------------------------------------*/

void G3d_setXdrNullDouble(double *d)
{
    G3d_setXdrNullNum(d, 0);
}

/*---------------------------------------------------------------------------*/

void G3d_setXdrNullFloat(float *f)
{
    G3d_setXdrNullNum(f, 1);
}

/*---------------------------------------------------------------------------*/

XDR xdrEncodeStream, xdrDecodeStream;	/* xdr support structures */

int G3d_initFpXdr(G3D_Map * map, int misuseBytes)



 /* nof addtl bytes allocated for the xdr array so that */
		      /* the array can also be (mis)used for other purposes */
{
    int doAlloc;

    doAlloc = 0;

    if (xdr == NULL) {
	xdrLength = map->tileSize * G3D_MAX(map->numLengthExtern,
					    map->numLengthIntern) +
	    misuseBytes;
	xdr = G3d_malloc(xdrLength);
	if (xdr == NULL) {
	    G3d_error("G3d_initFpXdr: error in G3d_malloc");
	    return 0;
	}

	doAlloc = 1;
    }
    else if (map->tileSize * G3D_MAX(map->numLengthExtern,
				     map->numLengthIntern) + misuseBytes
	     > xdrLength) {
	xdrLength = map->tileSize * G3D_MAX(map->numLengthExtern,
					    map->numLengthIntern) +
	    misuseBytes;
	xdr = G3d_realloc(xdr, xdrLength);
	if (xdr == NULL) {
	    G3d_error("G3d_initFpXdr: error in G3d_realloc");
	    return 0;
	}

	doAlloc = 1;
    }

    if (doAlloc) {
	xdrmem_create(&(xdrEncodeStream), xdr, (u_int) xdrLength, XDR_ENCODE);
	xdrmem_create(&(xdrDecodeStream), xdr, (u_int) xdrLength, XDR_DECODE);
    }

    return 1;
}

/*---------------------------------------------------------------------------*/

static void *xdrTmp;
static int dstType, srcType, type, externLength, eltLength, isFloat, useXdr;
static int (*xdrFun) ();
static XDR *xdrs;
static double tmpValue, *tmp;

int G3d_initCopyToXdr(G3D_Map * map, int sType)
{
    xdrTmp = xdr;
    useXdr = map->useXdr;
    srcType = sType;

    if (map->useXdr == G3D_USE_XDR) {
	if (!xdr_setpos(&(xdrEncodeStream), 0)) {
	    G3d_error("G3d_InitCopyToXdr: positioning xdr failed");
	    return 0;
	}
	xdrs = &(xdrEncodeStream);
    }

    type = map->type;
    isFloat = (type == FCELL_TYPE);
    externLength = G3d_externLength(type);
    eltLength = G3d_length(srcType);
    if (isFloat)
	xdrFun = xdr_float;
    else
	xdrFun = xdr_double;
    tmp = &tmpValue;

    return 1;
}

/*---------------------------------------------------------------------------*/

int G3d_copyToXdr(const void *src, int nofNum)
{
    int i;

    if (useXdr == G3D_NO_XDR) {
	G3d_copyValues(src, 0, srcType, xdrTmp, 0, type, nofNum);
	xdrTmp = G_incr_void_ptr(xdrTmp, nofNum * G3d_externLength(type));
	return 1;
    }

    for (i = 0; i < nofNum; i++, src = G_incr_void_ptr(src, eltLength)) {

	if (G3d_isNullValueNum(src, srcType)) {
	    G3d_setXdrNullNum(xdrTmp, isFloat);
	    if (!xdr_setpos(xdrs, xdr_getpos(xdrs) + externLength)) {
		G3d_error("G3d_copyToXdr: positioning xdr failed");
		return 0;
	    }
	}
	else {
	    if (type == srcType) {
		if (xdrFun(xdrs, src) < 0) {
		    G3d_error("G3d_copyToXdr: writing xdr failed");
		    return 0;
		}
	    }
	    else {
		if (type == FCELL_TYPE)
		    *((float *)tmp) = (float)*((double *)src);
		else
		    *((double *)tmp) = (double)*((float *)src);
		if (xdrFun(xdrs, tmp) < 0) {
		    G3d_error("G3d_copyToXdr: writing xdr failed");
		    return 0;
		}
	    }
	}

	xdrTmp = G_incr_void_ptr(xdrTmp, externLength);
    }

    return 1;
}

/*---------------------------------------------------------------------------*/

int G3d_initCopyFromXdr(G3D_Map * map, int dType)
{
    xdrTmp = xdr;
    useXdr = map->useXdr;
    dstType = dType;

    if (useXdr == G3D_USE_XDR) {
	if (!xdr_setpos(&(xdrDecodeStream), 0)) {
	    G3d_error("G3d_initCopyFromXdr: positioning xdr failed");
	    return 0;
	}
	xdrs = &(xdrDecodeStream);
    }

    type = map->type;
    isFloat = (type == FCELL_TYPE);
    externLength = G3d_externLength(type);
    eltLength = G3d_length(dstType);
    if (isFloat)
	xdrFun = xdr_float;
    else
	xdrFun = xdr_double;
    tmp = &tmpValue;

    return 1;
}

/*---------------------------------------------------------------------------*/

int G3d_copyFromXdr(int nofNum, void *dst)
{
    int i;

    if (useXdr == G3D_NO_XDR) {
	G3d_copyValues(xdrTmp, 0, type, dst, 0, dstType, nofNum);
	xdrTmp = G_incr_void_ptr(xdrTmp, nofNum * G3d_externLength(type));
	return 1;
    }

    for (i = 0; i < nofNum; i++, dst = G_incr_void_ptr(dst, eltLength)) {

	if (G3d_isXdrNullNum(xdrTmp, isFloat)) {
	    G3d_setNullValue(dst, 1, dstType);
	    if (!xdr_setpos(xdrs, xdr_getpos(xdrs) + externLength)) {
		G3d_error("G3d_copyFromXdr: positioning xdr failed");
		return 0;
	    }
	}
	else {
	    if (type == dstType) {
		if (xdrFun(xdrs, dst) < 0) {
		    G3d_error("G3d_copyFromXdr: reading xdr failed");
		    return 0;
		}
	    }
	    else {
		if (xdrFun(xdrs, tmp) < 0) {
		    G3d_error("G3d_copyFromXdr: reading xdr failed");
		    return 0;
		}
		if (type == FCELL_TYPE)
		    *((double *)dst) = (double)*((float *)tmp);
		else
		    *((float *)dst) = (float)*((double *)tmp);
	    }
	}

	xdrTmp = G_incr_void_ptr(xdrTmp, externLength);
    }

    return 1;
}
