/* === CMA-packing method#1 (revised) === */

/* Bugfixed on 20/02/2003 by SS :
   Method: 
   -- Set avg == 0, when ABS(vmax - vmin) >= INT_MAX
   Bug spotted/generated by Lueder van Bremen (ECMWF) in his experiment with MODIS (satob) winds */

/* Thanks to Bob Carruthers from Cray for supplying Cray X1 (SV2) 
   directives & mods for better vectorization (Aug-Sep/2004) */

#include "pcma.h"

#include <signal.h>
#include <math.h>

#ifdef VPP
#pragma global noalias
#pragma global novrec
#elif defined(NECSX)
#pragma cdir options -pvctl,nodep
#endif

/*		      0U * (((u) - avg) >= ZERO && (u) != nmdi && (u) != rmdi) + */

#define IS_ONE_OF(u) ( \
		      1U * (((u) - avg) <  ZERO && (u) != nmdi && (u) != rmdi) + \
		      2U * ((u) == nmdi) + \
		      3U * ((u) == rmdi))

#define TWO (N32BITS/b16)

#define DEFMASK(x) unsigned int mask##x = ( IS_ONE_OF(cma[i+(x)]) << (TWO*(x)) )

/* pcma_1_prepare: Prepare for compression */

static int *
pcma_1_prepare(double nmdi,
	       double rmdi,
	       const double cma[], /* double CMA[] */
	       int  lencma,        /* length of the double CMA[] */
	       int *Nbits,         /* No. of bits required */
	       int *Noffset,       /* Length of offset array [with the sign preserved] */
	       int *Npacked,       /* Minimum length for output packed array */
	       double *Avg         /* Value of the average */
	       )
{
  double avg = 0;
  double vmax = -INT_MAX;
  double vmin =  INT_MAX;
  int i, j, n;
  int nbits = 1;
  unsigned int maxval;
  int *Offset = NULL;
  
  n = 0;
  /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
  */
  for (i=0; i<lencma; i++) {
    if (cma[i] != nmdi && cma[i] != rmdi) {
      n++;
      avg += cma[i];
      if (vmax < cma[i]) vmax = cma[i];
      if (vmin > cma[i]) vmin = cma[i];
    }
  }

  if (vmin == vmax) {
    nbits = 0; /* Special case: all non-MDI's are the same */
    avg = vmax;
  }
  else if (ABS(vmax - vmin) < INT_MAX) {
    if (n > 1) avg /= n;
    /* floor'ing towards zero; trunc() works erratically on Linux */
    if (avg >= 0) 
      avg = floor(avg);
    else {
      avg = -avg;
      avg = -floor(avg);
    }
  }
  else { /* Play safe */
    avg = 0;
  }

  if (nbits > 0) {
    ALLOC(Offset, n);
    if (n > 0) {
      const double dmin = -INT_MAX;
      const double dmax =  INT_MAX;
      j = 0;
      /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
      */
      for (i=0; i<lencma; i++) {
	if (cma[i] != nmdi && cma[i] != rmdi) {
	  /* Offset[j] = (int)(cma[i] - avg); --- the BUG : a potential integer overflow */
	  double tmp = cma[i] - avg;
	  if      (tmp < dmin) Offset[j] = -INT_MAX;
	  else if (tmp > dmax) Offset[j] =  INT_MAX;
	  else                 Offset[j] = (int)tmp;
	  j++;
	}
      }
      
      maxval = 0;
      /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
      */
      for (j=0; j<n; j++) {
	int offset = Offset[j];
	if (offset < 0) offset = -offset;
	if (offset > maxval) maxval = offset;
      }
      nbits = 1;
      while (maxval >>= 1) nbits++;
    } /* if (n > 0) */
  }
  else {
    n = 0;
  }

  *Nbits = nbits;
  *Noffset = n;
  *Npacked = RNDUP(n * nbits,N32BITS)/N32BITS;
  *Avg = avg;

  return Offset;
}

/* pcma_1: perform the actual packing of a buffer */

