#include <stdio.h>
#include <string.h>
#include "grib_int.h"
#include "grib_decode.h"
#include "dmemory.h"
#include "error.h"

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#if defined(HAVE_LIBSZ)
#if defined(__cplusplus)
extern "C"
{
#endif
#include <szlib.h>
#ifdef __cplusplus
}
#endif

// clang-format off

#define OPTIONS_MASK        (SZ_RAW_OPTION_MASK | SZ_MSB_OPTION_MASK | SZ_NN_OPTION_MASK)

#define PIXELS_PER_BLOCK    (8)
#define PIXELS_PER_SCANLINE (PIXELS_PER_BLOCK*128)

#define MIN_COMPRESS        (0.95)
#define MIN_SIZE            (256)
#endif

#define  Z_SZIP  128

#if  defined (HAVE_LIBSZ) || defined (HAVE_LIBAEC)
#define SetLen3(var, offset, value) ((var[offset+0] = 0xFF & (value >> 16)), \
				     (var[offset+1] = 0xFF & (value >>  8)), \
				     (var[offset+2] = 0xFF & (value      )))
#define SetLen4(var, offset, value) ((var[offset+0] = 0xFF & (value >> 24)), \
				     (var[offset+1] = 0xFF & (value >> 16)), \
				     (var[offset+2] = 0xFF & (value >>  8)), \
				     (var[offset+3] = 0xFF & (value      )))
#endif

// clang-format on

int
gribGetZip(size_t recsize, unsigned char *gribbuffer, size_t *urecsize)
{
  int compress = 0;
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;

  int gribversion = gribVersion(gribbuffer, recsize);

  if (gribversion == 2) return compress;

  long gribrecsize;
  int nerr = grib1Sections(gribbuffer, (long) recsize, &pds, &gds, &bms, &bds, &gribrecsize);
  if (nerr < 0)
    {
      fprintf(stdout, "GRIB message error\n");
      return compress;
    }

  if (nerr > 0)
    {
      fprintf(stdout, "GRIB data corrupted!\n");
      return compress;
    }

  /* bds_len   = BDS_Len; */
  /* bds_nbits = BDS_NumBits; */
  int bds_flag = BDS_Flag;
  /* lspherc   =  bds_flag >> 7; */
  /* lcomplex  = (bds_flag >> 6)&1; */
  int lcompress = (bds_flag >> 4) & 1;

  size_t gribsize = 0;
  if (lcompress)
    {
      compress = BDS_Z;
      if (compress == Z_SZIP) gribsize = (size_t) GET_UINT3(bds[14], bds[15], bds[16]);
    }

  *urecsize = gribsize;

  return compress;
}

