#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <float.h>
#include "grib2.h"

void misspack(g2float *fld,g2int ndpts,g2int idrsnum,g2int *idrstmpl,
              unsigned char *cpack, g2int *lcpack)
//$$$  SUBPROGRAM DOCUMENTATION BLOCK
//                .      .    .                                       .
// SUBPROGRAM:    misspack
//   PRGMMR: Gilbert          ORG: W/NP11    DATE: 2000-06-21
//
// ABSTRACT: This subroutine packs up a data field using a complex
//   packing algorithm as defined in the GRIB2 documentation.  It
//   supports GRIB2 complex packing templates with or without
//   spatial differences (i.e. DRTs 5.2 and 5.3).
//   It also fills in GRIB2 Data Representation Template 5.2 or 5.3
//   with the appropriate values.
//   This version assumes that Missing Value Management is being used and that
//   1 or 2 missing values appear in the data.
//
// PROGRAM HISTORY LOG:
// 2000-06-21  Gilbert
//
// USAGE:    misspack(g2float *fld,g2int ndpts,g2int idrsnum,g2int *idrstmpl,
//                    unsigned char *cpack, g2int *lcpack)
//   INPUT ARGUMENT LIST:
//     fld[]    - Contains the data values to pack
//     ndpts    - The number of data values in array fld[]
//     idrsnum  - Data Representation Template number 5.N
//                Must equal 2 or 3.
//     idrstmpl - Contains the array of values for Data Representation
//                Template 5.2 or 5.3
//                [0] = Reference value - ignored on input
//                [1] = Binary Scale Factor
//                [2] = Decimal Scale Factor
//                    .
//                    .
//                [6] = Missing value management
//                [7] = Primary missing value
//                [8] = Secondary missing value
//                    .
//                    .
//               [16] = Order of Spatial Differencing  ( 1 or 2 )
//                    .
//                    .
//
//   OUTPUT ARGUMENT LIST:
//     idrstmpl - Contains the array of values for Data Representation
//                Template 5.3
//                [0] = Reference value - set by misspack routine.
//                [1] = Binary Scale Factor - unchanged from input
//                [2] = Decimal Scale Factor - unchanged from input
//                    .
//                    .
//     cpack    - The packed data field (character*1 array)
//     *lcpack   - length of packed field cpack(). (or -1 in case of error)
//
// REMARKS: None
//
// ATTRIBUTES:
//   LANGUAGE: C
//   MACHINE:
//
//$$$
{

      g2int  *ifld, *ifldmiss, *jfld;
      g2int  *jmin, *jmax, *lbit;
      const g2int zero=0;
      g2int  *gref, *gwidth, *glen;
      g2int  glength, grpwidth;
      g2int  i, n, iofst, ival1, ival2, isd, minsd, nbitsd = 0;
      g2int  nbitsgref, left, iwmax, ngwidthref, nbitsgwidth, ilmax;
      g2int  nglenref, nglenlast, nbitsglen /* , ij */;
      g2int  j, missopt, nonmiss, itemp, maxorig, nbitorig, miss1, miss2;
      g2int  ngroups, ng, num0, num1, num2;
      g2int  imax, lg, mtemp, ier = 0, igmax;
      g2int  kfildo, minpk, inc, maxgrps, ibit, jbit, kbit, novref, lbitref;
      g2float  rmissp, rmisss, bscale, dscale, rmin, rmax, temp;
      const g2int simple_alg = 0;
      const g2float alog2=0.69314718f;       //  ln(2.0)
      const g2int one=1;

      bscale=(float)int_power(2.0,-idrstmpl[1]);
      dscale=(float)int_power(10.0,idrstmpl[2]);
      missopt=idrstmpl[6];
      if ( missopt != 1 && missopt != 2 ) {
         printf("misspack: Unrecognized option.\n");
         *lcpack=-1;
         return;
      }
      else {    //  Get missing values
         rdieee(idrstmpl+7,&rmissp,1);
         if (missopt == 2) rdieee(idrstmpl+8,&rmisss,1);
      }
//
//  Find min value of non-missing values in the data,
//  AND set up missing value mapping of the field.
//
      ifldmiss = calloc(ndpts,sizeof(g2int));
      if( ifldmiss == NULL )
      {
          *lcpack = -1;
          return;
      }
      rmin=FLT_MAX;
      rmax=-FLT_MAX;
      if ( missopt ==  1 ) {        // Primary missing value only
         for ( j=0; j<ndpts; j++) {
           if (fld[j] == rmissp) {
              ifldmiss[j]=1;
           }
           else {
              ifldmiss[j]=0;
              if (fld[j] < rmin) rmin=fld[j];
              if (fld[j] > rmax) rmax=fld[j];
           }
         }
      }
      if ( missopt ==  2 ) {        // Primary and secondary missing values
         for ( j=0; j<ndpts; j++ ) {
           if (fld[j] == rmissp) {
              ifldmiss[j]=1;
           }
           else if (fld[j] == rmisss) {
              ifldmiss[j]=2;
           }
           else {
              ifldmiss[j]=0;
              if (fld[j] < rmin) rmin=fld[j];
              if (fld[j] > rmax) rmax=fld[j];
           }
         }
      }

      if( !(floor((double)rmin*dscale) >= -FLT_MAX && floor((double)rmin*dscale) <= FLT_MAX) )
      {
         fprintf(stderr,
                    "Scaled min value not representable on IEEE754 "
                    "single precision float\n");
        *lcpack = -1;
        free(ifldmiss);
        return;
      }
      if (idrstmpl[1] == 0)
      {
        double max_diff = RINT(rmax*dscale) - RINT(rmin*dscale);
        if( !(max_diff >= 0 && max_diff <= INT_MAX) )
        {
            fprintf(stderr,
                        "Difference of scaled max value with scaled min value "
                        "not representable on int \n");
            *lcpack = -1;
            free(ifldmiss);
            return;
        }
      }
      else
      {
        double max_diff = RINT(rmax*dscale - rmin*dscale) * bscale;
        if( !(max_diff >= 0 && max_diff <= INT_MAX) )
        {
            fprintf(stderr,
                        "Difference of scaled max value with scaled min value "
                        "not representable on int \n");
            *lcpack = -1;
            free(ifldmiss);
            return;
        }
      }
//
//  Allocate work arrays:
//  Note: -ifldmiss[j],j=0,ndpts-1 is a map of original field indicating
//         which of the original data values
//         are primary missing (1), secondary missing (2) or non-missing (0).
//        -jfld[j],j=0,nonmiss-1 is a subarray of just the non-missing values
//         from the original field.
//
      //if (rmin != rmax) {
        iofst=0;
        ifld = calloc(ndpts,sizeof(g2int));
        jfld = calloc(ndpts,sizeof(g2int));
        gref = calloc(ndpts,sizeof(g2int));
        gwidth = calloc(ndpts,sizeof(g2int));
        glen = calloc(ndpts,sizeof(g2int));
        if( ifld == NULL || jfld == NULL || gref == NULL || gwidth == NULL ||
            glen == NULL )
        {
            free(ifld);
            free(jfld);
            free(ifldmiss);
            free(gref);
            free(gwidth);
            free(glen);
            *lcpack = -1;
            return;
        }
        //
        //  Scale original data
        //
        nonmiss=0;
        if (idrstmpl[1] == 0) {        //  No binary scaling
           rmin=(g2float)RINT(rmin*dscale);
           for ( j=0; j<ndpts; j++) {
              if (ifldmiss[j] == 0) {
                jfld[nonmiss]=(g2int)(RINT(fld[j]*dscale)-rmin);
                nonmiss++;
              }
           }
        }
        else {                             //  Use binary scaling factor
           rmin=rmin*dscale;
           //rmax=rmax*dscale;
           for ( j=0; j<ndpts; j++ ) {
              if (ifldmiss[j] == 0) {
                jfld[nonmiss]=(g2int)RINT(((fld[j]*dscale)-rmin)*bscale);
                nonmiss++;
              }
           }
        }
        //
        //  Calculate Spatial differences, if using DRS Template 5.3
        //
        if (idrsnum == 3) {        // spatial differences
           if (idrstmpl[16]!=1 && idrstmpl[16]!=2) idrstmpl[16]=2;
           if (idrstmpl[16] == 1) {      // first order
              ival1=jfld[0];
              for ( j=nonmiss-1; j>0; j--)
                 jfld[j]=jfld[j]-jfld[j-1];
              jfld[0]=0;
           }
           else if (idrstmpl[16] == 2) {      // second order
              ival1=jfld[0];
              ival2=jfld[1];
              for ( j=nonmiss-1; j>1; j--)
                 jfld[j]=jfld[j]-(2*jfld[j-1])+jfld[j-2];
              jfld[0]=0;
              jfld[1]=0;
           }
           //
           //  subtract min value from spatial diff field
           //
           isd=idrstmpl[16];
           minsd=jfld[isd];
           for ( j=isd; j<nonmiss; j++ ) if ( jfld[j] < minsd ) minsd=jfld[j];
           for ( j=isd; j<nonmiss; j++ ) jfld[j]=jfld[j]-minsd;
           //
           //   find num of bits need to store minsd and add 1 extra bit
           //   to indicate sign
           //
           temp=(float)(log((double)(abs(minsd)+1))/alog2);
           nbitsd=(g2int)ceil(temp)+1;
           //
           //   find num of bits need to store ifld[0] ( and ifld[1]
           //   if using 2nd order differencing )
           //
           maxorig=ival1;
           if (idrstmpl[16]==2 && ival2>ival1) maxorig=ival2;
           temp=(float)(log((double)(maxorig+1))/alog2);
           nbitorig=(g2int)ceil(temp)+1;
           if (nbitorig > nbitsd) nbitsd=nbitorig;
           //   increase number of bits to even multiple of 8 ( octet )
           if ( (nbitsd%8) != 0) nbitsd=nbitsd+(8-(nbitsd%8));
           //
           //  Store extra spatial differencing info into the packed
           //  data section.
           //
           if (nbitsd != 0) {
              //   pack first original value
              if (ival1 >= 0) {
                 sbit(cpack,&ival1,iofst,nbitsd);
                 iofst=iofst+nbitsd;
              }
              else {
                 sbit(cpack,&one,iofst,1);
                 iofst=iofst+1;
                 itemp=abs(ival1);
                 sbit(cpack,&itemp,iofst,nbitsd-1);
                 iofst=iofst+nbitsd-1;
              }
              if (idrstmpl[16] == 2) {
               //  pack second original value
                 if (ival2 >= 0) {
                    sbit(cpack,&ival2,iofst,nbitsd);
                    iofst=iofst+nbitsd;
                 }
                 else {
                    sbit(cpack,&one,iofst,1);
                    iofst=iofst+1;
                    itemp=abs(ival2);
                    sbit(cpack,&itemp,iofst,nbitsd-1);
                    iofst=iofst+nbitsd-1;
                 }
              }
              //  pack overall min of spatial differences
              if (minsd >= 0) {
                 sbit(cpack,&minsd,iofst,nbitsd);
                 iofst=iofst+nbitsd;
              }
              else {
                 sbit(cpack,&one,iofst,1);
                 iofst=iofst+1;
                 itemp=abs(minsd);
                 sbit(cpack,&itemp,iofst,nbitsd-1);
                 iofst=iofst+nbitsd-1;
              }
           }
         //print *,'SDp ',ival1,ival2,minsd,nbitsd
        }       //  end of spatial diff section
        //
        //  Expand non-missing data values to original grid.
        //
        miss1=jfld[0];
        for ( j=0; j<nonmiss; j++) if (jfld[j] < miss1) miss1 = jfld[j];
        if( miss1 <= INT_MIN + 1 )
        {
            // E. Rouault: no idea if this is correct, but avoids integer
            // wrap over
            miss1++;
            miss2 = miss1 + 1;
        }
        else
        {
            miss1--;
            miss2=miss1-1;
        }
        n=0;
        for ( j=0; j<ndpts; j++) {
           if ( ifldmiss[j] == 0 ) {
              ifld[j]=jfld[n];
              n++;
           }
           else if ( ifldmiss[j] == 1 ) {
              ifld[j]=miss1;
           }
           else if ( ifldmiss[j] == 2 ) {
              ifld[j]=miss2;
           }
        }
        //
        //   Determine Groups to be used.
        //
        if ( simple_alg == 1 ) {
           //  set group length to 10 :  calculate number of groups
           //  and length of last group
           ngroups=ndpts/10;
           for (j=0;j<ngroups;j++) glen[j]=10;
           itemp=ndpts%10;
           if (itemp != 0) {
              ngroups++;
              glen[ngroups-1]=itemp;
           }
        }
        else {
           // Use Dr. Glahn's algorithm for determining grouping.
           //
           kfildo=6;
           minpk=10;
           inc=1;
           maxgrps=(ndpts/minpk)+1;
           jmin = calloc(maxgrps,sizeof(g2int));
           jmax = calloc(maxgrps,sizeof(g2int));
           lbit = calloc(maxgrps,sizeof(g2int));
           pack_gp(&kfildo,ifld,&ndpts,&missopt,&minpk,&inc,&miss1,&miss2,
                        jmin,jmax,lbit,glen,&maxgrps,&ngroups,&ibit,&jbit,
                        &kbit,&novref,&lbitref,&ier);
           //printf("SAGier = %d %d %d %d %d %d\n",ier,ibit,jbit,kbit,novref,lbitref);
           for ( ng=0; ng<ngroups; ng++) glen[ng]=glen[ng]+novref;
           free(jmin);
           free(jmax);
           free(lbit);
           if( ier != 0 )
           {
                free(ifld);
                free(jfld);
                free(ifldmiss);
                free(gref);
                free(gwidth);
                free(glen);
                *lcpack = -1;
                return;
           }
        }
        //
        //  For each group, find the group's reference value (min)
        //  and the number of bits needed to hold the remaining values
        //
        n=0;
        for ( ng=0; ng<ngroups; ng++) {
           //  how many of each type?
           num0=num1=num2=0;
           for (j=n; j<n+glen[ng]; j++) {
               if (ifldmiss[j] == 0 ) num0++;
               if (ifldmiss[j] == 1 ) num1++;
               if (ifldmiss[j] == 2 ) num2++;
           }
           if ( num0 == 0 ) {      // all missing values
              if ( num1 == 0 ) {       // all secondary missing
                gref[ng]=-2;
                gwidth[ng]=0;
              }
              else if ( num2 == 0 ) {       // all primary missing
                gref[ng]=-1;
                gwidth[ng]=0;
              }
              else {                          // both primary and secondary
                gref[ng]=0;
                gwidth[ng]=1;
              }
           }
           else {                      // contains some non-missing data
             //    find max and min values of group
             gref[ng]=2147483647;
             imax=-2147483647;
             j=n;
             for ( lg=0; lg<glen[ng]; lg++ ) {
                if ( ifldmiss[j] == 0 ) {
                  if (ifld[j] < gref[ng]) gref[ng]=ifld[j];
                  if (ifld[j] > imax) imax=ifld[j];
                }
                j++;
             }
             if (missopt == 1) imax=imax+1;
             if (missopt == 2) imax=imax+2;
             //   calc num of bits needed to hold data
             if ( gref[ng] != imax ) {
                temp=(float)(log((double)(imax-gref[ng]+1))/alog2);
                gwidth[ng]=(g2int)ceil(temp);
             }
             else {
                gwidth[ng]=0;
             }
           }
           //   Subtract min from data
           j=n;
           mtemp=(g2int)int_power(2.,gwidth[ng]);
           for ( lg=0; lg<glen[ng]; lg++ ) {
              if (ifldmiss[j] == 0)            // non-missing
                 ifld[j]=ifld[j]-gref[ng];
              else if (ifldmiss[j] == 1)         // primary missing
                 ifld[j]=mtemp-1;
              else if (ifldmiss[j] == 2)         // secondary missing
                 ifld[j]=mtemp-2;

              j++;
           }
           //   increment fld array counter
           n=n+glen[ng];
        }
        //
        //  Find max of the group references and calc num of bits needed
        //  to pack each groups reference value, then
        //  pack up group reference values
        //
        //printf(" GREFS: ");
        //for (j=0;j<ngroups;j++) printf(" %d",gref[j]); printf("\n");
        igmax=gref[0];
        for (j=1;j<ngroups;j++) if (gref[j] > igmax) igmax=gref[j];
        if (missopt == 1) igmax=igmax+1;
        if (missopt == 2) igmax=igmax+2;
        if (igmax != 0) {
           temp=(float)(log((double)(igmax+1))/alog2);
           nbitsgref=(g2int)ceil(temp);
           if( nbitsgref < 0 || nbitsgref >= 31 )
           {
                free(ifld);
                free(jfld);
                free(ifldmiss);
                free(gref);
                free(gwidth);
                free(glen);
                *lcpack = -1;
                return;
           }
           // reset the ref values of any "missing only" groups.
           mtemp=(g2int)int_power(2.,nbitsgref);
           for ( j=0; j<ngroups; j++ ) {
               if (gref[j] == -1) gref[j]=mtemp-1;
               if (gref[j] == -2) gref[j]=mtemp-2;
           }
           sbits(cpack,gref,iofst,nbitsgref,0,ngroups);
           itemp=nbitsgref*ngroups;
           iofst=iofst+itemp;
           //         Pad last octet with Zeros, if necessary,
           if ( (itemp%8) != 0) {
              left=8-(itemp%8);
              sbit(cpack,&zero,iofst,left);
              iofst=iofst+left;
           }
        }
        else {
           nbitsgref=0;
        }
        //
        //  Find max/min of the group widths and calc num of bits needed
        //  to pack each groups width value, then
        //  pack up group width values
        //
        //write(77,*)'GWIDTHS: ',(gwidth(j),j=1,ngroups)
        iwmax=gwidth[0];
        ngwidthref=gwidth[0];
        for (j=1;j<ngroups;j++) {
           if (gwidth[j] > iwmax) iwmax=gwidth[j];
           if (gwidth[j] < ngwidthref) ngwidthref=gwidth[j];
        }
        if (iwmax != ngwidthref) {
           temp=(float)(log((double)(iwmax-ngwidthref+1))/alog2);
           nbitsgwidth=(g2int)ceil(temp);
           for ( i=0; i<ngroups; i++) gwidth[i]=gwidth[i]-ngwidthref;
           sbits(cpack,gwidth,iofst,nbitsgwidth,0,ngroups);
           itemp=nbitsgwidth*ngroups;
           iofst=iofst+itemp;
           //         Pad last octet with Zeros, if necessary,
           if ( (itemp%8) != 0) {
              left=8-(itemp%8);
              sbit(cpack,&zero,iofst,left);
              iofst=iofst+left;
           }
        }
        else {
           nbitsgwidth=0;
           for (i=0;i<ngroups;i++) gwidth[i]=0;
        }
        //
        //  Find max/min of the group lengths and calc num of bits needed
        //  to pack each groups length value, then
        //  pack up group length values
        //
        //printf(" GLENS: ");
        //for (j=0;j<ngroups;j++) printf(" %d",glen[j]); printf("\n");
        ilmax=glen[0];
        nglenref=glen[0];
        for (j=1;j<ngroups-1;j++) {
           if (glen[j] > ilmax) ilmax=glen[j];
           if (glen[j] < nglenref) nglenref=glen[j];
        }
        nglenlast=glen[ngroups-1];
        if (ilmax != nglenref) {
           temp=(float)(log((double)(ilmax-nglenref+1))/alog2);
           nbitsglen=(g2int)ceil(temp);
           for ( i=0; i<ngroups-1; i++) glen[i]=glen[i]-nglenref;
           sbits(cpack,glen,iofst,nbitsglen,0,ngroups);
           itemp=nbitsglen*ngroups;
           iofst=iofst+itemp;
           //         Pad last octet with Zeros, if necessary,
           if ( (itemp%8) != 0) {
              left=8-(itemp%8);
              sbit(cpack,&zero,iofst,left);
              iofst=iofst+left;
           }
        }
        else {
           nbitsglen=0;
           for (i=0;i<ngroups;i++) glen[i]=0;
        }
        //
        //  For each group, pack data values
        //
        //write(77,*)'IFLDS: ',(ifld(j),j=1,ndpts)
        n=0;
        // ij=0;
        for ( ng=0; ng<ngroups; ng++) {
           glength=glen[ng]+nglenref;
           if (ng == (ngroups-1) ) glength=nglenlast;
           grpwidth=gwidth[ng]+ngwidthref;
       //write(77,*)'NGP ',ng,grpwidth,glength,gref(ng)
           if ( grpwidth != 0 ) {
              sbits(cpack,ifld+n,iofst,grpwidth,0,glength);
              iofst=iofst+(grpwidth*glength);
           }
       //  do kk=1,glength
       //     ij=ij+1
       //write(77,*)'SAG ',ij,fld(ij),ifld(ij),gref(ng),bscale,rmin,dscale
       //  enddo
           n=n+glength;
        }
        //         Pad last octet with Zeros, if necessary,
        if ( (iofst%8) != 0) {
           left=8-(iofst%8);
           sbit(cpack,&zero,iofst,left);
           iofst=iofst+left;
        }
        *lcpack=iofst/8;
        //
        free(ifld);
        free(jfld);
        free(ifldmiss);
        free(gref);
        free(gwidth);
        free(glen);
      //}
      //else {          //   Constant field ( max = min )
      //  nbits=0;
      //  *lcpack=0;
      //  nbitsgref=0;
      //  ngroups=0;
      //}

//
//  Fill in ref value and number of bits in Template 5.2
//
      mkieee(&rmin,idrstmpl+0,1);   // ensure reference value is IEEE format
      idrstmpl[3]=nbitsgref;
      idrstmpl[4]=0;         // original data were reals
      idrstmpl[5]=1;         // general group splitting
      idrstmpl[9]=ngroups;          // Number of groups
      idrstmpl[10]=ngwidthref;       // reference for group widths
      idrstmpl[11]=nbitsgwidth;      // num bits used for group widths
      idrstmpl[12]=nglenref;         // Reference for group lengths
      idrstmpl[13]=1;                // length increment for group lengths
      idrstmpl[14]=nglenlast;        // True length of last group
      idrstmpl[15]=nbitsglen;        // num bits used for group lengths
      if (idrsnum == 3) {
         idrstmpl[17]=nbitsd/8;      // num bits used for extra spatial
                                     // differencing values
      }

}