static int 
pcma_1(double avg,
       double nmdi,
       double rmdi,
       const double  cma[],   /* double CMA[] */
       int    lencma,   /* length of the double CMA[] */
       int Offset[],    /* preprocessed offset data */
       int noffset,     /* no. of elements in offset data */
       int nbits,       /* no. of bits involved */
       unsigned int    bitmap[], /* bitmap: 0=positive values, 1=negative, 2=nmdi, 3=rmdi */
       int lenbitmap,  /* length of bitmap[] */
       unsigned int    outbuf[], /* Effective packed output; w/o bitmap though */
       int noutbuf              /* No. of elements in outbuf[] */
       )
{
  int rc = 0;
  int i, j;
  int istart, iend;

  istart = 0;
  iend   = lencma - lencma%b16;
  /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
  */
  for (i=istart; i<iend; i+=b16) {
#if defined(SV2)
    int offset = i >> 4;
#else
    int offset = i/b16;
#endif

    DEFMASK(0) ; DEFMASK(1) ; DEFMASK(2) ; DEFMASK(3) ;
    DEFMASK(4) ; DEFMASK(5) ; DEFMASK(6) ; DEFMASK(7) ;
    DEFMASK(8) ; DEFMASK(9) ; DEFMASK(10); DEFMASK(11);
    DEFMASK(12); DEFMASK(13); DEFMASK(14); DEFMASK(15);

#if defined(SV2)
    bitmap[offset] =   
      mask0  |  mask1 |  mask2 |  mask3 | 
      mask4  |  mask5 |  mask6 |  mask7 |
      mask8  |  mask9 | mask10 | mask11 |
      mask12 | mask13 | mask14 | mask15;
#else
    bitmap[offset] =   
      mask0  +  mask1 +  mask2 +  mask3 + 
      mask4  +  mask5 +  mask6 +  mask7 +
      mask8  +  mask9 + mask10 + mask11 +
      mask12 + mask13 + mask14 + mask15;
#endif
  }

  if (iend < lencma) {
    unsigned int sum = 0;
    int offset = lencma/b16;

    /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
    */
    for (i=iend; i<lencma; i++) {
      int shift = TWO*(i-iend);
      unsigned int mask = IS_ONE_OF(cma[i]);
      sum += mask * PWR2(shift);
    }

    bitmap[offset] = sum;
  }

  /* Pack offsets */

  if (nbits > 0) {
    /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
    */
    for (j=0; j<noffset; j++) {
      if (Offset[j] < 0) Offset[j] = -Offset[j];
    }
  }

  if (nbits > 0) {
    vpack_bits_(&nbits,
		(const unsigned int *)Offset,
		&noffset,
		outbuf,
		&noutbuf,
		&rc);
  }
  else
    rc = 0;

  rc = (rc == noutbuf) ? lencma : -1;

  /* finish: */

  /* Upon successful completion returns no. of CMA-words packed */

  if (rc != lencma) {
    perror("pcma_1: (rc != lencma)");
  }

  return rc; 
}


int
pcma_1_driver(int method, /* ignored */
	      FILE *fp_out,
	      const double  cma[],
              int  lencma,
	      double nmdi,
	      double rmdi,
	      Packbuf *pbuf)
{
  int rc = 0;
  int replen, chunk;
  int count, nw, nwrt, nbits;
  int total_count = 0;
  int lenextra, lenbitmap, lenactive;
  unsigned int *packed_data = NULL;
  int hdrlen = PCMA_HDRLEN;
  double avg = 0;
  int noffset = 0;
  int *Offset = NULL;
  DRHOOK_START(pcma_1_driver);

  nwrt = 0;

  replen = lencma;
  chunk = replen-1;  /* Report w/o length information */

  count = hdrlen;   /* 'PCMA' + method & 3 zero bytes + no_of_packed_bytes + no_of_unpacked + 2 x double MDIs */

  if (replen > 1) {
    Offset = pcma_1_prepare(nmdi, rmdi, 
			    &cma[1], chunk, 
			    &nbits, &noffset, &lenactive, &avg); /* Active no. of words that are non-MDI */
    lenbitmap = RNDUP(chunk,b16)/b16;  /* Bitmap (roundep up and truncated) */
    lenextra = 1 + 1 + 1 * (sizeof(double)/sizeof(int)); /* nbits, noffset, avg */
  }
  else {
    lenextra = lenbitmap = lenactive = 0;
  }
  count += (lenextra + lenbitmap + lenactive);
    
  packed_data = pcma_alloc(pbuf, count);

  packed_data[0] = PCMA;
  packed_data[1] = 1 * MAXSHIFT + 0; /* zero no_of_unpacked means that 4th word, 
					packed_data[3] contains
					report length; ==> no_of_unpacked can now 
					be over 16megawords */
   /* extra + bitmap + packed_data */
  packed_data[2] = (lenextra + lenbitmap + lenactive) * sizeof(*packed_data);
  packed_data[3] = replen;
  memcpy(&packed_data[4],&nmdi,sizeof(nmdi));
  memcpy(&packed_data[6],&rmdi,sizeof(rmdi));

  if (replen > 1) {
    unsigned int *extra  = &packed_data[hdrlen];
    unsigned int *bitmap = &packed_data[hdrlen + lenextra];
    unsigned int *data   = (nbits > 0) ? &packed_data[hdrlen + lenextra + lenbitmap] : NULL;
    extra[0] = nbits;
    extra[1] = noffset;
    memcpy(&extra[2],&avg,sizeof(avg));

    nw = pcma_1(avg, nmdi, rmdi,
		&cma[1], chunk,
		Offset, noffset, nbits,
		bitmap, lenbitmap,
		data, lenactive);
    FREE(Offset);
  }

  if (fp_out) nwrt = fwrite(packed_data, sizeof(*packed_data), count, fp_out);

  total_count += count;

  rc = total_count *  sizeof(*packed_data);

  /* finish: */
  if (!pbuf) FREE(packed_data);

  DRHOOK_END(0);
  return rc;
}


