#include "grib_encode.h"
#include "grib_int.h"
#include "dmemory.h"
#include "error.h"

// GRIB block 0 - indicator block
static void
encodeIS(GRIBPACK *lGrib, long *gribLen)
{
  long z;
  // z = *gribLen;

  lGrib[0] = 'G';
  lGrib[1] = 'R';
  lGrib[2] = 'I';
  lGrib[3] = 'B';

  // lGrib[4]-lGrib[6] contains full length of grib record.
  // included before finished CODEGB

  z = 7;
  Put1Byte(1);  // grib version
  z = 8;

  *gribLen = z;
}

// GRIB block 5 - end block
static void
encodeES(GRIBPACK *lGrib, long *gribLen, long bdsstart)
{
  long z = *gribLen;

  lGrib[z++] = '7';
  lGrib[z++] = '7';
  lGrib[z++] = '7';
  lGrib[z++] = '7';

  if (z > JP24SET)
    {
      long bdslen = z - 4;
      // fprintf(stderr, "Abort: GRIB record too large (max = %d)!\n", JP23SET);
      // exit(1);
      /*
        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 (z % 120) lGrib[z++] = 0;

      if (z > JP23SET * 120)
        {
          fprintf(stderr, "Abort: GRIB1 record too large (size = %ld; max = %d)!\n", z, JP23SET * 120);
          exit(1);
        }

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

      lGrib[4] = (GRIBPACK) (itemp >> 16);
      lGrib[5] = (GRIBPACK) (itemp >> 8);
      lGrib[6] = (GRIBPACK) itemp;

      bdslen = z - bdslen;
      lGrib[bdsstart] = (GRIBPACK) (bdslen >> 16);
      lGrib[bdsstart + 1] = (GRIBPACK) (bdslen >> 8);
      lGrib[bdsstart + 2] = (GRIBPACK) bdslen;
    }
  else
    {
      lGrib[4] = (GRIBPACK) (z >> 16);
      lGrib[5] = (GRIBPACK) (z >> 8);
      lGrib[6] = (GRIBPACK) z;

      while (z % 8) lGrib[z++] = 0;
    }

  *gribLen = z;
}

// GRIB block 1 - product definition block.

#define DWD_extension_253_len 38
#define DWD_extension_254_len 26
#define ECMWF_extension_1_len 24
#define MPIM_extension_1_len 18

static long
getLocalExtLen(int *isec1)
{
  long extlen = 0;

  if (ISEC1_LocalFLag)
    {
      if (ISEC1_CenterID == 78 || ISEC1_CenterID == 215 || ISEC1_CenterID == 250)
        {
          if (isec1[36] == 254)
            extlen = DWD_extension_254_len;
          else if (isec1[36] == 253)
            extlen = DWD_extension_253_len;
        }
      else if (ISEC1_CenterID == 98)
        {
          if (isec1[36] == 1) extlen = ECMWF_extension_1_len;
        }
      else if (ISEC1_CenterID == 252)
        {
          if (isec1[36] == 1) extlen = MPIM_extension_1_len;
        }
    }

  return extlen;
}

static long
getPdsLen(int *isec1)
{
  long pdslen = 28;

  pdslen += getLocalExtLen(isec1);

  return pdslen;
}

static void
encodePDS_DWD_local_Extension_254(GRIBPACK *lGrib, long *zs, int *isec1)
{
  long z = *zs;

  const long localextlen = getLocalExtLen(isec1);
  for (long i = 0; i < localextlen - 2; i++) Put1Byte(isec1[24 + i]);

  int isvn = isec1[49] << 15 | isec1[48];  // DWD experiment identifier
  Put2Byte(isvn);                          // DWD run type (0=main, 2=ass, 3=test)

  *zs = z;
}

static void
encodePDS_DWD_local_Extension_253(GRIBPACK *lGrib, long *zs, int *isec1)
{
  long z = *zs;

  const long localextlen = DWD_extension_254_len;
  for (long i = 0; i < localextlen - 2; i++) Put1Byte(isec1[24 + i]);

  int isvn = isec1[49] << 15 | isec1[48]; /* DWD experiment identifier    */
  Put2Byte(isvn);                         /* DWD run type (0=main, 2=ass, 3=test) */
  Put1Byte(isec1[50]);                    /* 55 User id, specified by table       */
  Put2Byte(isec1[51]);                    /* 56 Experiment identifier             */
  Put2Byte(isec1[52]);                    /* 58 Ensemble identification by table  */
  Put2Byte(isec1[53]);                    /* 60 Number of ensemble members        */
  Put2Byte(isec1[54]);                    /* 62 Actual number of ensemble member  */
  Put1Byte(isec1[55]);                    /* 64 Model major version number        */
  Put1Byte(isec1[56]);                    /* 65 Model minor version number        */
  Put1Byte(0);                            /* 66 Blank for even buffer length      */

  *zs = z;
}

static void
encodePDS_ECMWF_local_Extension_1(GRIBPACK *lGrib, long *zs, int *isec1)
{
  long z = *zs;

  const long localextlen = getLocalExtLen(isec1);
  for (long i = 0; i < localextlen - 12; i++) Put1Byte(isec1[24 + i]);
  /* 12 bytes explicitly encoded below:         */
  Put1Byte(isec1[36]); /* ECMWF local GRIB use definition identifier */
                       /*    1=MARS labelling or ensemble fcst. data */
  Put1Byte(isec1[37]); /* Class                                      */
  Put1Byte(isec1[38]); /* Type                                       */
  Put2Byte(isec1[39]); /* Stream                                     */

  // Version number or experiment identifier
  Put1Byte(((unsigned char *) &isec1[40])[0]);
  Put1Byte(((unsigned char *) &isec1[40])[1]);
  Put1Byte(((unsigned char *) &isec1[40])[2]);
  Put1Byte(((unsigned char *) &isec1[40])[3]);

  Put1Byte(isec1[41]); /* Ensemble forecast number                   */
  Put1Byte(isec1[42]); /* Total number of forecasts in ensemble      */
  Put1Byte(0);         /* (Spare)                                    */

  *zs = z;
}

static void
encodePDS_MPIM_local_Extension_1(GRIBPACK *lGrib, long *zs, int *isec1)
{
  long z = *zs;

  const long localextlen = getLocalExtLen(isec1);
  for (long i = 0; i < localextlen - 6; i++) Put1Byte(isec1[24 + i]);
  /* 6 bytes explicitly encoded below:          */
  Put1Byte(isec1[36]); /* MPIM local GRIB use definition identifier  */
                       /*    (extension identifier)                  */
  Put1Byte(isec1[37]); /* type of ensemble forecast                  */
  Put2Byte(isec1[38]); /* individual ensemble member                 */
  Put2Byte(isec1[39]); /* number of forecasts in ensemble            */

  *zs = z;
}

// GRIB BLOCK 1 - PRODUCT DESCRIPTION SECTION
static void
encodePDS(GRIBPACK *lpds, long pdsLen, int *isec1)
{
  GRIBPACK *lGrib = lpds;
  long z = 0;
  int ival;

  int century = ISEC1_Century;
  int year = ISEC1_Year;

  if (century < 0)
    {
      century = -century;
      year = -year;
    }

  Put3Byte(pdsLen);               /*  0 Length of Block 1        */
  Put1Byte(ISEC1_CodeTable);      /*  3 Local table number       */
  Put1Byte(ISEC1_CenterID);       /*  4 Identification of centre */
  Put1Byte(ISEC1_ModelID);        /*  5 Identification of model  */
  Put1Byte(ISEC1_GridDefinition); /*  6 Grid definition          */
  Put1Byte(ISEC1_Sec2Or3Flag);    /*  7 Block 2 included         */
  Put1Byte(ISEC1_Parameter);      /*  8 Parameter Code           */
  Put1Byte(ISEC1_LevelType);      /*  9 Type of level            */
  // clang-format off
  if ( (ISEC1_LevelType !=  20) &&
       (ISEC1_LevelType != GRIB1_LTYPE_99)           &&
       (ISEC1_LevelType != GRIB1_LTYPE_ISOBARIC)     &&
       (ISEC1_LevelType != GRIB1_LTYPE_ISOBARIC_PA)  &&
       (ISEC1_LevelType != GRIB1_LTYPE_ALTITUDE)     &&
       (ISEC1_LevelType != GRIB1_LTYPE_HEIGHT)       &&
       (ISEC1_LevelType != GRIB1_LTYPE_SIGMA)        &&
       (ISEC1_LevelType != GRIB1_LTYPE_HYBRID)       &&
       (ISEC1_LevelType != GRIB1_LTYPE_LANDDEPTH)    &&
       (ISEC1_LevelType != GRIB1_LTYPE_ISENTROPIC)   &&
       (ISEC1_LevelType != 115) &&
       (ISEC1_LevelType != 117) &&
       (ISEC1_LevelType != 125) &&
       (ISEC1_LevelType != 127) &&
       (ISEC1_LevelType != 160) &&
       (ISEC1_LevelType != 210) )
    {
      Put1Byte(ISEC1_Level1);
      Put1Byte(ISEC1_Level2);
    }
  else
    {
      Put2Byte(ISEC1_Level1);     /* 10 Level                    */    
    }
  // clang-format on

  Put1Int(year);          /* 12 Year of Century          */
  Put1Byte(ISEC1_Month);  /* 13 Month                    */
  Put1Byte(ISEC1_Day);    /* 14 Day                      */
  Put1Byte(ISEC1_Hour);   /* 15 Hour                     */
  Put1Byte(ISEC1_Minute); /* 16 Minute                   */

  Put1Byte(ISEC1_TimeUnit); /* 17 Time unit                */
  if (ISEC1_TimeRange == 10)
    {
      Put1Byte(ISEC1_TimePeriod1);
      Put1Byte(ISEC1_TimePeriod2);
    }
  else if (ISEC1_TimeRange == 113 || ISEC1_TimeRange == 0)
    {
      Put1Byte(ISEC1_TimePeriod1);
      Put1Byte(0);
    }
  else if (ISEC1_TimeRange == 5 || ISEC1_TimeRange == 4 || ISEC1_TimeRange == 3 || ISEC1_TimeRange == 2)
    {
      Put1Byte(ISEC1_TimePeriod1);
      Put1Byte(ISEC1_TimePeriod2);
    }
  else
    {
      Put1Byte(0);
      Put1Byte(0);
    }
  Put1Byte(ISEC1_TimeRange); /* 20 Timerange flag           */
  Put2Byte(ISEC1_AvgNum);    /* 21 Average                  */

  Put1Byte(ISEC1_AvgMiss);       /* 23 Missing from averages    */
  Put1Byte(century);             /* 24 Century                  */
  Put1Byte(ISEC1_SubCenterID);   /* 25 Subcenter                */
  Put2Int(ISEC1_DecScaleFactor); /* 26 Decimal scale factor     */

  if (ISEC1_LocalFLag)
    {
      if (ISEC1_CenterID == 78 || ISEC1_CenterID == 215 || ISEC1_CenterID == 250)
        {
          if (isec1[36] == 254)
            encodePDS_DWD_local_Extension_254(lGrib, &z, isec1);
          else if (isec1[36] == 253)
            encodePDS_DWD_local_Extension_253(lGrib, &z, isec1);
        }
      else if (ISEC1_CenterID == 98)
        {
          if (isec1[36] == 1) encodePDS_ECMWF_local_Extension_1(lGrib, &z, isec1);
        }
      else if (ISEC1_CenterID == 252)
        {
          if (isec1[36] == 1) encodePDS_MPIM_local_Extension_1(lGrib, &z, isec1);
        }
      else
        {
          const long localextlen = getLocalExtLen(isec1);
          for (long i = 0; i < localextlen; i++) Put1Byte(isec1[24 + i]);
        }
    }
}

// clang-format off
#include "templates.h"

#include "minmax_val.c"
#include "encode_array.c"

#ifdef T
#undef T
#endif
#define T double
#include "encode_array.tmpl"

#ifdef T
#undef T
#endif
#define T float
#include "encode_array.tmpl"


#ifdef T
#undef T
#endif
#define T double
#include "grib_encode.tmpl"

#ifdef T
#undef T
#endif
#define T float
#include "grib_encode.tmpl"
// clang-format on

void encode_dummy(void);
void
encode_dummy(void)
{
  (void) encode_array_unrolled_double(0, 0, 0, NULL, NULL, 0, 0, NULL);
  (void) encode_array_unrolled_float(0, 0, 0, NULL, NULL, 0, 0, NULL);
}