int
gribZip(unsigned char *dbuf, long dbufsize, unsigned char *sbuf, long sbufsize)
{
#if !defined(HAVE_LIBSZ)
  static int libszwarn = 1;
#endif
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
  bool llarge = false;

  unsigned gribLen = GET_UINT3(dbuf[4], dbuf[5], dbuf[6]);

  int rec_len = (int) gribLen;

  long gribrecsize;
  int nerr = grib1Sections(dbuf, dbufsize, &pds, &gds, &bms, &bds, &gribrecsize);
  if (nerr < 0)
    {
      fprintf(stdout, "GRIB message error\n");
      return (int) gribrecsize;
    }

  if (nerr > 0)
    {
      fprintf(stdout, "GRIB data corrupted!\n");
      return (int) gribrecsize;
    }

  int bds_zoffset = 12;

  unsigned bds_len = BDS_Len;
  if (gribLen > JP23SET && bds_len <= 120)
    {
      gribLen &= JP23SET;
      gribLen *= 120;
      bds_len = correct_bdslen(bds_len, gribLen, bds - dbuf);
      llarge = true;
      bds_zoffset += 2;
    }

  if (gribLen > JP24SET || llarge) return (int) gribLen;

#if defined(HAVE_LIBSZ)
  {
    int bds_zstart = 14;
    unsigned gribLenOld = 0;
    int bds_head = 11;
    int bds_ext = 0;
    unsigned char *pbuf = NULL;

    int bds_nbits = BDS_NumBits;
    int bds_flag = BDS_Flag;
    int bds_ubits = bds_flag & 15;
    int lspherc = bds_flag >> 7;
    int lcomplex = (bds_flag >> 6) & 1;
    /* lcompress = (bds_flag >> 4)&1; */

    if (bds_nbits != 8 && bds_nbits != 16 && bds_nbits != 24 && bds_nbits != 32)
      {
        static bool linfo = true;
        if (linfo && bds_nbits != 0)
          {
            linfo = false;
            fprintf(stderr, "GRIB szip supports only 8, 16, 24 and 32 bit data!\n");
          }
        return rec_len;
      }

    int bits_per_sample = (bds_nbits == 24) ? 8 : bds_nbits;

    SZ_com_t sz_param; /* szip parameter block */
    sz_param.options_mask = OPTIONS_MASK;
    sz_param.bits_per_pixel = bits_per_sample;
    sz_param.pixels_per_block = PIXELS_PER_BLOCK;
    sz_param.pixels_per_scanline = PIXELS_PER_SCANLINE;

    if (lspherc)
      {
        bds_ext = 4;
        if (lcomplex)
          {
            int jup = bds[15];
            int ioff = (jup + 1) * (jup + 2);
            bds_ext += 3 + 4 * ioff;
          }
      }

    size_t datstart = bds_head + bds_ext;

    size_t datsize = ((((bds_len - datstart) * 8 - bds_ubits) / bds_nbits) * bds_nbits) / 8;

    if (datsize < MIN_SIZE) return rec_len;
    /*
    fprintf(stderr, "%d %d %d %d\n", bds_len, datstart, bds_len - datstart, datsize);
    */
    size_t sourceLen = datsize;
    size_t destLen = sbufsize;

    unsigned char *source = bds + datstart;
    unsigned char *dest = sbuf;

    if (bds_nbits == 24)
      {
        long nelem = sourceLen / 3;
        pbuf = (unsigned char *) Malloc(sourceLen);
        for (long i = 0; i < nelem; ++i)
          {
            pbuf[i] = source[3 * i];
            pbuf[nelem + i] = source[3 * i + 1];
            pbuf[2 * nelem + i] = source[3 * i + 2];
          }
        source = pbuf;
      }

    int status = SZ_BufftoBuffCompress(dest, &destLen, source, sourceLen, &sz_param);
    if (status != SZ_OK)
      {
        if (status == SZ_NO_ENCODER_ERROR)
          Warning("SZ_NO_ENCODER_ERROR code %3d level %3d", PDS_Parameter, PDS_Level2);
        else if (status == SZ_PARAM_ERROR)
          Warning("SZ_PARAM_ERROR code %3d level %3d", PDS_Parameter, PDS_Level2);
        else if (status == SZ_MEM_ERROR)
          Warning("SZ_MEM_ERROR code %3d level %3d", PDS_Parameter, PDS_Level2);
        else if (status == SZ_OUTBUFF_FULL)
          /*Warning("SZ_OUTBUFF_FULL code %3d level %3d", PDS_Parameter, PDS_Level2)*/;
        else
          Warning("SZ ERROR: %d code %3d level %3d", status, PDS_Parameter, PDS_Level2);
      }

    if (pbuf) Free(pbuf);
    /*
    fprintf(stderr, "sourceLen, destLen %d %d\n", sourceLen, destLen);
    */
    if (destLen < MIN_COMPRESS * sourceLen)
      {
        source = bds + datstart + bds_zoffset;
        memcpy(source, dest, destLen);

        /* ----++++ number of unused bits at end of section) */

        BDS_Flag -= bds_ubits;

        gribLenOld = gribLen;

        if (bds_ext)
          for (long i = bds_ext - 1; i >= 0; --i) bds[bds_zoffset + bds_head + i] = bds[bds_head + i];

        /*
        fprintf(stderr, "destLen, datsize, datstart %d %d %d\n", destLen, datsize, datstart);
        */
        /*	memcpy(bds + datstart + bds_zoffset, source, destLen); */
        /*
          fprintf(stderr, "z>>> %d %d %d %d <<<\n", (int) bds[0+datstart+bds_zoffset],
            (int)bds[1+datstart+bds_zoffset], (int)bds[2+datstart+bds_zoffset], (int)bds[3+datstart+bds_zoffset]);
        */
        if (llarge)
          {
            if (gribLenOld % 120)
              {
                fprintf(stderr, "Internal problem, record length not multiple of 120!");
                while (gribLenOld % 120) gribLenOld++;
              }
            // gribLenOld = gribLenOld / (-120);
            // gribLenOld = JP23SET - gribLenOld + 1;

            SetLen3(bds, bds_zstart, gribLenOld);
            SetLen4(bds, bds_zstart + 3, sourceLen);
            SetLen4(bds, bds_zstart + 7, destLen);
          }
        else
          {
            SetLen3(bds, bds_zstart, gribLenOld);
            SetLen3(bds, bds_zstart + 3, sourceLen);
            SetLen3(bds, bds_zstart + 6, destLen);
          }

        int bdsLen = datstart + bds_zoffset + destLen;

        bds[11] = 0;
        bds[12] = 0;

        BDS_Z = Z_SZIP;

        BDS_Flag += 16;
        if ((bdsLen % 2) == 1)
          {
            BDS_Flag += 8;
            bds[bdsLen++] = 0;
          }

        SetLen3(bds, 0, bdsLen);

        gribLen = (bds - dbuf) + bdsLen;

        dbuf[gribLen++] = '7';
        dbuf[gribLen++] = '7';
        dbuf[gribLen++] = '7';
        dbuf[gribLen++] = '7';

        if (llarge)
          {
            long bdslen = gribLen - 4;

            /*
              If a very large product, the section 4 length field holds
              the number of bytes in the product after section 4 upto
              the end of the padding bytes.
              This is a fixup to get round the restriction on product lengths
              due to the count being only 24 bits. It is only possible because
              the (default) rounding for GRIB products is 120 bytes.
            */
            while (gribLen % 120) dbuf[gribLen++] = 0;

            long itemp = gribLen / (-120);
            itemp = JP23SET - itemp + 1;

            SetLen3(dbuf, 4, itemp);

            bdslen = gribLen - bdslen;

            SetLen3(bds, 0, bdslen);
          }
        else
          {
            SetLen3(dbuf, 4, gribLen);
          }
      }
    else
      {
      }
    /*
    fprintf(stderr, "%3d %3d griblen in %6d  out %6d  CR %g   slen %6d dlen %6d  CR %g\n",
            PDS_Parameter, PDS_Level1, gribLenOld, gribLen,
            ((double)gribLenOld)/gribLen, sourceLen, destLen,
            ((double)sourceLen)/destLen);
    */
  }

#else

  UNUSED(sbuf);
  UNUSED(sbufsize);

  if (libszwarn)
    {
      Warning("Compression disabled, szlib not available!");
      libszwarn = 0;
    }
#endif

  if (llarge)
    while (gribLen % 120) dbuf[gribLen++] = 0;
  else
    while (gribLen & 7) dbuf[gribLen++] = 0;

  rec_len = (int) gribLen;

  return rec_len;
}