/* ======================================================================================== */


#define POS_BITSET  0
#define NEG_BITSET  1
#define NMDI_BITSET 2
#define RMDI_BITSET 3

/* upcma_1: perform the actual unpacking of a buffer */

static int 
upcma_1(int swp,
	double avg,
	double nmdi,
	double rmdi,
	double  cma[],   /* double CMA[] */
	int    stacma,   /* Starting loop index (>=0) for the CMA[] */
	int    endcma,   /* Ending loop index for the CMA[] */
	int Offset[],    /* preprocessed offset data */
	int noffset,     /* no. of elements in offset data */
	int nbits,       /* no. of bits involved */
	const unsigned int    bitmap[], /* bitmap: 0=positive values, 1=negative, 2=NMDI, 3=RMDI */
	int lenbitmap,  /* length of bitmap[] */
	const unsigned int    outbuf[], /* Effective packed output; w/o bitmap though */
	int noutbuf              /* No. of elements in outbuf[] */
	)
{
  int rc = 0;
  int i;

  if (swp) { /* swap bytes */
    swap4bytes_((unsigned int *)bitmap,&lenbitmap);
    swap4bytes_((unsigned int *)outbuf,&noutbuf);
  }

  if (nbits > 0) {
    /* Unpack offsets */
    vunpack_bits_(&nbits,
		  outbuf,
		  &noutbuf,
		  (unsigned int *)Offset,
		  &noffset,
		  &rc);
  }
  else
    rc = 0;

  if (rc == noffset) {
    int j = 0;

    for (i=0; i<stacma; i++) { /* get the right starting point for "j" */
      int offset = i/b16;
      int shift  = (TWO*i)%N32BITS;
      unsigned int bitset = ((bitmap[offset] & (3U * PWR2(shift))) / PWR2(shift));
      if (bitset == POS_BITSET || bitset == NEG_BITSET) j++;
    }

    if (nbits > 0) {
      /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
      */
      for (i=stacma; i<endcma; i++) {
#if defined(SV2)
        int mask_size = TWO;
        int offset;
        int shift;
        unsigned int bitset;

        offset = (i >> 4) & 0x07FFFFFF;
        shift = mask_size*i;
        shift = (((shift >> 5) & 0x03FFFFFF) << 5) ^ shift;
        bitset = ((bitmap[offset] & (3U << shift)) >> shift) & 0x3;
#else
        int offset = i/b16;
	int shift  = (TWO*i)%N32BITS;
        unsigned int bitset = ((bitmap[offset] & (3U * PWR2(shift))) / PWR2(shift));
#endif

	if (bitset == POS_BITSET || bitset == NEG_BITSET) {
	  /* Important: 
	     In the following stmt, keep "+ avg" for compatibility reasons, 
	     since avg is 0 only for databases created with the bugfixed version */
	  cma[i] = (1 - 2 * (int)bitset) * Offset[j] + avg; 
	  j++;
	}
	else if (bitset == NMDI_BITSET) {
	  cma[i] = nmdi;
	}
	else if (bitset == RMDI_BITSET) {
	  cma[i] = rmdi;
	}
      }
    }
    else {
      /*
#if defined(SV2)
#pragma _CRI ivdep
#endif
      */
      for (i=stacma; i<endcma; i++) {
#if defined(SV2)
        int mask_size = TWO;
        int offset;
        int shift;
        unsigned int bitset;

        offset = (i >> 4) & 0x07FFFFFF;
        shift = mask_size*i;
        shift = (((shift >> 5) & 0x03FFFFFF) << 5) ^ shift;
        bitset = ((bitmap[offset] & (3U << shift)) >> shift) & 0x3;
#else
        int offset = i/b16;
	int shift  = (TWO*i)%N32BITS;
        unsigned int bitset = ((bitmap[offset] & (3U * PWR2(shift))) / PWR2(shift));
#endif
	
	if (bitset == POS_BITSET || bitset == NEG_BITSET) {
	  cma[i] = avg;
	  j++;
	}
	else if (bitset == NMDI_BITSET) {
	  cma[i] = nmdi;
	}
	else if (bitset == RMDI_BITSET) {
	  cma[i] = rmdi;
	}
      }
    }
    
    rc = endcma - stacma;
  }
  else
    rc = -1;

  /* finish: */

  /* Upon successful completion returns no. of uint CMA-words unpacked */

  if (rc != endcma-stacma) {
    perror("upcma_1: (rc != endcma-stacma)");
  }

  if (rc == endcma-stacma && swp && pcma_restore_packed) { /* restore */
    swap4bytes_((unsigned int *)bitmap,&lenbitmap);
    swap4bytes_((unsigned int *)outbuf,&noutbuf);
  }

  return rc; 
}