int
gribUnzip(unsigned char *dbuf, long dbufsize, unsigned char *sbuf, long sbufsize)
{
#if !defined(HAVE_LIBSZ)
  static int libszwarn = 1;
#endif
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
  size_t gribLen = 0;
  size_t destLen, sourceLen;
  enum
  {
    bds_head = 11
  };
  int bds_ext = 0;

  UNUSED(dbufsize);

  long gribrecsize;
  int nerr = grib1Sections(sbuf, sbufsize, &pds, &gds, &bms, &bds, &gribrecsize);
  if (nerr < 0)
    {
      fprintf(stdout, "GRIB message error\n");
      return 0;
    }

  if (nerr > 0)
    {
      fprintf(stdout, "GRIB data corrupted!\n");
      return 0;
    }

  // unsigned bds_len = BDS_Len;
  bool llarge = false;

  int bds_zoffset = 12;
  if (llarge) bds_zoffset += 2;

  int bds_nbits = BDS_NumBits;
  int bds_flag = BDS_Flag;
  int lspherc = bds_flag >> 7;
  int lcomplex = (bds_flag >> 6) & 1;
  /* lcompress = (bds_flag >> 4)&1; */

  if (lspherc)
    {
      if (lcomplex)
        {
          int jup = bds[bds_zoffset + 15];
          int ioff = (jup + 1) * (jup + 2);
          bds_ext = 4 + 3 + 4 * ioff;
        }
      else
        {
          bds_ext = 4;
        }
    }

  size_t datstart = bds_head + (size_t) bds_ext;

  unsigned char *source = bds + datstart + bds_zoffset;
  if (llarge)
    sourceLen = ((size_t) ((bds[21] << 24) + (bds[22] << 16) + (bds[23] << 8) + bds[24]));
  else
    sourceLen = ((size_t) ((bds[20] << 16) + (bds[21] << 8) + bds[22]));

  nerr = grib1Sections(dbuf, sbufsize, &pds, &gds, &bms, &bds, &gribrecsize);
  if (nerr < 0)
    {
      fprintf(stdout, "GRIB message error\n");
      return 0;
    }

  if (nerr > 0)
    {
      fprintf(stdout, "GRIB data corrupted!\n");
      return 0;
    }

  unsigned char *dest = bds + datstart;
  if (llarge)
    destLen = ((size_t) ((bds[17] << 24) + (bds[18] << 16) + (bds[19] << 8) + bds[20]));
  else
    destLen = ((size_t) ((bds[17] << 16) + (bds[18] << 8) + bds[19]));

  BDS_Flag = (unsigned char) (BDS_Flag - 16);

  size_t bdsLen = datstart + destLen;

#if defined(HAVE_LIBSZ)
  {
    int bds_zstart = 14;
    unsigned recLen = GET_UINT3(bds[bds_zstart], bds[bds_zstart + 1], bds[bds_zstart + 2]);

    int bits_per_sample = (bds_nbits == 24) ? 8 : bds_nbits;

    SZ_com_t sz_param; /* szip parameter block */
    sz_param.options_mask = OPTIONS_MASK;
    sz_param.bits_per_pixel = bits_per_sample;
    sz_param.pixels_per_block = PIXELS_PER_BLOCK;
    sz_param.pixels_per_scanline = PIXELS_PER_SCANLINE;

    if (bds_ext)
      for (long i = 0; i < bds_ext; ++i) bds[bds_head + i] = bds[bds_zoffset + bds_head + i];

    /*    fprintf(stderr, "gribUnzip: sourceLen %ld; destLen %ld\n", (long)sourceLen, (long)destLen);
    fprintf(stderr, "gribUnzip: sourceOff %d; destOff %d\n", bds[12], bds[11]);
    fprintf(stderr, "gribUnzip: reclen %d; bdslen %d\n", recLen, bdsLen);
    */

    size_t tmpLen = destLen;

    int status = SZ_BufftoBuffDecompress(dest, &tmpLen, source, sourceLen, &sz_param);
    if (status != SZ_OK)
      {
        if (status == SZ_NO_ENCODER_ERROR)
          Warning("SZ_NO_ENCODER_ERROR code %3d level %3d", PDS_Parameter, PDS_Level2);
        else if (status == SZ_PARAM_ERROR)
          Warning("SZ_PARAM_ERROR code %3d level %3d", PDS_Parameter, PDS_Level2);
        else if (status == SZ_MEM_ERROR)
          Warning("SZ_MEM_ERROR code %3d level %3d", PDS_Parameter, PDS_Level2);
        else if (status == SZ_OUTBUFF_FULL)
          Warning("SZ_OUTBUFF_FULL code %3d level %3d", PDS_Parameter, PDS_Level2);
        else
          Warning("SZ ERROR: %d code %3d level %3d", status, PDS_Parameter, PDS_Level2);
      }
    /*
    fprintf(stderr, "gribUnzip: sl = %ld  dl = %ld   tl = %ld\n",
            (long)sourceLen, (long)destLen,(long) tmpLen);
    */
    if (tmpLen != destLen)
      Warning("unzip size differ: code %3d level %3d  ibuflen %ld ubuflen %ld", PDS_Parameter, PDS_Level2, (long) destLen,
              (long) tmpLen);

    if (bds_nbits == 24)
      {
        long nelem = tmpLen / 3;
        unsigned char *pbuf = (unsigned char *) Malloc(tmpLen);
        for (long i = 0; i < nelem; ++i)
          {
            pbuf[3 * i] = dest[i];
            pbuf[3 * i + 1] = dest[nelem + i];
            pbuf[3 * i + 2] = dest[2 * nelem + i];
          }
        memcpy(dest, pbuf, tmpLen);
        Free(pbuf);
      }

    int bds_ubits = BDS_Flag & 15;
    BDS_Flag -= bds_ubits;

    if ((bdsLen % 2) == 1)
      {
        BDS_Flag += 8;
        bds[bdsLen++] = 0;
      }

    SetLen3(bds, 0, bdsLen);

    gribLen = (bds - dbuf) + bdsLen;

    dbuf[gribLen++] = '7';
    dbuf[gribLen++] = '7';
    dbuf[gribLen++] = '7';
    dbuf[gribLen++] = '7';

    if (llarge)
      {
        long itemp;
        bdsLen = gribLen - 4;
        /*
          If a very large product, the section 4 length field holds
          the number of bytes in the product after section 4 upto
          the end of the padding bytes.
          This is a fixup to get round the restriction on product lengths
          due to the count being only 24 bits. It is only possible because
          the (default) rounding for GRIB products is 120 bytes.
        */
        while (gribLen % 120) dbuf[gribLen++] = 0;

        if (gribLen != (size_t) recLen) fprintf(stderr, "Internal problem, recLen and gribLen differ!\n");

        itemp = gribLen / (-120);
        itemp = JP23SET - itemp + 1;

        SetLen3(dbuf, 4, itemp);

        bdsLen = gribLen - bdsLen;

        SetLen3(bds, 0, bdsLen);
      }
    else
      {
        SetLen3(dbuf, 4, recLen);
      }
    /*
    fprintf(stderr, "recLen, gribLen, bdsLen %d %d %d\n", recLen, gribLen, bdsLen);
    */
    if (llarge)
      while (gribLen % 120) dbuf[gribLen++] = 0;
    else
      while (gribLen & 7) dbuf[gribLen++] = 0;
    /*
    fprintf(stderr, "recLen, gribLen, bdsLen %d %d %d\n", recLen, gribLen, bdsLen);
    */
  }
#else
  UNUSED(bds_nbits);
  UNUSED(sourceLen);
  UNUSED(source);
  UNUSED(bdsLen);
  UNUSED(dest);

  if (libszwarn)
    {
      Warning("Decompression disabled, szlib not available!");
      libszwarn = 0;
    }
#endif

  return (int) gribLen;
}