int
upcma_1_driver(int method,
	       int swp, int can_swp_data,
	       int new_version,
	       const unsigned int packed_data[],
	       int len_packed_data,
	       int msgbytes,
	       double nmdi,
	       double rmdi,
	       FILE *fp_out, 
	       const int idx[], int idxlen,
	       int fill_zeroth_cma,
	       double cma[], int lencma)
{
  int rc = 0;
  int nwrt = 0;
  int replen;
  DRHOOK_START(upcma_1_driver);

  replen = lencma;
  if (fp_out) { /* backward compatibility */
    idxlen = 0;
    fill_zeroth_cma = 1;
  }
  if (fill_zeroth_cma) {
    cma[0] = replen;
    fill_zeroth_cma = 1;
  }
  rc = 1;

  if (replen > 1) {
    int chunk  = replen - 1;
    int stacma = 0;
    int endcma = chunk;
    int lenextra  = 1 + 1 + 1 * (sizeof(double)/sizeof(int));
    int lenbitmap = RNDUP(chunk,b16)/b16;
    int lenactive = len_packed_data - lenbitmap - lenextra;

    const unsigned int *extra  = &packed_data[0];
    const unsigned int *bitmap = &packed_data[lenextra];
    const unsigned int *data;

    int nbits = extra[0];
    int noffset = extra[1];
    int *Offset = NULL;
    double avg;
    const int one = 1;
    double *cma_addr = &cma[fill_zeroth_cma];

    memcpy(&avg,&extra[2],sizeof(avg));

    if (swp) {
      swap4bytes_(&nbits, &one);
      swap4bytes_(&noffset, &one);
      swap8bytes_(&avg, &one);
    }

#ifdef DEBUG
    fprintf(stdout,
	    "upcma_1_driver(method=%d,swp=%d): avg=%.20g, nbits=%d, noffset=%d\n",
	    method, swp, avg, nbits, noffset);
#endif

    if (nbits > 0) {
      ALLOC(Offset, noffset);
      data = &packed_data[lenextra + lenbitmap];
    }
    else {
      Offset = NULL;
      data = NULL;
    }

    STAEND_FIX();

    rc = upcma_1(swp,
		 avg,nmdi,rmdi,
		 cma_addr, stacma, endcma,
		 Offset, noffset, nbits,
		 bitmap, lenbitmap,
		 data, lenactive);

    SWAP_DATA_BACKv2(swp,can_swap_data,cma,stacma,endcma);

    FREE(Offset);
    
    rc = (rc == endcma-stacma) ? replen : rc;
  }

  if (fp_out) nwrt = fwrite(cma, sizeof(double), lencma, fp_out);

  DRHOOK_END(0);
  return rc;
}
