
/* Automatically generated by m214003 at 2026-02-23, do not edit */

/* CDILIB_VERSION="2.6.0" */

#if defined(_WIN32) || defined(_WIN64)
#define restrict
#define ssize_t long
#elif ! defined HAVE_CONFIG_H
#define HAVE_UNISTD_H
#endif

#ifdef _ARCH_PWR6
#pragma options nostrict
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#ifdef  HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
#include <float.h>
#include <math.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
#include <assert.h>

#ifdef HAVE_LIBGRIB_API
#include <grib_api.h>
#endif

#ifdef HAVE_MMAP
#include <sys/mman.h> /* mmap() is defined in this header */
#endif

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>
#endif

#ifdef HAVE_LIBSZ
#include <szlib.h>
#endif

#ifndef HAVE_CONFIG_H
#define  HAVE_LIBGRIB      1
#define  HAVE_LIBCGRIBEX   1
#define  HAVE_LIBSERVICE   1
#define  HAVE_LIBEXTRA     1
#define  HAVE_LIBIEG       1
#define  CDI              -1
#endif

#ifndef ASYNC_WORKER_H
#define ASYNC_WORKER_H

typedef struct AsyncJob AsyncJob;
typedef struct AsyncManager AsyncManager;

// a negative threadCount gives the number of cores that should remain unused by the worker threads, returns an error code
int AsyncWorker_init(AsyncManager **jobManager, int threadCount);

// executes work(data) in a worker thread, must be followed by a call to AsyncWorker_wait()
AsyncJob *AsyncWorker_requestWork(AsyncManager *jobManager, int (*work)(void *data), void *data);

// waits for the async job to finish and returns its result (or some other error code)
int AsyncWorker_wait(AsyncManager *jobManager, AsyncJob *job);

// return the number of workers that are currently idle
int AsyncWorker_availableWorkers(AsyncManager *jobManager);

// waits for all pending jobs to finish, stops all workers, returns a non-zero error code from a pending job if there were any
int AsyncWorker_finalize(AsyncManager *jobManager);

#endif
#ifndef CDI_DATETIME_H
#define CDI_DATETIME_H

#include <stdbool.h>
#include <stdint.h>

// clang-format off

#ifdef __cplusplus
extern "C" {
#endif

typedef struct
{
  int   year;      // year of date
  short month;     // month of date
  short day;       // day of date
} CdiDate;

typedef struct
{
  short hour;      // hour part of time
  short minute;	   // minute part of time
  short second;	   // second part of time
  short ms;        // milli-second part of time. 0<=ms<=999
} CdiTime;

typedef struct
{
  CdiDate date;    // date elements
  CdiTime time;    // time elements
} CdiDateTime;

CdiDateTime cdiDateTime_set(int64_t date, int time);
CdiDate cdiDate_set(int64_t date);
CdiTime cdiTime_set(int time);
int64_t cdiDate_get(CdiDate cdiDate);
int cdiTime_get(CdiTime cdiTime);

CdiDate cdiDate_encode(int year, int month, int day);
void cdiDate_decode(CdiDate cdiDate, int *year, int *month, int *day);
CdiTime cdiTime_encode(int hour, int minute, int second, int ms);
void cdiTime_decode(CdiTime cdiTime, int *hour, int *minute, int *second, int *ms);

void cdiDate_init(CdiDate *cdiDate);
void cdiTime_init(CdiTime *cdiTime);
void cdiDateTime_init(CdiDateTime *cdiDateTime);

bool cdiDate_isEQ(CdiDate cdiDate1, CdiDate cdiDate2);
bool cdiTime_isEQ(CdiTime cdiTime1, CdiTime cdiTime2);
bool cdiDateTime_isEQ(CdiDateTime cdiDateTime1, CdiDateTime cdiDateTime2);
bool cdiDateTime_isNE(CdiDateTime cdiDateTime1, CdiDateTime cdiDateTime2);
bool cdiDateTime_isLT(CdiDateTime cdiDateTime1, CdiDateTime cdiDateTime2);
bool cdiDateTime_isNull(CdiDateTime cdiDateTime);

const char *CdiDateTime_string(CdiDateTime cdiDateTime);

#ifdef __cplusplus
}
#endif

// clang-format on

#endif /* CDI_DATETIME_H */
/*
  CDI C header file

  This is the only file that must be included to use the CDI library from C.
*/

#ifndef CDI_H_
#define CDI_H_

// clang-format off

#include <stdio.h>
#include <stdint.h>    // int64_t
#include <stdbool.h>
#include <sys/types.h>

#ifndef CDI_SIZE_TYPE
#define CDI_SIZE_TYPE int
#endif

typedef CDI_SIZE_TYPE SizeType;

//FINT_ON  <--- don't change or remove this line!!!
// Start of fortran interface for the following routines (make_fint.c)

#ifdef __cplusplus
extern "C" {
#endif

#define  CDI_MAX_NAME             256   // Max length of a name

#define  CDI_UNDEFID               -1
#define  CDI_GLOBAL                -1   // Global var ID for vlist and Z-axis
#define  CDI_XAXIS                  1   // X-axis ID for grid
#define  CDI_YAXIS                  2   // Y-axis ID for grid

// Byte order

#define  CDI_BIGENDIAN              0   // Byte order BIGENDIAN
#define  CDI_LITTLEENDIAN           1   // Byte order LITTLEENDIAN
#define  CDI_PDPENDIAN              2

#define  CDI_REAL                   1   // Real numbers
#define  CDI_COMP                   2   // Complex numbers
#define  CDI_BOTH                   3   // Both numbers

// Error identifier

#define  CDI_NOERR                  0   // No Error
#define  CDI_EEOF                  -1   // The end of file was encountered
#define  CDI_ETMOF                 -9   // Too many open files
#define  CDI_ESYSTEM              -10   // Operating system error
#define  CDI_EINVAL               -20   // Invalid argument
#define  CDI_EISDIR               -21   // Is a directory
#define  CDI_EISEMPTY             -22   // Is empty
#define  CDI_EUFTYPE              -23   // Unsupported file type
#define  CDI_ELIBNAVAIL           -24   // xxx library not available
#define  CDI_EUFSTRUCT            -25   // Unsupported file structure
#define  CDI_EUNC4                -26   // Unsupported NetCDF4 structure
#define  CDI_EDIMSIZE             -27   // Invalid dimension size
#define  CDI_EQENF                -50   // Query entries not found
#define  CDI_EQNAVAIL             -51   // Query not available for file type
#define  CDI_ELIMIT               -99   // Internal limits exceeded

// File types

#define  CDI_FILETYPE_GRB           1   // File type GRIB
#define  CDI_FILETYPE_GRB2          2   // File type GRIB version 2
#define  CDI_FILETYPE_NC            3   // File type NetCDF
#define  CDI_FILETYPE_NC2           4   // File type NetCDF version 2 (64-bit offset)
#define  CDI_FILETYPE_NC4           5   // File type NetCDF version 4
#define  CDI_FILETYPE_NC4C          6   // File type NetCDF version 4 (classic)
#define  CDI_FILETYPE_NC5           7   // File type NetCDF version 5 (64-bit data)
#define  CDI_FILETYPE_SRV           8   // File type SERVICE
#define  CDI_FILETYPE_EXT           9   // File type EXTRA
#define  CDI_FILETYPE_IEG          10   // File type IEG
#define  CDI_FILETYPE_NCZARR       11   // File type NetCDF NCZarr Data Model

// Compatibility defines for release 1.8.3 (obsolete defines)
#define  FILETYPE_GRB               1   // File type GRIB
#define  FILETYPE_GRB2              2   // File type GRIB version 2
#define  FILETYPE_NC                3   // File type NetCDF
#define  FILETYPE_NC2               4   // File type NetCDF version 2 (64-bit offset)
#define  FILETYPE_NC4               5   // File type NetCDF version 4

// Protocols (in filename/URI)

#define CDI_PROTOCOL_OTHER          0   // Any other protocol (might be supported by NetCDF library)
#define CDI_PROTOCOL_FILE           1
#define CDI_PROTOCOL_FDB            2
#define CDI_PROTOCOL_ACROSS         3

// Compress types

#define  CDI_COMPRESS_NONE          0
#define  CDI_COMPRESS_SZIP          1
#define  CDI_COMPRESS_AEC           2
#define  CDI_COMPRESS_ZIP           3
#define  CDI_COMPRESS_JPEG          4
#define  CDI_COMPRESS_FILTER        5

// external data types

// Compatibility defines for release 1.8.3 (obsolete defines)
#define  DATATYPE_PACK16           16
#define  DATATYPE_PACK24           24
#define  DATATYPE_FLT32           132
#define  DATATYPE_FLT64           164
#define  DATATYPE_INT32           232
#define  DATATYPE_INT             251

#define  CDI_DATATYPE_PACK          0
#define  CDI_DATATYPE_PACK1         1
#define  CDI_DATATYPE_PACK2         2
#define  CDI_DATATYPE_PACK3         3
#define  CDI_DATATYPE_PACK4         4
#define  CDI_DATATYPE_PACK5         5
#define  CDI_DATATYPE_PACK6         6
#define  CDI_DATATYPE_PACK7         7
#define  CDI_DATATYPE_PACK8         8
#define  CDI_DATATYPE_PACK9         9
#define  CDI_DATATYPE_PACK10       10
#define  CDI_DATATYPE_PACK11       11
#define  CDI_DATATYPE_PACK12       12
#define  CDI_DATATYPE_PACK13       13
#define  CDI_DATATYPE_PACK14       14
#define  CDI_DATATYPE_PACK15       15
#define  CDI_DATATYPE_PACK16       16
#define  CDI_DATATYPE_PACK17       17
#define  CDI_DATATYPE_PACK18       18
#define  CDI_DATATYPE_PACK19       19
#define  CDI_DATATYPE_PACK20       20
#define  CDI_DATATYPE_PACK21       21
#define  CDI_DATATYPE_PACK22       22
#define  CDI_DATATYPE_PACK23       23
#define  CDI_DATATYPE_PACK24       24
#define  CDI_DATATYPE_PACK25       25
#define  CDI_DATATYPE_PACK26       26
#define  CDI_DATATYPE_PACK27       27
#define  CDI_DATATYPE_PACK28       28
#define  CDI_DATATYPE_PACK29       29
#define  CDI_DATATYPE_PACK30       30
#define  CDI_DATATYPE_PACK31       31
#define  CDI_DATATYPE_PACK32       32
#define  CDI_DATATYPE_CPX32        64
#define  CDI_DATATYPE_CPX64       128
#define  CDI_DATATYPE_FLT32       132
#define  CDI_DATATYPE_FLT64       164
#define  CDI_DATATYPE_INT8        208
#define  CDI_DATATYPE_INT16       216
#define  CDI_DATATYPE_INT32       232
#define  CDI_DATATYPE_UINT8       308
#define  CDI_DATATYPE_UINT16      316
#define  CDI_DATATYPE_UINT32      332

// internal data types
#define  CDI_DATATYPE_INT         251
#define  CDI_DATATYPE_FLT         252
#define  CDI_DATATYPE_TXT         253
#define  CDI_DATATYPE_CPX         254
#define  CDI_DATATYPE_UCHAR       255
#define  CDI_DATATYPE_LONG        256
#define  CDI_DATATYPE_UINT        257

// Chunk types

#define  CDI_CHUNK_AUTO             1  // use default chunk size
#define  CDI_CHUNK_GRID             2
#define  CDI_CHUNK_LINES            3

// GRID types

#define  GRID_GENERIC               1  // Generic grid
#define  GRID_GAUSSIAN              2  // Regular Gaussian lon/lat grid
#define  GRID_GAUSSIAN_REDUCED      3  // Reduced Gaussian lon/lat grid
#define  GRID_LONLAT                4  // Regular longitude/latitude grid
#define  GRID_SPECTRAL              5  // Spherical harmonic coefficients (spectral gaussian)
#define  GRID_FOURIER               6  // Fourier coefficients (spectral gaussian)
#define  GRID_GME                   7  // Icosahedral-hexagonal GME grid
#define  GRID_TRAJECTORY            8  // Trajectory
#define  GRID_UNSTRUCTURED          9  // General unstructured grid
#define  GRID_CURVILINEAR          10  // Curvilinear grid
#define  GRID_HEALPIX              11  // HealPIX grid
#define  GRID_PROJECTION           12  // Projected coordinates
#define  GRID_CHARXY               13  // One horizontal character dimension

#define  CDI_PROJ_RLL              21  // Rotated Latitude Longitude
#define  CDI_PROJ_LCC              22  // Lambert Conformal Conic
#define  CDI_PROJ_LAEA             23  // Lambert Azimuthal Equal Area
#define  CDI_PROJ_SINU             24  // Sinusoidal
#define  CDI_PROJ_STERE            25  // Polar stereographic
#define  CDI_PROJ_HEALPIX          26  // Healpix

// ZAXIS types

#define  ZAXIS_SURFACE              0  // Surface level
#define  ZAXIS_GENERIC              1  // Generic level
#define  ZAXIS_HYBRID               2  // Hybrid level
#define  ZAXIS_HYBRID_HALF          3  // Hybrid half level
#define  ZAXIS_PRESSURE             4  // Isobaric pressure level in Pascal
#define  ZAXIS_HEIGHT               5  // Height above ground
#define  ZAXIS_DEPTH_BELOW_SEA      6  // Depth below sea level in meters
#define  ZAXIS_DEPTH_BELOW_LAND     7  // Depth below land surface in centimeters
#define  ZAXIS_ISENTROPIC           8  // Isentropic
#define  ZAXIS_TRAJECTORY           9  // Trajectory
#define  ZAXIS_ALTITUDE            10  // Altitude above mean sea level in meters
#define  ZAXIS_SIGMA               11  // Sigma level
#define  ZAXIS_MEANSEA             12  // Mean sea level
#define  ZAXIS_TOA                 13  // Norminal top of atmosphere
#define  ZAXIS_SEA_BOTTOM          14  // Sea bottom
#define  ZAXIS_ATMOSPHERE          15  // Entire atmosphere
#define  ZAXIS_CLOUD_BASE          16  // Cloud base level
#define  ZAXIS_CLOUD_TOP           17  // Level of cloud tops
#define  ZAXIS_ISOTHERM_ZERO       18  // Level of 0o C isotherm
#define  ZAXIS_SNOW                19  // Snow level
#define  ZAXIS_LAKE_BOTTOM         20  // Lake or River Bottom
#define  ZAXIS_SEDIMENT_BOTTOM     21  // Bottom Of Sediment Layer
#define  ZAXIS_SEDIMENT_BOTTOM_TA  22  // Bottom Of Thermally Active Sediment Layer
#define  ZAXIS_SEDIMENT_BOTTOM_TW  23  // Bottom Of Sediment Layer Penetrated By Thermal Wave
#define  ZAXIS_MIX_LAYER           24  // Mixing Layer
#define  ZAXIS_REFERENCE           25  // zaxis reference number
#define  ZAXIS_CHAR                26  // Area types
#define  ZAXIS_TROPOPAUSE          27  // Tropopause

// SUBTYPE types

enum {
  SUBTYPE_TILES                   = 0  // Tiles variable
};

#define MAX_KV_PAIRS_MATCH 10

/* Data structure defining a key-value search, possibly with multiple
   key-value pairs in combination.

   Currently, only multiple pairs combined by AND are supported.
*/
typedef struct  {
  int nAND;                                   // no. of key-value pairs that have to match
  int key_value_pairs[2][MAX_KV_PAIRS_MATCH]; // key-value pairs
} subtype_query_t;



// TIME types

#define  TIME_CONSTANT            0  // Time constant
#define  TIME_VARYING             1  // Time varying
#define  TIME_VARIABLE            1  // obsolete, use TIME_VARYING

// TSTEP types

#define  TSTEP_CONSTANT           0  // obsolete, use TIME_CONSTANT
#define  TSTEP_INSTANT            1  // Instant
#define  TSTEP_AVG                2  // Average
#define  TSTEP_ACCUM              3  // Accumulation
#define  TSTEP_MAX                4  // Maximum
#define  TSTEP_MIN                5  // Minimum
#define  TSTEP_DIFF               6  // Difference
#define  TSTEP_RMS                7  // Root mean square
#define  TSTEP_SD                 8  // Standard deviation
#define  TSTEP_COV                9  // Covariance
#define  TSTEP_RATIO             10  // Ratio
#define  TSTEP_SUM               11  // Summation
#define  TSTEP_RANGE             12
#define  TSTEP_INSTANT2          13
#define  TSTEP_INSTANT3          14

// TAXIS types

#define  TAXIS_ABSOLUTE           1
#define  TAXIS_RELATIVE           2
#define  TAXIS_FORECAST           3

// TUNIT types

#define  TUNIT_SECOND             1
#define  TUNIT_MINUTE             2
#define  TUNIT_QUARTER            3
#define  TUNIT_30MINUTES          4
#define  TUNIT_HOUR               5
#define  TUNIT_3HOURS             6
#define  TUNIT_6HOURS             7
#define  TUNIT_12HOURS            8
#define  TUNIT_DAY                9
#define  TUNIT_MONTH             10
#define  TUNIT_YEAR              11

// CALENDAR types

#define  CALENDAR_STANDARD        0  // don't change this value (used also in cgribexlib)!
#define  CALENDAR_GREGORIAN       1
#define  CALENDAR_PROLEPTIC       2
#define  CALENDAR_360DAYS         3
#define  CALENDAR_365DAYS         4
#define  CALENDAR_366DAYS         5
#define  CALENDAR_NONE            6

#define  CDI_HAS_CGRIBEX          1
#define  CDI_NC_HAS_FILTER        2
#define  CDI_NC_HAS_DAP           3
#define  CDI_NC_HAS_S3            4
#define  CDI_NC_HAS_HDF5          5

// number of unsigned char needed to store UUID
#define  CDI_UUID_SIZE           16

// Structs that are used to return data to the user

typedef struct CdiParam { int discipline; int category; int number; } CdiParam;


// Opaque types
typedef struct CdiIterator CdiIterator;
typedef struct CdiGribIterator CdiGribIterator;

// CDI control routines

void    cdiReset(void);

const char *cdiStringError(int cdiErrno);
void    cdiDebug(int debug);

const char *cdiLibraryVersion(void);
void    cdiPrintVersion(void);

int     cdiHaveFiletype(int fileType);
int     cdiGetConfig(int confType);


void    cdiDefMissval(double missval);
double  cdiInqMissval(void);
void    cdiDefGlobal(const char *string, int val);

int     namespaceNew(void);
void    namespaceSetActive(int namespaceID);
int     namespaceGetActive(void);
void    namespaceDelete(int namespaceID);


// CDI converter routines

// parameter

void    cdiParamToString(int param, char *paramstr, int maxlen);

void    cdiDecodeParam(int param, int *pnum, int *pcat, int *pdis);
int     cdiEncodeParam(int pnum, int pcat, int pdis);

// date format:  YYYYMMDD
// time format:    hhmmss

void    cdiDecodeDate(int date, int *year, int *month, int *day);
int     cdiEncodeDate(int year, int month, int day);

void    cdiDecodeTime(int time, int *hour, int *minute, int *second);
int     cdiEncodeTime(int hour, int minute, int second);


// STREAM control routines

int     cdiGetFiletype(const char *uri, int *byteorder);
int     cdiGetProtocol(const char *uri, const char **filename);

int     streamOpenReadNCMem(int ncid);
int     streamOpenWriteNCMem(int ncid);
void    streamCloseNCMem(int ncid);

//      streamOpenRead: Open a dataset for reading
int     streamOpenRead(const char *path);

//      streamOpenWrite: Create a new dataset
int     streamOpenWrite(const char *path, int filetype);

int     streamOpenAppend(const char *path);

//      streamClose: Close an open dataset
void    streamClose(int streamID);

//      streamSync: Synchronize an Open Dataset to Disk
void    streamSync(int streamID);

void    streamDefMaxSteps(int streamID, int maxSteps);
void    streamDefNumWorker(int streamID, int numWorker);

int     streamInqNumSteps(int streamID);

//      streamDefVlist: Define the Vlist for a stream
void    streamDefVlist(int streamID, int vlistID);

//      streamInqVlist: Get the Vlist of a stream
int     streamInqVlist(int streamID);

//      streamInqFiletype: Get the filetype
int     streamInqFiletype(int streamID);

//      streamDefByteorder: Define the byteorder
void    streamDefByteorder(int streamID, int byteorder);

//      streamInqByteorder: Get the byteorder
int     streamInqByteorder(int streamID);

void    streamDefShuffle(int streamID, int shuffle);

void    streamDefFilter(int streamID, const char *filterSpec);

//      streamDefCompType: Define compression type
void    streamDefCompType(int streamID, int comptype);

//      streamInqCompType: Get compression type
int     streamInqCompType(int streamID);

//      streamDefCompLevel: Define compression level
void    streamDefCompLevel(int streamID, int complevel);

//      streamInqCompLevel: Get compression level
int     streamInqCompLevel(int streamID);

//      streamDefTimestep: Define time step
int     streamDefTimestep(int streamID, int tsID);

//      streamInqTimestep: Get time step
int     streamInqTimestep(int streamID, int tsID);

//      PIO: query currently set timestep id
int     streamInqCurTimestepID(int streamID);

const char *streamFilename(int streamID);
const char *streamFilesuffix(int filetype);

SizeType streamNvals(int streamID);

int     streamInqNvars(int streamID);

// STREAM var I/O routines (random access)

//      streamWriteVar: Write a variable
void    streamWriteVar(int streamID, int varID, const double data[], SizeType numMissVals);
void    streamWriteVarF(int streamID, int varID, const float data[], SizeType numMissVals);

//      streamReadVar: Read a variable
void    streamReadVar(int streamID, int varID, double data[], SizeType *numMissVals);
void    streamReadVarF(int streamID, int varID, float data[], SizeType *numMissVals);
void    streamReadVarPart(int streamID, int varID, int varType, int start, SizeType size, void *data, SizeType *numMissVals, int memType);

//      streamWriteVarSlice: Write a horizontal slice of a variable
void    streamWriteVarSlice(int streamID, int varID, int levelID, const double data[], SizeType numMissVals);
void    streamWriteVarSliceF(int streamID, int varID, int levelID, const float data[], SizeType numMissVals);

//      streamReadVarSlice: Read a horizontal slice of a variable
void    streamReadVarSlice(int streamID, int varID, int levelID, double data[], SizeType *numMissVals);
void    streamReadVarSliceF(int streamID, int varID, int levelID, float data[], SizeType *numMissVals);
void    streamReadVarSlicePart(int streamID, int varID, int levelID, int varType, int start, SizeType size, void *data, SizeType *numMissVals, int memType);

void    streamWriteVarChunk(int streamID, int varID, const int rect[][2], const double data[], SizeType numMissVals);
void    streamWriteVarChunkF(int streamID, int varID, const int rect[][2], const float data[], SizeType numMissVals);


// STREAM field I/O routines (sequential access)

void    streamDefField(int streamID, int  varID, int  levelID);
void    streamInqField(int streamID, int *varID, int *levelID);
void    streamWriteField(int streamID, const double data[], SizeType numMissVals);
void    streamWriteFieldF(int streamID, const float data[], SizeType numMissVals);
void    streamReadField(int streamID, double data[], SizeType *numMissVals);
void    streamReadFieldF(int streamID, float data[], SizeType *numMissVals);
void    streamCopyField(int streamIDdest, int streamIDsrc);

void *  stream_get_pointer(int streamID);
void *  stream_get_vlist_pointer(int streamID);
void    pstreamInqField(void *streamPtr, int *varID, int *levelID);

void    streamInqGRIBinfo(int streamID, int *intnum, float *fltnum, off_t *bignum);


// File driven I/O (may yield better performance than using the streamXXX functions)

// Creation & Destruction
CdiIterator *cdiIterator_new(const char *path);  // Requires a subsequent call to cdiIteratorNextField() to point the iterator at the first field.
CdiIterator *cdiIterator_clone(CdiIterator *me);
char *cdiIterator_serialize(CdiIterator *me);  // Returns a malloc'ed string.
CdiIterator *cdiIterator_deserialize(const char *description);  // description is a string that was returned by cdiIteratorSerialize(). Returns a copy of the original iterator.
void cdiIterator_print(CdiIterator *me, FILE *stream);
void cdiIterator_delete(CdiIterator *me);

// Advancing an iterator
int cdiIterator_nextField(CdiIterator *me);      // Points the iterator at the next field, returns CDI_EEOF if there are no more fields in the file.

// Introspecting metadata
// All outXXX arguments to these functions may be NULL.
char *cdiIterator_inqStartTime(CdiIterator *me);    // Returns the (start) time as an ISO-8601 coded string. The caller is responsible to Free() the returned string.
char *cdiIterator_inqEndTime(CdiIterator *me);      // Returns the end time of an integration period as an ISO-8601 coded string, or NULL if there is no end time. The caller is responsible to Free() the returned string.
char *cdiIterator_inqRTime(CdiIterator *me);        // Returns the reference date as an ISO-8601 coded string. The caller is responsible to Free() the returned string.
char *cdiIterator_inqVTime(CdiIterator *me);        // Returns the validity date as an ISO-8601 coded string. The caller is responsible to Free() the returned string.
int cdiIterator_inqLevelType(CdiIterator *me, int levelSelector, char **outName_optional, char **outLongName_optional, char **outStdName_optional, char **outUnit_optional);      // callers are responsible to Free() strings that they request
int cdiIterator_inqLevel(CdiIterator *me, int levelSelector, double *outValue1_optional, double *outValue2_optional);       // outValue2 is only written to if the level is a hybrid level
int cdiIterator_inqLevelUuid(CdiIterator *me, int *outVgridNumber_optional, int *outLevelCount_optional, unsigned char outUuid_optional[CDI_UUID_SIZE]);   // outUuid must point to a buffer of 16 bytes, returns an error code if no generalized zaxis is used.
int cdiIterator_inqTile(CdiIterator *me, int *outTileIndex, int *outTileAttribute); // Returns CDI_EINVAL if there is no tile information connected to the current field, *outTileIndex and *outTileAttribute will be set to -1 in this case.
int cdiIterator_inqTileCount(CdiIterator *me, int *outTileCount, int *outTileAttributeCount); // outTileAttributeCount is the count for the tile associated with the current field, a total attribute count cannot be inquired. Returns CDI_EINVAL if there is no tile information connected to the current field, *outTileCount and *outTileAttributeCount will be set to 0 in this case.
CdiParam cdiIterator_inqParam(CdiIterator *me);
void cdiIterator_inqParamParts(CdiIterator *me, int *outDiscipline, int *outCategory, int *outNumber);	// Some FORTRAN compilers produce wrong code for the cdiIterator_inqParam()-wrapper, rendering it unusable from FORTRAN. This function is the workaround.
int cdiIterator_inqDatatype(CdiIterator *me);
int cdiIterator_inqFiletype(CdiIterator *me);
int cdiIterator_inqTsteptype(CdiIterator *me);
char *cdiIterator_inqVariableName(CdiIterator *me);     // The caller is responsible to Free() the returned buffer.
int cdiIterator_inqGridId(CdiIterator *me);             // The returned id is only valid until the next call to cdiIteratorNextField().

// Reading data
void cdiIterator_readField(CdiIterator *me, double data[], SizeType *numMissVals_optional);
void cdiIterator_readFieldF(CdiIterator *me, float data[], SizeType *numMissVals_optional);
// TODO[NH]: Add functions to read partial fields.


// Direct access to grib fields
CdiGribIterator *cdiGribIterator_clone(CdiIterator *me);  // Returns NULL if the associated file is not a GRIB file.
void cdiGribIterator_delete(CdiGribIterator *me);

// Callthroughs to GRIB-API
int cdiGribIterator_getLong(CdiGribIterator *me, const char *key, long *value);         // Same semantics as grib_get_long().
int cdiGribIterator_getDouble(CdiGribIterator *me, const char *key, double *value);     // Same semantics as grib_get_double().
int cdiGribIterator_getLength(CdiGribIterator *me, const char *key, size_t *value);     // Same semantics as grib_get_length().
int cdiGribIterator_getString(CdiGribIterator *me, const char *key, char *value, size_t *length);       // Same semantics as grib_get_string().
int cdiGribIterator_getSize(CdiGribIterator *me, const char *key, size_t *value);       // Same semantics as grib_get_size().
int cdiGribIterator_getLongArray(CdiGribIterator *me, const char *key, long *value, size_t *array_size);       // Same semantics as grib_get_long_array().
int cdiGribIterator_getDoubleArray(CdiGribIterator *me, const char *key, double *value, size_t *array_size);   // Same semantics as grib_get_double_array().

// Convenience functions for accessing GRIB-API keys
int cdiGribIterator_inqEdition(CdiGribIterator *me);
long cdiGribIterator_inqLongValue(CdiGribIterator *me, const char *key);       // Aborts on failure to fetch the given key.
long cdiGribIterator_inqLongDefaultValue(CdiGribIterator *me, const char *key, long defaultValue); // Returns the default value if the given key is not present.
double cdiGribIterator_inqDoubleValue(CdiGribIterator *me, const char *key);   // Aborts on failure to fetch the given key.
double cdiGribIterator_inqDoubleDefaultValue(CdiGribIterator *me, const char *key, double defaultValue); // Returns the default value if the given key is not present.
char *cdiGribIterator_inqStringValue(CdiGribIterator *me, const char *key);    // Returns a malloc'ed string.

// VLIST routines

//      vlistCreate: Create a variable list
int     vlistCreate(void);

//      vlistDestroy: Destroy a variable list
void    vlistDestroy(int vlistID);

//      vlistDuplicate: Duplicate a variable list
int     vlistDuplicate(int vlistID);

//      vlistCopyFlag: Copy some entries of a variable list
void    vlistCopyFlag(int vlistID2, int vlistID1);

void    vlistClearFlag(int vlistID);

//      vlistCat: Concatenate two variable lists
void    vlistCat(int vlistID2, int vlistID1);

//      vlistMerge: Merge two variable lists
void    vlistMerge(int vlistID2, int vlistID1);

void    vlistPrint(int vlistID);

//      vlistNumber: Number type in a variable list
int     vlistNumber(int vlistID);

//      vlistNvars: Number of variables in a variable list
int     vlistNvars(int vlistID);

//      vlistNumGrids: Number of grids in a variable list
int     vlistNumGrids(int vlistID);

//      vlistNumZaxis: Number of zaxis in a variable list
int     vlistNumZaxis(int vlistID);

//      vlistNsubtypes: Number of subtypes in a variable list
int     vlistNsubtypes(int vlistID);

void    vlistDefNtsteps(int vlistID, int nts);
int     vlistNtsteps(int vlistID);
SizeType vlistGridsizeMax(int vlistID);
int     vlistGrid(int vlistID, int index);
int     vlistGridIndex(int vlistID, int gridID);
void    vlistChangeGridIndex(int vlistID, int index, int gridID);
void    vlistChangeGrid(int vlistID, int gridID1, int gridID2);
int     vlistZaxis(int vlistID, int index);
int     vlistZaxisIndex(int vlistID, int zaxisID);
void    vlistChangeZaxisIndex(int vlistID, int index, int zaxisID);
void    vlistChangeZaxis(int vlistID, int zaxisID1, int zaxisID2);
int     vlistNumFields(int vlistID);
int     vlistSubtype(int vlistID, int index);
int     vlistSubtypeIndex(int vlistID, int subtypeID);

//      vlistDefTaxis: Define the time axis of a variable list
void    vlistDefTaxis(int vlistID, int taxisID);

//      vlistInqTaxis: Get the time axis of a variable list
int     vlistInqTaxis(int vlistID);

void    vlistDefTable(int vlistID, int tableID);
int     vlistInqTable(int vlistID);
void    vlistDefInstitut(int vlistID, int instID);
int     vlistInqInstitut(int vlistID);
void    vlistDefModel(int vlistID, int modelID);
int     vlistInqModel(int vlistID);


// VLIST VAR routines

//      vlistDefVarTiles: Create a new tile-based variable
int     vlistDefVarTiles(int vlistID, int gridID, int zaxisID, int timetype, int tilesetID);

//      vlistDefVar: Create a new variable
int     vlistDefVar(int vlistID, int gridID, int zaxisID, int timetype);

void    vlistChangeVarGrid(int vlistID, int varID, int gridID);
void    vlistChangeVarZaxis(int vlistID, int varID, int zaxisID);

void    vlistInqVar(int vlistID, int varID, int *gridID, int *zaxisID, int *timetype);
int     vlistInqVarGrid(int vlistID, int varID);
int     vlistInqVarZaxis(int vlistID, int varID);

//      used in MPIOM
int     vlistInqVarID(int vlistID, int code);

void    vlistDefVarTimetype(int vlistID, int varID, int timetype);
int     vlistInqVarTimetype(int vlistID, int varID);

void    vlistDefVarTsteptype(int vlistID, int varID, int tsteptype);

//      vlistInqVarTsteptype: Get the timestep type of a Variable
int     vlistInqVarTsteptype(int vlistID, int varID);

void    vlistDefVarCompType(int vlistID, int varID, int comptype);
int     vlistInqVarCompType(int vlistID, int varID);
void    vlistDefVarCompLevel(int vlistID, int varID, int complevel);
int     vlistInqVarCompLevel(int vlistID, int varID);

//      vlistDefVarParam: Define the parameter number of a Variable
void    vlistDefVarParam(int vlistID, int varID, int param);

//      vlistInqVarParam: Get the parameter number of a Variable
int     vlistInqVarParam(int vlistID, int varID);

//      vlistDefVarCode: Define the code number of a Variable
void    vlistDefVarCode(int vlistID, int varID, int code);

//      vlistInqVarCode: Get the code number of a Variable
int     vlistInqVarCode(int vlistID, int varID);

//      vlistDefVarDatatype: Define the data type of a Variable
void    vlistDefVarDatatype(int vlistID, int varID, int datatype);

//      vlistInqVarDatatype: Get the data type of a Variable
int     vlistInqVarDatatype(int vlistID, int varID);

void    vlistDefVarXYZ(int vlistID, int varID, int xyz);
int     vlistInqVarXYZ(int vlistID, int varID);

void    vlistDefVarNSB(int vlistID, int varID, int nsb);
int     vlistInqVarNSB(int vlistID, int varID);

int     vlistInqVarNumber(int vlistID, int varID);

void    vlistDefVarInstitut(int vlistID, int varID, int instID);
int     vlistInqVarInstitut(int vlistID, int varID);
void    vlistDefVarModel(int vlistID, int varID, int modelID);
int     vlistInqVarModel(int vlistID, int varID);
void    vlistDefVarTable(int vlistID, int varID, int tableID);
int     vlistInqVarTable(int vlistID, int varID);

//      vlistDefVarName: Define the name of a Variable
void    vlistDefVarName(int vlistID, int varID, const char *name);

//      vlistInqVarName: Get the name of a Variable
void    vlistInqVarName(int vlistID, int varID, char *name);

//      vlistCopyVarName: Safe and convenient version of vlistInqVarName
char   *vlistCopyVarName(int vlistId, int varId);

//      vlistDefVarStdname: Define the standard name of a Variable
void    vlistDefVarStdname(int vlistID, int varID, const char *stdname);

//      vlistInqVarStdname: Get the standard name of a Variable
void    vlistInqVarStdname(int vlistID, int varID, char *stdname);

//      vlistDefVarLongname: Define the long name of a Variable
void    vlistDefVarLongname(int vlistID, int varID, const char *longname);

//      vlistInqVarLongname: Get the long name of a Variable
void    vlistInqVarLongname(int vlistID, int varID, char *longname);

//      vlistDefVarUnits: Define the units of a Variable
void    vlistDefVarUnits(int vlistID, int varID, const char *units);

//      vlistInqVarUnits: Get the units of a Variable
void    vlistInqVarUnits(int vlistID, int varID, char *units);

//      vlistDefVarMissval: Define the missing value of a Variable
void    vlistDefVarMissval(int vlistID, int varID, double missval);

//      vlistInqVarMissval: Get the missing value of a Variable
double  vlistInqVarMissval(int vlistID, int varID);

SizeType vlistInqVarSize(int vlistID, int varID);

void    vlistDefIndex(int vlistID, int varID, int levID, int index);
int     vlistInqIndex(int vlistID, int varID, int levID);
void    vlistDefFlag(int vlistID, int varID, int levID, int flag);
int     vlistInqFlag(int vlistID, int varID, int levID);
int     vlistFindVar(int vlistID, int fvarID);
int     vlistFindLevel(int vlistID, int fvarID, int flevelID);
int     vlistMergedVar(int vlistID, int varID);
int     vlistMergedLevel(int vlistID, int varID, int levelID);

int     pvlistInqFlag(void *vlistPtr, int varID, int levelID);

//      cdiClearAdditionalKeys: Clear the list of additional GRIB keys
void    cdiClearAdditionalKeys(void);
//      cdiDefAdditionalKey: Register an additional GRIB key which is read when file is opened
void    cdiDefAdditionalKey(const char *string);

//      vlistDefVarIntKey: Set an arbitrary keyword/integer value pair for GRIB API
void    vlistDefVarIntKey(int vlistID, int varID, const char *name, int value);
//      vlistDefVarDblKey: Set an arbitrary keyword/double value pair for GRIB API
void    vlistDefVarDblKey(int vlistID, int varID, const char *name, double value);

//      vlistDefVarIntArrKey: Set an arbitrary keyword/integer array pair for GRIB API
void    vlistDefVarIntArrKey(int vlistID, int varID, const char *name, int *values, int nvalues);
//      vlistDefVarDblArrKey: Set an arbitrary keyword/double array pair for GRIB API
void    vlistDefVarDblArrKey(int vlistID, int varID, const char *name, double *values, int nvalues);

//      vlistHasVarKey: returns 1 if meta-data key was read, 0 otherwise
int     vlistHasVarKey(int vlistID, int varID, const char *name);
//      vlistInqVarDblKey: raw access to GRIB meta-data
double  vlistInqVarDblKey(int vlistID, int varID, const char *name);
//      vlistInqVarIntKey: raw access to GRIB meta-data
int     vlistInqVarIntKey(int vlistID, int varID, const char *name);
//      vlistInqVarDblArrKey: raw access to GRIB meta-data
double *vlistInqVarDblArrKey(int vlistID, int varID, const char *name);
//      vlistInqVarIntArrKey: raw access to GRIB meta-data
int    *vlistInqVarIntArrKey(int vlistID, int varID, const char *name);

// CDI attributes

//      cdiInqNatts: Get number of attributes assigned to this variable
int     cdiInqNatts(int cdiID, int varID, int *nattsp);
//      cdiInqAtt: Get information about an attribute
int     cdiInqAtt(int cdiID, int varID, int attrnum, char *name, int *typep, int *lenp);
int     cdiInqAttLen(int cdiID, int varID, const char *name);
int     cdiInqAttType(int cdiID, int varID, const char *name);
int     cdiDelAtt(int cdiID, int varID, const char *name);

int     cdiCopyAtts(int cdiID1, int varID1, int cdiID2, int varID2);

//      cdiDefAttInt: Define an integer attribute
int     cdiDefAttInt(int cdiID, int varID, const char *name, int type, int len, const int ip[]);
//      cdiDefAttFlt: Define a floating point attribute
int     cdiDefAttFlt(int cdiID, int varID, const char *name, int type, int len, const double dp[]);
//      cdiDefAttTxt: Define a text attribute
int     cdiDefAttTxt(int cdiID, int varID, const char *name, int len, const char *tp_cbuf);

//      cdiInqAttInt: Get the value(s) of an integer attribute
int     cdiInqAttInt(int cdiID, int varID, const char *name, int mlen, int ip[]);
//      cdiInqAttFlt: Get the value(s) of a floating point attribute
int     cdiInqAttFlt(int cdiID, int varID, const char *name, int mlen, double dp[]);
//      cdiInqAttTxt: Get the value(s) of a text attribute
int     cdiInqAttTxt(int cdiID, int varID, const char *name, int mlen, char *tp_cbuf);


// GRID routines

void    gridName(int gridtype, char *gridname);
const char *gridNamePtr(int gridtype);

void    gridCompress(int gridID);

void    gridDefMaskGME(int gridID, const int mask[]);
int     gridInqMaskGME(int gridID, int mask[]);

void    gridDefMask(int gridID, const int mask[]);
int     gridInqMask(int gridID, int mask[]);

//      gridCreate: Create a horizontal Grid
int     gridCreate(int gridtype, SizeType size);

//      gridDestroy: Destroy a horizontal Grid
void    gridDestroy(int gridID);

//      gridDuplicate: Duplicate a Grid
int     gridDuplicate(int gridID);

//      gridDefProj: Define the projection ID of a Grid
void    gridDefProj(int gridID, int projID);

//      gridInqProj: Get the projection ID of a Grid
int     gridInqProj(int gridID);

//      gridInqProjType: Get the projection type
int     gridInqProjType(int gridID);

//      gridInqType: Get the type of a Grid
int     gridInqType(int gridID);

//      gridInqSize: Get the size of a Grid
SizeType gridInqSize(int gridID);

//      gridDefXsize: Define the size of a X-axis
void    gridDefXsize(int gridID, SizeType xsize);

//      gridInqXsize: Get the size of a X-axis
SizeType gridInqXsize(int gridID);

//      gridDefYsize: Define the size of a Y-axis
void    gridDefYsize(int gridID, SizeType ysize);

//      gridInqYsize: Get the size of a Y-axis
SizeType gridInqYsize(int gridID);

void    gridDefIndices(int gridID, const int64_t *indices);
SizeType gridInqIndices(int gridID, int64_t *indices);

//      gridDefNP: Define the number of parallels between a pole and the equator
void    gridDefNP(int gridID, int np);

//      gridInqNP: Get the number of parallels between a pole and the equator
int     gridInqNP(int gridID);

//      gridDefXvals: Define the values of a X-axis
void    gridDefXvals(int gridID, const double xvals[]);

//      gridInqXvals: Get all values of a X-axis
SizeType gridInqXvals(int gridID, double xvals[]);
SizeType gridInqXvalsPart(int gridID, int start, SizeType size, double xvals[]);
const double *gridInqXvalsPtr(int gridID);

//      gridInqXIsc: Find out whether X-coordinate is of type CHAR
int     gridInqXIsc(int gridID);

//      gridInqXCvals: Get strings from X-axis in case grid is of type GRID_CHARXY
SizeType gridInqXCvals(int gridID, char *xcvals[]);

//      gridDefYvals: Define the values of a Y-axis
void    gridDefYvals(int gridID, const double yvals[]);

//      gridInqYvals: Get all values of a Y-axis
SizeType gridInqYvals(int gridID, double yvals[]);
SizeType gridInqYvalsPart(int gridID, int start, SizeType size, double yvals[]);
const double *gridInqYvalsPtr(int gridID);

//      gridInqYIsc: Find out whether Y-coordinate is of type CHAR
int     gridInqYIsc(int gridID);

//      gridInqYCvals: Get strings from Y-axis in case grid is of type GRID_CHARXY
SizeType gridInqYCvals(int gridID, char *ycvals[]);

// CDI var keys

// String keys
#define  CDI_KEY_NAME                          942  // Variable name
#define  CDI_KEY_LONGNAME                      943  // Long name of the variable
#define  CDI_KEY_STDNAME                       944  // CF Standard name of the variable
#define  CDI_KEY_UNITS                         945  // Units of the variable
#define  CDI_KEY_DATATYPE                      946  // Data type
#define  CDI_KEY_REFERENCEURI                  947  // Reference URI to grid file

// Integer keys
#define  CDI_KEY_NUMBEROFGRIDUSED              961  // GRIB2 numberOfGridUsed
#define  CDI_KEY_NUMBEROFGRIDINREFERENCE       962  // GRIB2 numberOfGridInReference
#define  CDI_KEY_NUMBEROFVGRIDUSED             963  // GRIB2 numberOfVGridUsed
#define  CDI_KEY_NLEV                          964  // GRIB2 nlev
#define  CDI_KEY_CHUNKTYPE                     965  // ChunkType: CDI_CHUNK_AUTO/CDI_CHUNK_GRID/CDI_CHUNK_LINES
#define  CDI_KEY_CHUNKSIZE                     966  // ChunkSize
#define  CDI_KEY_CHUNKSIZE_DIMT                967  // ChunkSize time dimension
#define  CDI_KEY_CHUNKSIZE_DIMZ                968  // ChunkSize zaxis dimension
#define  CDI_KEY_CHUNKSIZE_DIMY                969  // ChunkSize yaxis dimension
#define  CDI_KEY_CHUNKSIZE_DIMX                970  // ChunkSize xaxis dimension

// Floating point keys
#define  CDI_KEY_MISSVAL                       701  // Missing value
#define  CDI_KEY_ADDOFFSET                     702  // Add offset
#define  CDI_KEY_SCALEFACTOR                   703  // Scale factor
#define  CDI_KEY_FILTERSPEC_IN                 720  // NetCDF4 filter specification read in
#define  CDI_KEY_FILTERSPEC                    721  // NetCDF4 filter specification

// Byte array keys
#define  CDI_KEY_UUID                          960  // UUID for grid/Z-axis reference [size: CDI_UUID_SIZE]


#define  CDI_KEY_DIMNAME                       941  // Dimension name

#define  CDI_KEY_PSNAME                        950  // Z-axis surface pressure name
#define  CDI_KEY_P0NAME                        951  // Z-axis reference pressure name
#define  CDI_KEY_P0VALUE                       952  // Z-axis reference pressure in Pa

#define  CDI_KEY_TABLESVERSION                 801  // GRIB2 tablesVersion
#define  CDI_KEY_LOCALTABLESVERSION            802  // GRIB2 localTablesVersion
#define  CDI_KEY_TYPEOFGENERATINGPROCESS       803  // GRIB2 typeOfGeneratingProcess
#define  CDI_KEY_PRODUCTDEFINITIONTEMPLATE     804  // GRIB2 productDefinitionTemplate
#define  CDI_KEY_TYPEOFPROCESSEDDATA           805  // GRIB2 typeOfProcessedData
#define  CDI_KEY_SHAPEOFTHEEARTH               806  // GRIB2 shapeOfTheEarth
#define  CDI_KEY_BACKGROUNDPROCESS             807  // GRIB2 backgroundProcess
#define  CDI_KEY_TYPEOFENSEMBLEFORECAST        808  // GRIB2 typeOfEnsembleForecast
#define  CDI_KEY_NUMBEROFFORECASTSINENSEMBLE   809  // GRIB2 numberOfForecastsInEnsemble
#define  CDI_KEY_PERTURBATIONNUMBER            810  // GRIB2 perturbationNumber
#define  CDI_KEY_CENTRE                        811  // GRIB2 centre
#define  CDI_KEY_SUBCENTRE                     812  // GRIB2 subCentre
#define  CDI_KEY_MPIMTYPE                      813  // GRIB2 mpimType
#define  CDI_KEY_MPIMCLASS                     814  // GRIB2 mpimClass
#define  CDI_KEY_MPIMUSER                      815  // GRIB2 mpimUser
#define  CDI_KEY_REVSTATUS                     816  // GRIB2 revStatus
#define  CDI_KEY_REVNUMBER                     817  // GRIB2 revNumber
#define  CDI_KEY_GRIB2LOCALSECTIONNUMBER       818  // GRIB2 grib2LocalSectionNumber
#define  CDI_KEY_SECTION2PADDINGLENGTH         819  // GRIB2 length of section2Padding
#define  CDI_KEY_SECTION2PADDING               820  // GRIB2 section2Padding
#define  CDI_KEY_CONSTITUENTTYPE               821  // GRIB2 constituentType
#define  CDI_KEY_TYPEOFTIMEINCREMENT           822  // GRIB2 typeOfTimeIncrement
#define  CDI_KEY_TYPEOFFIRSTFIXEDSURFACE       823  // GRIB2 typeOfFirstFixedSurface
#define  CDI_KEY_TYPEOFSECONDFIXEDSURFACE      824  // GRIB2 typeOfSecondFixedSurface
#define  CDI_KEY_UVRELATIVETOGRID              825  // GRIB  uvRelativeToGrid
#define  CDI_KEY_SCANNINGMODE                  826  // GRIB  scanningMode

#define  CDI_KEY_VDIMNAME                      920  // Vertex dimension name
#define  CDI_KEY_GRIDMAP_VARTYPE               921  // Grid mapping var datatype
#define  CDI_KEY_GRIDMAP_VARNAME               922  // Grid mapping var name
#define  CDI_KEY_GRIDMAP_NAME                  923  // Grid mapping name

//      cdiDefKeyInt: Define an integer value from a key
int     cdiDefKeyInt(int cdiID, int varID, int key, int value);

//      cdiInqKeyInt: Get an integer value from a key
int     cdiInqKeyInt(int cdiID, int varID, int key, int *value);

//      cdiDefKeyFloat: Define a float value from a key
int     cdiDefKeyFloat(int cdiID, int varID, int key, double value);

//      cdiInqKeyFloat Get a float value from a key
int     cdiInqKeyFloat(int cdiID, int varID, int key, double *value);

//      cdiDefKeyBytes: Define a byte array from a key
int     cdiDefKeyBytes(int cdiID, int varID, int key, const unsigned char bytes[], int length);

//      cdiInqKeyBytes: Get a byte array from a key
int     cdiInqKeyBytes(int cdiID, int varID, int key, unsigned char bytes[], int *length);

//      cdiDefKeyString: Define a string from a key
int     cdiDefKeyString(int cdiID, int varID, int key, const char *string);

//      cdiInqKeyString: Get a string from a key
int     cdiInqKeyString(int cdiID, int varID, int key, char *string, int *length);

//      cdiInqKeyLen: Get the length of the string representation of the key
int     cdiInqKeyLen(int cdiID, int varID, int key, int *length);

int     cdiCopyKeys(int cdiID1, int varID1, int cdiID2, int varID2);

int     cdiCopyKey(int cdiID1, int varID1, int key, int cdiID2);

int     cdiDeleteKey(int cdiID, int varID, int key);

// GRID routines

//      gridDefXname: Define the name of a X-axis
void    gridDefXname(int gridID, const char *xname);

//      gridInqXname: Get the name of a X-axis
void    gridInqXname(int gridID, char *xname);

//      gridDefXlongname: Define the longname of a X-axis
void    gridDefXlongname(int gridID, const char *xlongname);

//      gridInqXlongname: Get the longname of a X-axis
void    gridInqXlongname(int gridID, char *xlongname);

//      gridDefXunits: Define the units of a X-axis
void    gridDefXunits(int gridID, const char *xunits);

//      gridInqXunits: Get the units of a X-axis
void    gridInqXunits(int gridID, char *xunits);

//      gridDefYname: Define the name of a Y-axis
void    gridDefYname(int gridID, const char *yname);

//      gridInqYname: Get the name of a Y-axis
void    gridInqYname(int gridID, char *yname);

//      gridDefYlongname: Define the longname of a Y-axis
void    gridDefYlongname(int gridID, const char *ylongname);

//      gridInqYlongname: Get the longname of a Y-axis
void    gridInqYlongname(int gridID, char *ylongname);

//      gridDefYunits: Define the units of a Y-axis
void    gridDefYunits(int gridID, const char *yunits);

//      gridInqYunits: Get the units of a Y-axis
void    gridInqYunits(int gridID, char *yunits);

void    gridDefDatatype(int gridID, int datatype);
int     gridInqDatatype(int gridID);

//      gridInqXval: Get one value of a X-axis
double  gridInqXval(int gridID, SizeType index);

//      gridInqYval: Get one value of a Y-axis
double  gridInqYval(int gridID, SizeType index);

double  gridInqXinc(int gridID);
double  gridInqYinc(int gridID);

int     gridIsCyclic(int gridID);

int     gridInqTrunc(int gridID);
void    gridDefTrunc(int gridID, int trunc);

// Reference of an unstructured grid

//      gridDefNumber: Define the reference number for an unstructured grid
void    gridDefNumber(int gridID, int number);

//      gridInqNumber: Get the reference number to an unstructured grid
int     gridInqNumber(int gridID);

//      gridDefPosition: Define the position of grid in the reference file
void    gridDefPosition(int gridID, int position);

//      gridInqPosition: Get the position of grid in the reference file
int     gridInqPosition(int gridID);

//      gridDefReference: Define the reference URI for an unstructured grid
void    gridDefReference(int gridID, const char *reference);

//      gridInqReference: Get the reference URI to an unstructured grid
int     gridInqReference(int gridID, char *reference);

//      gridDefUUID: Define the UUID of an unstructured grid
void    gridDefUUID(int gridID, const unsigned char uuid[CDI_UUID_SIZE]);

//      gridInqUUID: Get the UUID of an unstructured grid
void    gridInqUUID(int gridID, unsigned char uuid[CDI_UUID_SIZE]);

// Rotated Lon/Lat grid
void    gridDefParamRLL(int gridID, double xpole, double ypole, double angle);
void    gridInqParamRLL(int gridID, double *xpole, double *ypole, double *angle);

// Hexagonal GME grid
void    gridDefParamGME(int gridID, int nd, int ni, int ni2, int ni3);
void    gridInqParamGME(int gridID, int *nd, int *ni, int *ni2, int *ni3);

void    gridDefArea(int gridID, const double area[]);
void    gridInqArea(int gridID, double area[]);
int     gridHasArea(int gridID);

//      gridDefNvertex: Define the number of vertex of a Gridbox
void    gridDefNvertex(int gridID, int nvertex);

//      gridInqNvertex: Get the number of vertex of a Gridbox
int     gridInqNvertex(int gridID);

//      gridDefXbounds: Define the bounds of a X-axis
void    gridDefXbounds(int gridID, const double xbounds[]);

//      gridInqXbounds: Get the bounds of a X-axis
SizeType gridInqXbounds(int gridID, double xbounds[]);
SizeType gridInqXboundsPart(int gridID, int start, SizeType size, double xbounds[]);
const double *gridInqXboundsPtr(int gridID);

//      gridDefYbounds: Define the bounds of a Y-axis
void    gridDefYbounds(int gridID, const double ybounds[]);

//      gridInqYbounds: Get the bounds of a Y-axis
SizeType gridInqYbounds(int gridID, double ybounds[]);
SizeType gridInqYboundsPart(int gridID, int start, SizeType size, double ybounds[]);
const double *gridInqYboundsPtr(int gridID);

void    gridDefReducedPoints(int gridID, int reducedPointsSize, const int reducedPoints[]);
void    gridInqReducedPoints(int gridID, int reducedPoints[]);
void    gridChangeType(int gridID, int gridtype);

void    gridDefComplexPacking(int gridID, int lpack);
int     gridInqComplexPacking(int gridID);

// ZAXIS routines

void    zaxisName(int zaxistype, char *zaxisname);
const char *zaxisNamePtr(int leveltype);

//      zaxisCreate: Create a vertical Z-axis
int     zaxisCreate(int zaxistype, int size);

//      zaxisDestroy: Destroy a vertical Z-axis
void    zaxisDestroy(int zaxisID);

//      zaxisInqType: Get the type of a Z-axis
int     zaxisInqType(int zaxisID);

//      zaxisInqSize: Get the size of a Z-axis
int     zaxisInqSize(int zaxisID);

//      zaxisDuplicate: Duplicate a Z-axis
int     zaxisDuplicate(int zaxisID);

//      zaxisDefLevels: Define the levels of a Z-axis
void    zaxisDefLevels(int zaxisID, const double levels[]);

//      zaxisDefCvals: Define area types of a Z-axis
void    zaxisDefCvals(int zaxisID, const char *cvals[], int clength);

//      zaxisInqLevels: Get all levels of a Z-axis
int     zaxisInqLevels(int zaxisID, double levels[]);

//      zaxisInqCLen: Get maximal string length of character Z-axis
int     zaxisInqCLen(int zaxisID);

//      zaxisInqCVals: Get all string values of a character Z-axis
int     zaxisInqCVals(int zaxisID, char ***clevels);

//      zaxisDefLevel: Define one level of a Z-axis
void    zaxisDefLevel(int zaxisID, int levelID, double levels);

//      zaxisInqLevel: Get one level of a Z-axis
double  zaxisInqLevel(int zaxisID, int levelID);

//      zaxisDefNlevRef: Define the number of half levels of a generalized Z-axis
void    zaxisDefNlevRef(int gridID, int nhlev);

//      zaxisInqNlevRef: Get the number of half levels of a generalized Z-axis
int     zaxisInqNlevRef(int gridID);

//      zaxisDefNumber: Define the reference number for a generalized Z-axis
void    zaxisDefNumber(int gridID, int number);

//      zaxisInqNumber: Get the reference number to a generalized Z-axis
int     zaxisInqNumber(int gridID);

//      zaxisDefUUID: Define the UUID of a generalized Z-axis
void    zaxisDefUUID(int zaxisID, const unsigned char uuid[CDI_UUID_SIZE]);

//      zaxisInqUUID: Get the UUID of a generalized Z-axis
void    zaxisInqUUID(int zaxisID, unsigned char uuid[CDI_UUID_SIZE]);

//      zaxisDefName: Define the name of a Z-axis
void    zaxisDefName(int zaxisID, const char *name_optional);

//      zaxisInqName: Get the name of a Z-axis
void    zaxisInqName(int zaxisID, char *name);

//      zaxisDefLongname: Define the longname of a Z-axis
void    zaxisDefLongname(int zaxisID, const char *longname_optional);

//      zaxisInqLongname: Get the longname of a Z-axis
void    zaxisInqLongname(int zaxisID, char *longname);

//      zaxisDefUnits: Define the units of a Z-axis
void    zaxisDefUnits(int zaxisID, const char *units_optional);

//      zaxisInqUnits: Get the units of a Z-axis
void    zaxisInqUnits(int zaxisID, char *units);

//      zaxisInqStdname: Get the standard name of a Z-axis
void    zaxisInqStdname(int zaxisID, char *stdname);

void    zaxisDefDatatype(int zaxisID, int datatype);
int     zaxisInqDatatype(int zaxisID);

void    zaxisDefPositive(int zaxisID, int positive);
int     zaxisInqPositive(int zaxisID);

void    zaxisDefScalar(int zaxisID);
int     zaxisInqScalar(int zaxisID);

void    zaxisDefVct(int zaxisID, int size, const double vct[]);
void    zaxisInqVct(int zaxisID, double vct[]);
int     zaxisInqVctSize(int zaxisID);
const double *zaxisInqVctPtr(int zaxisID);
void    zaxisDefLbounds(int zaxisID, const double lbounds[]);
int     zaxisInqLbounds(int zaxisID, double lbounds_optional[]);
double  zaxisInqLbound(int zaxisID, int index);
void    zaxisDefUbounds(int zaxisID, const double ubounds[]);
int     zaxisInqUbounds(int zaxisID, double ubounds_optional[]);
double  zaxisInqUbound(int zaxisID, int index);
void    zaxisDefWeights(int zaxisID, const double weights[]);
int     zaxisInqWeights(int zaxisID, double weights_optional[]);
void    zaxisChangeType(int zaxisID, int zaxistype);

// TAXIS routines

//      taxisCreate: Create a Time axis
int     taxisCreate(int taxistype);

//      taxisDestroy: Destroy a Time axis
void    taxisDestroy(int taxisID);

int     taxisDuplicate(int taxisID);

void    taxisCopyTimestep(int taxisIDdes, int taxisIDsrc);

void    taxisDefType(int taxisID, int taxistype);
int     taxisInqType(int taxisID);

//      taxisDefVdate: Define the verification date
void    taxisDefVdate(int taxisID, int date);

//      taxisDefVtime: Define the verification time
void    taxisDefVtime(int taxisID, int time);

//      taxisInqVdate: Get the verification date
int     taxisInqVdate(int taxisID);

//      taxisInqVtime: Get the verification time
int     taxisInqVtime(int taxisID);

//      taxisDefRdate: Define the reference date
void    taxisDefRdate(int taxisID, int date);

//      taxisDefRtime: Define the reference time
void    taxisDefRtime(int taxisID, int time);

//      taxisInqRdate: Get the reference date
int     taxisInqRdate(int taxisID);

//      taxisInqRtime: Get the reference time
int     taxisInqRtime(int taxisID);

int     taxisHasBounds(int taxisID);
void    taxisWithBounds(int taxisID);

void    taxisDeleteBounds(int taxisID);

void    taxisDefVdateBounds(int taxisID, int vdate_lb, int vdate_ub);

void    taxisDefVtimeBounds(int taxisID, int vtime_lb, int vtime_ub);

void    taxisInqVdateBounds(int taxisID, int *vdate_lb, int *vdate_ub);

void    taxisInqVtimeBounds(int taxisID, int *vtime_lb, int *vtime_ub);

//      taxisDefCalendar: Define the calendar
void    taxisDefCalendar(int taxisID, int calendar);

//      taxisInqCalendar: Get the calendar
int     taxisInqCalendar(int taxisID);

void    taxisDefTunit(int taxisID, int tunit);
int     taxisInqTunit(int taxisID);

void    taxisDefForecastTunit(int taxisID, int tunit);
int     taxisInqForecastTunit(int taxisID);

void    taxisDefForecastPeriod(int taxisID, double fc_period);
double  taxisInqForecastPeriod(int taxisID);

void    taxisDefNumavg(int taxisID, int numavg);
int     taxisInqNumavg(int taxisID);

const char *taxisNamePtr(int taxisID);
const char *tunitNamePtr(int tunitID);


// Institut routines

int     institutDef(int center, int subcenter, const char *name, const char *longname);
int     institutInq(int center, int subcenter, const char *name, const char *longname);
int     institutInqNumber(void);
int     institutInqCenter(int instID);
int     institutInqSubcenter(int instID);
const char *institutInqNamePtr(int instID);
const char *institutInqLongnamePtr(int instID);

// Model routines

int     modelDef(int instID, int modelgribID, const char *name);
int     modelInq(int instID, int modelgribID, const char *name);
int     modelInqInstitut(int modelID) ;
int     modelInqGribID(int modelID);
const char *modelInqNamePtr(int modelID);

// Table routines

//      tableFWriteC: write table of parameters to FILE* in C language format
void    tableFWriteC(FILE *ptfp, int tableID);
//      tableWrite: write table of parameters to file in tabular format
void    tableWrite(const char *filename, int tableID);
//      tableRead: read table of parameters from file in tabular format
int     tableRead(const char *tablefile);
int     tableDef(int modelID, int tablenum, const char *tablename);

const char *tableInqNamePtr(int tableID);

int     tableInq(int modelID, int tablenum, const char *tablename);
int     tableInqNumber(void);

int     tableInqNum(int tableID);
int     tableInqModel(int tableID);

void    tableInqEntry(int tableID, int id, int ltype, char *name, char *longname, char *units);

// Subtype routines

//      subtypeCreate: Create a variable subtype
int     subtypeCreate(int subtype);

//      Gives a textual summary of the variable subtype
void    subtypePrint(int subtypeID);

// Compares two subtype data structures
int     subtypeCompare(int subtypeID1, int subtypeID2);

//      subtypeInqSize: Get the size of a subtype (e.g. no. of tiles)
int     subtypeInqSize(int subtypeID);

//      subtypeInqActiveIndex: Get the currently active index of a subtype (e.g. current tile index)
int     subtypeInqActiveIndex(int subtypeID);

//      subtypeDefActiveIndex: Set the currently active index of a subtype (e.g. current tile index)
void    subtypeDefActiveIndex(int subtypeID, int index);

//      Generate a "query object" out of a key-value pair
subtype_query_t keyValuePair(const char *key, int value);

//       Generate an AND-combined "query object" out of two previous query objects
subtype_query_t matchAND(subtype_query_t q1, subtype_query_t q2);

//      subtypeInqSubEntry: Returns subtype entry ID for a given criterion
int     subtypeInqSubEntry(int subtypeID, subtype_query_t criterion);

//      subtypeInqTile: Specialized version of subtypeInqSubEntry looking for tile/attribute pair
int     subtypeInqTile(int subtypeID, int tileindex, int attribute);

//      subtypeInqAttribute: Inquire the value of a subtype attribute. Returns CDI_EINVAL if the attribute does not exist.
int     subtypeInqAttribute(int subtypeID, int index, const char *key, int *outValue);

//      vlistInqVarSubtype: Return subtype ID for a given variable
int     vlistInqVarSubtype(int vlistID, int varID);

void gribapiLibraryVersion(int *major_version, int *minor_version, int *revision_version);

// Compatibility functions for release 1.8.3 (obsolete functions)
void zaxisDefLtype(int zaxisID, int ltype);
int  vlistInqVarTypeOfGeneratingProcess(int vlistID, int varID);
void vlistDefVarTypeOfGeneratingProcess(int vlistID, int varID, int typeOfGeneratingProcess);
void vlistDefVarProductDefinitionTemplate(int vlistID, int varID, int productDefinitionTemplate);

// Compatibility functions for ParaView vtkCDIReader (obsolete functions)
int vlistNgrids(int vlistID); // calls vlistNumGrids()
int vlistNzaxis(int vlistID); // calls vlistNumZaxis()

#ifdef __cplusplus
}
#endif

// End of fortran interface
//FINT_OFF  <--- don't change or remove this line!!!

#ifdef __cplusplus
extern "C" {
#endif

// CDI query interface

struct CdiQuery;

struct CdiQuery *cdiQueryCreate(void);
struct CdiQuery *cdiQueryClone(const struct CdiQuery *query);
void cdiQueryDelete(struct CdiQuery *query);
void cdiQuerySetNames(struct CdiQuery *query, int numNames, const char **names);
void cdiQuerySetCells(struct CdiQuery *query, int numCells, const size_t *cells);
void cdiQuerySetLayers(struct CdiQuery *query, int numLayers, const int *layers);
void cdiQuerySetSteps(struct CdiQuery *query, int numSteps, const int *steps);
size_t cdiQueryGetCell(const struct CdiQuery *query, int index);
int cdiQueryGetLayer(const struct CdiQuery *query, int index);
int cdiQueryGetStep(const struct CdiQuery *query, int index);
int cdiQueryName(struct CdiQuery *query, const char *name);
// int cdiQueryCell(struct CdiQuery *query, size_t cell);
// int cdiQueryLayer(struct CdiQuery *query, int layer);
// int cdiQueryStep(struct CdiQuery *query, int step);
int cdiQueryNumNames(const struct CdiQuery *query);
int cdiQueryNumCells(const struct CdiQuery *query);
int cdiQueryNumLayers(const struct CdiQuery *query);
int cdiQueryNumSteps(const struct CdiQuery *query);
int cdiQueryNumEntries(const struct CdiQuery *query);
int cdiQueryNumEntriesFound(const struct CdiQuery *query);
void cdiQueryPrint(const struct CdiQuery *query);
void cdiQueryPrintEntriesNotFound(const struct CdiQuery *query);

// streamOpenReadQuery: Open a dataset for reading and apply query
int streamOpenReadQuery(const char *path, struct CdiQuery *query);

// CDI interface for paraview vtkCDIReader.cxx


// taxisDefRdatetime: Define the reference date/time
void taxisDefRdatetime(int taxisID, CdiDateTime rDateTime);
// taxisInqRdatetime: Get the reference date/time
CdiDateTime taxisInqRdatetime(int taxisID);
// taxisDefFdatetime: Define the forecast reference date/time
void taxisDefFdatetime(int taxisID, CdiDateTime fDateTime);
// taxisInqFdatetime: Get the forecast reference date/time
CdiDateTime taxisInqFdatetime(int taxisID);
// taxisDefVdatetime: Define the verification date/time
void taxisDefVdatetime(int taxisID, CdiDateTime vDateTime);
// taxisInqVdatetime: Get the verification date/time
CdiDateTime taxisInqVdatetime(int taxisID);
void taxisDefVdatetimeBounds(int taxisID, CdiDateTime vDateTime_lb, CdiDateTime vDateTime_ub);
void taxisInqVdatetimeBounds(int taxisID, CdiDateTime *vDateTime_lb, CdiDateTime *vDateTime_ub);

// date format:  YYYYMMDD
// time format:    hhmmss

int64_t date_to_julday(int calendar, int64_t date);  // Used in paraview vtkCDIReader.cxx
int64_t julday_to_date(int calendar, int64_t julday);

int time_to_sec(int time);                           // Used in paraview vtkCDIReader.cxx
int sec_to_time(int secofday);

// CDI projection parameter interface

struct CDI_GridProjParams
{
  double mv;     // Missing value
  double lon_0;  // The East longitude of the meridian which is parallel to the Y-axis
  double lat_0;  // Latitude of the projection origin
  double lat_1;  // First latitude from the pole at which the secant cone cuts the sphere
  double lat_2;  // Second latitude at which the secant cone cuts the sphere
  // lat_ts = lat_1;
  double a;      // Semi-major axis or earth radius in metres (optional)
  double b;      // Semi-minor axis in metres (optional)
  double rf;     // Inverse flattening (1/f) (optional)
  double xval_0; // Longitude of the first grid point in degree (optional)
  double yval_0; // Latitude of the first grid point in degree (optional)
  double x_0;    // False easting (optional)
  double y_0;    // False northing (optional)
  double x_SP;   // Longitude of southern pole
  double y_SP;   // Latitude of southern pole
  int nside;     // HEALPix number of points along a side (number of data points should be = 12 * nside * nside)
  int order;     // HEALPix ordering convention (0:ring; 1:nested)
};

void gridProjParamsInit(struct CDI_GridProjParams *gridProjParams);

// Lambert Conformal Conic grid
void gridDefParamsLCC(int gridID, struct CDI_GridProjParams gridProjParams);
int  gridInqParamsLCC(int gridID, struct CDI_GridProjParams *gridProjParams);

// Polar stereographic grid
void gridDefParamsSTERE(int gridID, struct CDI_GridProjParams gridProjParams);
int  gridInqParamsSTERE(int gridID, struct CDI_GridProjParams *gridProjParams);

// HEALPix grid
void gridDefParamsHEALPIX(int gridID, struct CDI_GridProjParams gridProjParams);
int  gridInqParamsHEALPIX(int gridID, struct CDI_GridProjParams *gridProjParams);

#define HAVE_CDI_PROJ_FUNCS 1
extern int (*proj_lonlat_to_lcc_func)(struct CDI_GridProjParams gpp, size_t, double*, double*);
extern int (*proj_lcc_to_lonlat_func)(struct CDI_GridProjParams gpp, double, double, size_t, double*, double*);
extern int (*proj_lonlat_to_stere_func)(struct CDI_GridProjParams gpp, size_t, double*, double*);
extern int (*proj_stere_to_lonlat_func)(struct CDI_GridProjParams gpp, double, double, size_t, double*, double*);

// Used in CDO remap_scrip_io.cc
void cdf_def_var_filter(int ncid, int ncvarID, const char *filterSpec);

#ifdef __cplusplus
}
#endif

// clang-format on

#endif /* CDI_H_ */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _DMEMORY_H
#define _DMEMORY_H

// clang-format off

#include <stdio.h>

// if DEBUG_MEMORY is defined setenv MEMORY_DEBUG to debug memory
#define  DEBUG_MEMORY

#ifndef  WITH_FUNCTION_NAME
#define  WITH_FUNCTION_NAME
#endif

#ifdef  __cplusplus
extern "C" {
#endif

extern size_t  memTotal(void);
extern void    memDebug(int debug);
extern void    memExitOnError(void);

extern void   *memRealloc(void *ptr, size_t size, const char *file, const char *functionname, int line);
extern void   *memCalloc(size_t nobjs, size_t size, const char *file, const char *functionname, int line);
extern void   *memMalloc(size_t size, const char *file, const char *functionname, int line);
extern void    memFree(void *ptr, const char *file, const char *functionname, int line);

#ifdef  __cplusplus
}
#endif

#ifdef  DEBUG_MEMORY

#ifdef  WITH_FUNCTION_NAME
#define  Realloc(p, s)  memRealloc((p), (s), __FILE__, __func__, __LINE__)
#define   Calloc(n, s)   memCalloc((n), (s), __FILE__, __func__, __LINE__)
#define   Malloc(s)      memMalloc((s), __FILE__, __func__, __LINE__)
#define     Free(p)        memFree((p), __FILE__, __func__, __LINE__)
#else
#define  Realloc(p, s)  memRealloc((p), (s), __FILE__, (void *) NULL, __LINE__)
#define   Calloc(n, s)   memCalloc((n), (s), __FILE__, (void *) NULL, __LINE__)
#define   Malloc(s)      memMalloc((s), __FILE__, (void *) NULL, __LINE__)
#define     Free(p)        memFree((p), __FILE__, (void *) NULL, __LINE__)
#endif

#else

#include <stdlib.h>

#define  Realloc(p, s)  realloc((p), (s))
#define   Calloc(n, s)   calloc((n), (s))
#define   Malloc(s)      malloc((s))
#define     Free(p)        free((p))

#endif /* DEBUG_MEMORY */

// clang-format on

#endif /* _DMEMORY_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _ERROR_H_
#define _ERROR_H_

// clang-format off

#include <stdarg.h>
#include <stdlib.h>
#include <stdbool.h>

#ifndef  WITH_CALLER_NAME
#define  WITH_CALLER_NAME
#endif

#ifdef __cplusplus
extern "C" {
#endif

extern int _ExitOnError;  // If set to 1, exit on error (default 1)
extern int _Verbose;      // If set to 1, errors are reported (default 1)
extern int _Debug;        // If set to 1, debuggig (default 0)

void SysError_(const char *caller, const char *fmt, ...);
void    Error_(const char *caller, const char *fmt, ...);
void  Warning_(const char *caller, const char *fmt, ...);
/* delegate used by Warning_ unless mode is PIO */
void cdiWarning(const char *caller, const char *fmt, va_list ap);
void  Message_(const char *caller, const char *fmt, ...);

#ifdef WITH_CALLER_NAME
#  define  SysError(...)  SysError_(__func__, __VA_ARGS__)
#  define    Errorc(...)     Error_(  caller, __VA_ARGS__)
#  define     Error(...)     Error_(__func__, __VA_ARGS__)
#  define   Warning(...)   Warning_(__func__, __VA_ARGS__)
#  define  Messagec(...)   Message_(  caller, __VA_ARGS__)
#  define   Message(...)   Message_(__func__, __VA_ARGS__)
#else
#  define  SysError(...)  SysError_((void *), __VA_ARGS__)
#  define    Errorc(...)     Error_((void *), __VA_ARGS__)
#  define     Error(...)     Error_((void *), __VA_ARGS__)
#  define   Warning(...)   Warning_((void *), __VA_ARGS__)
#  define  Messagec(...)   Message_((void *), __VA_ARGS__)
#  define   Message(...)   Message_((void *), __VA_ARGS__)
#endif

/* If we're not using GNU C, elide __attribute__ */
#ifndef __GNUC__
#  define  __attribute__(x)  /*NOTHING*/
#endif

void cdiAbortC(const char *caller, const char *filename,
               const char *functionname, int line,
               const char *errorString, ... )
  __attribute__((noreturn));
#define xabortC(caller, ...)                                    \
  cdiAbortC(caller, __FILE__, __func__, __LINE__, __VA_ARGS__ )
#define xabort(...)                                             \
  cdiAbortC(NULL, __FILE__, __func__, __LINE__, __VA_ARGS__ )
#define cdiAbort(file, func, line, ...)                 \
  cdiAbortC(NULL, (file), (func), (line), __VA_ARGS__)

#define xassert(arg) do {                       \
    if ((arg)) { } else {                       \
      xabort("assertion `" #arg "` failed");}   \
  } while(0)

void
cdiAbortC_serial(const char *caller, const char *filename,
                 const char *functionname, int line,
                 const char *errorString, va_list ap)
  __attribute__((noreturn));


bool cdiObsoleteInfo(const char *oldFunction, const char *newFunction);

#if defined (__cplusplus)
}
#endif

// clang-format on

#endif /* _ERROR_H_ */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>
#endif



#include <stdbool.h>

#ifdef HAVE_LIBPTHREAD
#ifdef __APPLE__
#include <dispatch/dispatch.h>
#else
#include <errno.h>
#include <semaphore.h>
#endif

typedef struct sema
{
#ifdef __APPLE__
  dispatch_semaphore_t sem;
#else
  sem_t sem;
#endif
} sema_t;
#endif

struct AsyncJob
{
  bool inUse;
#ifdef HAVE_LIBPTHREAD
  sema_t request, completion;
#endif
  int (*work)(void *data);
  void *data;
  int result;
};

struct AsyncManager
{
  int workerCount, idleWorkerCount;
  AsyncJob *communicators;
};

#ifdef HAVE_LIBPTHREAD
static inline int
sema_init(sema_t *s, int pshared, uint32_t value)
{
  int status = 0;
#ifdef __APPLE__
  dispatch_semaphore_t *sem = &s->sem;

  (void) pshared;
  *sem = dispatch_semaphore_create(value);
#else
  status = sem_init(&s->sem, pshared, value);
#endif
  return status;
}

static inline int
sema_wait(sema_t *s)
{
#ifdef __APPLE__
  dispatch_semaphore_wait(s->sem, DISPATCH_TIME_FOREVER);
#else
  int r;

  do {
    r = sem_wait(&s->sem);
  } while (r == -1 && errno == EINTR);
#endif
  return 0;
}

static inline int
sema_post(sema_t *s)
{
#ifdef __APPLE__
  dispatch_semaphore_signal(s->sem);
#else
  sem_post(&s->sem);
#endif
  return 0;
}

static void *
workerMain(void *arg)
{
  AsyncJob *communicator = (AsyncJob *) arg;

  while (true)
  {
    while (sema_wait(&communicator->request))
      ;
    if (communicator->work)
    {
      communicator->result = communicator->work(communicator->data);
      if (sema_post(&communicator->completion)) xabort("sema_post() failed");
    }
    else
    {
      if (sema_post(&communicator->completion)) xabort("sema_post() failed");
      break;
    }
  }

  return NULL;
}

static void
startWorker(AsyncJob *communicator)
{
  communicator->inUse = false;
  communicator->work = NULL;
  communicator->data = NULL;
  communicator->result = 0;
  if (sema_init(&communicator->request, 0, 0)) xabort("sema_init() failed");
  if (sema_init(&communicator->completion, 0, 0)) xabort("sema_init() failed");

  pthread_t worker;
  if (pthread_create(&worker, NULL, workerMain, communicator)) xabort("pthread_create() failed");
  if (pthread_detach(worker)) xabort("pthread_detach() failed");
}
#endif

int
AsyncWorker_init(AsyncManager **jobManager, int threadCount)
{
  if (threadCount <= 0)
  {
    xabort("CPU core count discovery not implemented yet");
    return CDI_EINVAL;  // TODO: discover CPU core count, and set threadCount to a sensible positive value
  }

  if (*jobManager) return CDI_NOERR;

#ifdef HAVE_LIBPTHREAD
  AsyncManager *jobManager_ = *jobManager = (AsyncManager *) Malloc(sizeof(AsyncManager));
  jobManager_->workerCount = threadCount;
  jobManager_->communicators = (AsyncJob *) Malloc((size_t) threadCount * sizeof(AsyncJob));

  for (int i = 0; i < threadCount; i++) startWorker(jobManager_->communicators + i);
  jobManager_->idleWorkerCount = threadCount;
#else

  Error("pthread support not compiled in!");
#endif

  return CDI_NOERR;
}

AsyncJob *
AsyncWorker_requestWork(AsyncManager *jobManager, int (*work)(void *data), void *data)
{
  if (!jobManager) xabort("AsyncWorker_requestWork() called without calling AsyncWorker_init() first");
  if (!work)
    xabort("AsyncWorker_requestWork() called without a valid function pointer");  // need to catch this condition to stop users from
                                                                                  // terminating our worker threads

  // find an unused worker
  if (!jobManager->idleWorkerCount) return NULL;

  AsyncJob *worker = NULL;
  for (int i = 0; i < jobManager->workerCount; i++)
  {
    if (!jobManager->communicators[i].inUse)
    {
      worker = &jobManager->communicators[i];
      break;
    }
  }
  if (!worker) xabort("internal error: idleWorkerCount is not in sync with the worker states, please report this bug");

  // pass the request to that worker
  jobManager->idleWorkerCount--;
  worker->inUse = true;
  worker->work = work;
  worker->data = data;
  worker->result = 0;
#ifdef HAVE_LIBPTHREAD
  if (sema_post(&worker->request)) xabort("sema_post() failed");
#endif
  return worker;
}

int
AsyncWorker_wait(AsyncManager *jobManager, AsyncJob *job)
{
  if (!jobManager) xabort("AsyncWorker_wait() called without calling AsyncWorker_init() first");
  if (job < jobManager->communicators) return CDI_EINVAL;
  if (job >= jobManager->communicators + jobManager->workerCount) return CDI_EINVAL;
  if (!job->inUse) return CDI_EINVAL;

#ifdef HAVE_LIBPTHREAD
  while (sema_wait(&job->completion))
    ;
#endif
  int result = job->result;

  // reset the communicator
  job->work = NULL;
  job->data = NULL;
  job->result = 0;
  job->inUse = false;
  jobManager->idleWorkerCount++;

  return result;
}

int
AsyncWorker_availableWorkers(AsyncManager *jobManager)
{
  if (!jobManager) return 0;
  return jobManager->idleWorkerCount;
}

int
AsyncWorker_finalize(AsyncManager *jobManager)
{
  int result = CDI_NOERR;
  if (!jobManager) return CDI_NOERR;

  for (int i = 0; i < jobManager->workerCount; i++)
  {
    AsyncJob *curWorker = &jobManager->communicators[i];

    // finish any pending job
    if (curWorker->inUse)
    {
      AsyncWorker_wait(jobManager, curWorker);
      if (curWorker->result) result = curWorker->result;
    }

    // send the teardown signal
    curWorker->inUse = true;
    curWorker->work = NULL;
    curWorker->data = NULL;
    curWorker->result = 0;
#ifdef HAVE_LIBPTHREAD
    if (sema_post(&curWorker->request)) xabort("sema_post() failed");
#endif
    // wait for the worker to exit
    AsyncWorker_wait(jobManager, curWorker);
  }

  Free(jobManager->communicators);
  Free(jobManager);

  return result;
}
#ifndef _BASETIME_H
#define _BASETIME_H

#include <stdbool.h>

typedef struct
{
  int ncvarid;
  int ncdimid;
  int ncvarboundsid;
  int leadtimeid;
  bool hasUnits;
  bool isWRF;  // true for time axis in WRF format
} basetime_t;

void basetimeInit(basetime_t *basetime);

#endif /* _BASETIME_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stddef.h>  // for NULL

void
basetimeInit(basetime_t *basetime)
{
  if (basetime == NULL) Error("Internal problem! Basetime not allocated.");

  if (basetime)
  {
    basetime->ncvarid = CDI_UNDEFID;
    basetime->ncdimid = CDI_UNDEFID;
    basetime->ncvarboundsid = CDI_UNDEFID;
    basetime->leadtimeid = CDI_UNDEFID;
    basetime->hasUnits = false;
    basetime->isWRF = false;
  }
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _FILE_H
#define _FILE_H

#include <stdio.h>
#include <sys/types.h>

#define FILE_UNDEFID -1

#define FILE_TYPE_OPEN 1
#define FILE_TYPE_FOPEN 2

// buffer types for FILE_TYPE_OPEN
#define FILE_BUFTYPE_STD 1
#define FILE_BUFTYPE_MMAP 2

const char *fileLibraryVersion(void);

void fileDebug(int debug);

void *filePtr(int fileID);

int fileSetBufferType(int fileID, int type);
void fileSetBufferSize(int fileID, long buffersize);

int fileOpen(const char *filename, const char *mode);
int fileOpen_serial(const char *filename, const char *mode);
int fileClose(int fileID);
int fileClose_serial(int fileID);

char *fileInqName(int fileID);
int fileInqMode(int fileID);

int fileFlush(int fileID);
void fileClearerr(int fileID);
int fileEOF(int fileID);
int filePtrEOF(void *fileptr);
void fileRewind(int fileID);

off_t fileGetPos(int fileID);
int fileSetPos(int fileID, off_t offset, int whence);

int fileGetc(int fileID);
int filePtrGetc(void *fileptr);

size_t filePtrRead(void *fileptr, void *restrict ptr, size_t size);
size_t fileRead(int fileID, void *restrict ptr, size_t size);
size_t fileWrite(int fileID, const void *restrict ptr, size_t size);

#endif /* _FILE_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef SWAP_H_
#define SWAP_H_

void swap4byte(void *ptr, size_t size);
void swap8byte(void *ptr, size_t size);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef BINARY_H
#define BINARY_H

#ifdef HAVE_CONFIG_H
#endif

#include <stdio.h>
#include <inttypes.h>

#ifndef HOST_ENDIANNESS
#define HOST_ENDIANNESS (((const unsigned char *) &(const uint32_t[1]){ UINT32_C(0x00030201) })[0])
#endif

uint32_t get_uint32(unsigned char *x);
uint64_t get_uint64(unsigned char *x);
uint32_t get_swap_uint32(unsigned char *x);
uint64_t get_swap_uint64(unsigned char *x);

size_t binReadF77Block(int fileID, int byteswap);
void binWriteF77Block(int fileID, int byteswap, size_t blocksize);

int binReadInt32(int fileID, int byteswap, size_t size, int32_t *ptr);
int binReadInt64(int fileID, int byteswap, size_t size, int64_t *ptr);

int binWriteInt32(int fileID, int byteswap, size_t size, int32_t *ptr);
int binWriteInt64(int fileID, int byteswap, size_t size, int64_t *ptr);

int binWriteFlt32(int fileID, int byteswap, size_t size, float *ptr);
int binWriteFlt64(int fileID, int byteswap, size_t size, double *ptr);

#ifdef HAVE__FLOAT16
int binWriteFlt16(int fileID, size_t size, _Float16 *ptr);
#endif

#endif /* BINARY_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#define CDI_BIGENDIAN 0     // Byte order BIGENDIAN
#define CDI_LITTLEENDIAN 1  // Byte order LITTLEENDIAN

uint32_t
get_uint32(unsigned char *x)
{
  // clang-format off
  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      return (((uint32_t)x[0])<<24) + (((uint32_t)x[1])<<16) + (((uint32_t)x[2])<< 8) + (uint32_t)x[3];
    case CDI_LITTLEENDIAN:
      return (((uint32_t)x[3])<<24) + (((uint32_t)x[2])<<16) + (((uint32_t)x[1])<< 8) + (uint32_t)x[0];
    default:
      Error("Unhandled endianness %d", HOST_ENDIANNESS);
      return UINT32_C(0xFFFFFFFF);
    }
  // clang-format on
}

uint32_t
get_swap_uint32(unsigned char *x)
{
  // clang-format off
  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      return (((uint32_t)x[3])<<24) + (((uint32_t)x[2])<<16) + (((uint32_t)x[1])<< 8) + (uint32_t)x[0];
    case CDI_LITTLEENDIAN:
      return (((uint32_t)x[0])<<24) + (((uint32_t)x[1])<<16) + (((uint32_t)x[2])<< 8) + (uint32_t)x[3];
    default:
      Error("Unhandled endianness %d", HOST_ENDIANNESS);
      return UINT32_C(0xFFFFFFFF);
    }
  // clang-format on
}

uint64_t
get_uint64(unsigned char *x)
{
  // clang-format off
  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      return (((uint64_t)x[0])<<56) + (((uint64_t)x[1])<<48) + (((uint64_t)x[2])<<40) + (((uint64_t)x[3])<<32) +
             (((uint64_t)x[4])<<24) + (((uint64_t)x[5])<<16) + (((uint64_t)x[6])<< 8) +   (uint64_t)x[7];
    case CDI_LITTLEENDIAN:
      return (((uint64_t)x[7])<<56) + (((uint64_t)x[6])<<48) + (((uint64_t)x[5])<<40) + (((uint64_t)x[4])<<32) +
             (((uint64_t)x[3])<<24) + (((uint64_t)x[2])<<16) + (((uint64_t)x[1])<< 8) +   (uint64_t)x[0];
    default:
      Error("Unhandled endianness %d", HOST_ENDIANNESS);
      return UINT64_C(0xFFFFFFFFFFFFFFFF);
    }
  // clang-format on
}

uint64_t
get_swap_uint64(unsigned char *x)
{
  // clang-format off
  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      return (((uint64_t)x[7])<<56) + (((uint64_t)x[6])<<48) + (((uint64_t)x[5])<<40) + (((uint64_t)x[4])<<32) +
             (((uint64_t)x[3])<<24) + (((uint64_t)x[2])<<16) + (((uint64_t)x[1])<< 8) +   (uint64_t)x[0];
    case CDI_LITTLEENDIAN:
      return (((uint64_t)x[0])<<56) + (((uint64_t)x[1])<<48) + (((uint64_t)x[2])<<40) + (((uint64_t)x[3])<<32) +
             (((uint64_t)x[4])<<24) + (((uint64_t)x[5])<<16) + (((uint64_t)x[6])<< 8) +   (uint64_t)x[7];
    default:
      Error("Unhandled endianness %d", HOST_ENDIANNESS);
      return UINT64_C(0xFFFFFFFFFFFFFFFF);
    }
  // clang-format on
}

size_t
binReadF77Block(int fileID, int byteswap)
{
  unsigned char f77block[4];
  size_t blocklen = 0;

  if (fileRead(fileID, f77block, 4) == 4) { blocklen = byteswap ? get_swap_uint32(f77block) : get_uint32(f77block); }

  return blocklen;
}

void
binWriteF77Block(int fileID, int byteswap, size_t blocksize)
{
  static const unsigned int s[4] = { 0, 8, 16, 24 };
  const unsigned long ublocksize = (unsigned long) blocksize;
  unsigned char f77block[4];

  switch (HOST_ENDIANNESS)
  {
    case CDI_BIGENDIAN:
      if (byteswap)
      {
        for (int i = 0; i <= 3; ++i) f77block[i] = (unsigned char) (ublocksize >> s[i]);
      }
      else
      {
        for (int i = 0; i <= 3; ++i) f77block[3 - i] = (unsigned char) (ublocksize >> s[i]);
      }
      break;
    case CDI_LITTLEENDIAN:
      if (byteswap)
      {
        for (int i = 0; i <= 3; ++i) f77block[3 - i] = (unsigned char) (ublocksize >> s[i]);
      }
      else
      {
        for (int i = 0; i <= 3; ++i) f77block[i] = (unsigned char) (ublocksize >> s[i]);
      }
      break;
    default: Error("Unhandled endianness %d", HOST_ENDIANNESS);
  }

  if (fileWrite(fileID, f77block, 4) != 4) Error("Write failed on %s", fileInqName(fileID));
}

int
binReadInt32(int fileID, int byteswap, size_t size, int32_t *ptr)
{
  fileRead(fileID, (void *) ptr, 4 * size);
  if (byteswap) swap4byte(ptr, size);
  return 0;
}

int
binReadInt64(int fileID, int byteswap, size_t size, int64_t *ptr)
{
  fileRead(fileID, (void *) ptr, 8 * size);
  if (byteswap) swap8byte(ptr, size);
  return 0;
}

int
binWriteInt32(int fileID, int byteswap, size_t size, int32_t *ptr)
{
  if (byteswap) swap4byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 4 * size);
  return 0;
}

int
binWriteInt64(int fileID, int byteswap, size_t size, int64_t *ptr)
{
  if (byteswap) swap8byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 8 * size);
  return 0;
}

int
binWriteFlt32(int fileID, int byteswap, size_t size, float *ptr)
{
  if (byteswap) swap4byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 4 * size);
  return 0;
}

int
binWriteFlt64(int fileID, int byteswap, size_t size, double *ptr)
{
  if (byteswap) swap8byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 8 * size);
  return 0;
}

#ifdef HAVE__FLOAT16
int
binWriteFlt16(int fileID, size_t size, _Float16 *ptr)
{
  fileWrite(fileID, (void *) ptr, 2 * size);
  return 0;
}
#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CALENDAR_H
#define CALENDAR_H

#include <stdint.h>  // int64_t

// clang-format off

#ifdef __cplusplus
extern "C" {
#endif

void decode_calday(int daysPerYear, int64_t days, int *year, int *month, int *day);
int64_t encode_calday(int daysPerYear, int year, int month, int day);

static inline int
calendar_dpy(int calendar)
{
  int daysPerYear = 0;

  if      (calendar == CALENDAR_360DAYS) daysPerYear = 360;
  else if (calendar == CALENDAR_365DAYS) daysPerYear = 365;
  else if (calendar == CALENDAR_366DAYS) daysPerYear = 366;

  return daysPerYear;
}

int days_per_year(int calendar, int year);
int days_per_month(int calendar, int year, int month);

#ifdef __cplusplus
}
#endif

// clang-format on

#endif
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>


static const int month_360[12] = { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 };
static const int month_365[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static const int month_366[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static const int *
get_dayspermonth_array(int daysPerYear)
{
  // clang-format off
  return  (daysPerYear == 360) ? month_360 :
          (daysPerYear == 365) ? month_365 :
          (daysPerYear == 366) ? month_366 : NULL;
  // clang-format on
}

int
days_per_month(int calendar, int year, int month)
{
  int daysPerYear = calendar_dpy(calendar);
  const int *daysPerMonthArray = (daysPerYear == 360) ? month_360 : ((daysPerYear == 365) ? month_365 : month_366);

  int daysPerMonth = (month >= 1 && month <= 12) ? daysPerMonthArray[month - 1] : 0;

  if (daysPerYear == 0 && month == 2) daysPerMonth = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ? 29 : 28;

  return daysPerMonth;
}

int
days_per_year(int calendar, int year)
{
  int daysPerYear = calendar_dpy(calendar);
  if (daysPerYear == 0)
  {
    if (year == 1582 && (calendar == CALENDAR_STANDARD || calendar == CALENDAR_GREGORIAN))
      daysPerYear = 355;
    else if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
      daysPerYear = 366;
    else
      daysPerYear = 365;
  }

  return daysPerYear;
}

void
decode_calday(int daysPerYear, int64_t days, int *year, int *month, int *day)
{
  *year = (int) ((days - 1) / daysPerYear);
  days -= (*year * daysPerYear);

  const int *daysPerMonthArray = get_dayspermonth_array(daysPerYear);

  int i = 0;
  if (daysPerMonthArray)
    for (i = 0; i < 12; i++)
    {
      if (days > daysPerMonthArray[i])
        days -= daysPerMonthArray[i];
      else
        break;
    }

  *month = i + 1;
  *day = (int) days;
}

int64_t
encode_calday(int daysPerYear, int year, int month, int day)
{
  int64_t rval = (int64_t) daysPerYear * year + day;

  const int *daysPerMonthArray = get_dayspermonth_array(daysPerYear);

  if (daysPerMonthArray)
    for (int i = 0; i < month - 1; i++) rval += daysPerMonthArray[i];

  return rval;
}
#ifndef CDF_H
#define CDF_H

void cdfDebug(int debug);

extern int CDF_Debug;

const char *cdfLibraryVersion(void);

int cdfOpen(const char *filename, const char *mode, int filetype);
int cdf4Open(const char *filename, const char *mode, int *filetype);
void cdfClose(int fileID);

#endif /* CDF_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDI_FDB_H
#define CDI_FDB_H

#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBFDB5

#include <fdb5/api/fdb_c.h>

typedef struct
{
  char *item;
  char *keys[32];
  char *values[32];
  int numKeys;
} KeyValueItem;

typedef struct
{
  char *keys[32];
  char *values[32];
  int numKeys;
} fdbKeyValueEntry;

typedef struct
{
  int fdbIndex;
  int date;
  int time;
  int param;
  int levtype;
  int ilevel;
} RecordInfoEntry;

void check_fdb_error(int errorNum);
void cdi_fdb_delete_kvlist(int numItems, fdbKeyValueEntry *keyValueList);
void decode_fdbitem(const char *fdbItem, KeyValueItem *keyValue);
int cdi_fdb_fill_kvlist(fdb_handle_t *fdb, fdb_request_t *request, fdbKeyValueEntry **keyValueList);
long cdi_fdb_read_record(fdb_handle_t *fdb, const fdbKeyValueEntry *keyValue, size_t *buffersize, void **gribbuffer);
// int check_keyvalueList(int numItems, fdbKeyValueEntry *keyValueList);
void print_keyvalueList(int numItems, fdbKeyValueEntry *keyValueList);
void print_keyvalueList_sorted(int numItems, fdbKeyValueEntry *keyValueList, RecordInfoEntry *recordInfoList);
void cdi_fdb_sort_datetime(int numItems, RecordInfoEntry *recordInfo);
int get_num_records(int numItems, RecordInfoEntry *recordInfoList);
int decode_keyvalue(int numItems, fdbKeyValueEntry *keyValueList, RecordInfoEntry *recordInfoList);
int remove_duplicate_timesteps(RecordInfoEntry *recordInfoList, int numRecords, int numTimesteps, int *timestepRecordOffset);
fdb_request_t *cdi_create_fdb_request(const char *filename);

#endif

#endif /* CDI_FDB_H */
#ifndef JULIAN_DATE_H
#define JULIAN_DATE_H


// clang-format off

#ifdef __cplusplus
extern "C" {
#endif

typedef struct
{
  int64_t julianDay;
  double secondOfDay;
} JulianDate;

JulianDate julianDate_encode(int calendar, CdiDateTime dt);
CdiDateTime julianDate_decode(int calendar, JulianDate julianDate);
JulianDate julianDate_add_seconds(JulianDate julianDate, int64_t seconds);
JulianDate julianDate_add(JulianDate julianDate1, JulianDate julianDate2);
JulianDate julianDate_sub(JulianDate julianDate1, JulianDate julianDate2);
double julianDate_to_seconds(JulianDate julianDate);

double secofday_encode(CdiTime time);
CdiTime secofday_decode(double secondOfDay);

#ifdef __cplusplus
}
#endif

// clang-format on

#endif /* JULIAN_DATE_H */
#ifndef CDI_LIMITS_H
#define CDI_LIMITS_H

#define MAX_DIMENSIONS 5     // maximum number of dimensions per variable
#define MAX_DIMS_PS 16       // maximum number of dimensions per stream
#define MAX_GRIDS_PS 128     // maximum number of different grids per stream
#define MAX_ZAXES_PS 128     // maximum number of different zaxes per stream
#define MAX_ATTRIBUTES 256   // maximum number of attributes per variable
#define MAX_KEYS 64          // maximum number of keys per variable
#define MAX_SUBTYPES_PS 128  // maximum number of different subtypes per stream

#endif /* CDI_LIMITS_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDI_KEY_H
#define CDI_KEY_H

#include <stdio.h>
#include <stdint.h>

// CDI key
typedef struct
{
  uint16_t key;   // CDI key
  uint16_t type;  // KEY_INT, KEY_FLOAT, KEY_BYTES
  int length;     // number of bytes in v.s
  union
  {
    int i;
    double d;
    unsigned char *s;
  } v;
} cdi_key_t;

typedef struct
{
  uint16_t nalloc;  // number allocated >= nelems
  uint16_t nelems;  // length of the array
  cdi_key_t value[MAX_KEYS];
} cdi_keys_t;

enum
{
  KeyInt = 1,
  KeyFloat,
  KeyBytes
};

void cdiDefVarKeyInt(cdi_keys_t *keysp, int key, int value);
void cdiDefVarKeyFloat(cdi_keys_t *keysp, int key, double value);
void cdiDefVarKeyBytes(cdi_keys_t *keysp, int key, const unsigned char *bytes, int length);
int cdiInqVarKeyInt(const cdi_keys_t *keysp, int key);
int cdiInqVarKeyBytes(const cdi_keys_t *keysp, int key, unsigned char *bytes, int *length);

cdi_key_t *find_key(cdi_keys_t *keysp, int key);
const char *cdiInqVarKeyStringPtr(const cdi_keys_t *keysp, int key);

static inline const char *
cdiInqVarKeyString(const cdi_keys_t *keysp, int key)
{
  const char *string = cdiInqVarKeyStringPtr(keysp, key);
  if (string == NULL) string = "";
  return string;
}

int cdiCopyVarKey(const cdi_keys_t *keysp1, int key, cdi_keys_t *keysp2);
void cdiCopyVarKeys(const cdi_keys_t *keysp1, cdi_keys_t *keysp2);
void cdiDeleteVarKeys(cdi_keys_t *keysp);
void cdiDeleteKeys(int cdiID, int varID);
void cdiPrintKeys(int cdiID, int varID);

void cdiInitKeys(cdi_keys_t *keysp);

int cdi_key_compare(cdi_keys_t *keyspa, cdi_keys_t *keyspb, int keynum);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef RESOURCE_HANDLE_H
#define RESOURCE_HANDLE_H

#ifdef HAVE_CONFIG_H
#endif

#include <stdio.h>

/*
 * CDI internal handling of resource handles given to user code
 */

/*
 * for reasons of compatibility with cfortran.h, the handle type is: int
 */
typedef int cdiResH;

/* return 0 on equality, not 0 otherwise */
typedef int (*valCompareFunc)(void *, void *);
typedef void (*valDestroyFunc)(void *);
typedef void (*valPrintFunc)(void *, FILE *);
typedef int (*valGetPackSizeFunc)(void *, void *context);
typedef void (*valPackFunc)(void *, void *buf, int size, int *pos, void *context);
typedef int (*valTxCodeFunc)(void *);

typedef struct
{
  valCompareFunc valCompare;
  valDestroyFunc valDestroy;
  valPrintFunc valPrint;
  valGetPackSizeFunc valGetPackSize;
  valPackFunc valPack;
  valTxCodeFunc valTxCode;
} resOps;

enum
{
  RESH_IN_USE_BIT = 1 << 0,
  RESH_SYNC_BIT = 1 << 1,
  /* resource holds no value */
  RESH_UNUSED = 0,
  /* resource was deleted and needs to be synced */
  RESH_DESYNC_DELETED = RESH_SYNC_BIT,
  /* resource is synchronized */
  RESH_IN_USE = RESH_IN_USE_BIT,
  /* resource is in use, desynchronized and needs to be synced */
  RESH_DESYNC_IN_USE = RESH_IN_USE_BIT | RESH_SYNC_BIT,
};

void reshListCreate(int namespaceID);
void reshListDestruct(int namespaceID);
int reshPut(void *, const resOps *);
void reshReplace(cdiResH resH, void *p, const resOps *ops);
void reshRemove(cdiResH, const resOps *);
/*> doesn't check resource type */
void reshDestroy(cdiResH);

unsigned reshCountType(const resOps *resTypeOps);

void *reshGetValue(const char *caller, const char *expressionString, cdiResH id, const resOps *ops);
#define reshGetVal(resH, ops) reshGetValue(__func__, #resH, resH, ops)

void reshGetResHListOfType(unsigned numIDs, int IDs[], const resOps *ops);

enum cdiApplyRet
{
  CDI_APPLY_ERROR = -1,
  CDI_APPLY_STOP,
  CDI_APPLY_GO_ON,
};
enum cdiApplyRet cdiResHApply(enum cdiApplyRet (*func)(int id, void *res, const resOps *p, void *data), void *data);
enum cdiApplyRet cdiResHFilterApply(const resOps *p, enum cdiApplyRet (*func)(int id, void *res, void *data), void *data);

int reshPackBufferCreate(char **packBuf, int *packBufSize, void *context);
void reshPackBufferDestroy(char **);
int reshResourceGetPackSize_intern(int resh, const resOps *ops, void *context, const char *caller, const char *expressionString);
#define reshResourceGetPackSize(resh, ops, context) reshResourceGetPackSize_intern(resh, ops, context, __func__, #resh)
void reshPackResource_intern(int resh, const resOps *ops, void *buf, int buf_size, int *position, void *context, const char *caller,
                             const char *expressionString);
#define reshPackResource(resh, ops, buf, buf_size, position, context) \
  reshPackResource_intern(resh, ops, buf, buf_size, position, context, __func__, #resh)

void reshSetStatus(cdiResH, const resOps *, int);
int reshGetStatus(cdiResH, const resOps *);

void reshLock(void);
void reshUnlock(void);

enum reshListMismatch
{
  cdiResHListOccupationMismatch,
  cdiResHListResourceTypeMismatch,
  cdiResHListResourceContentMismatch,
};

int reshListCompare(int nsp0, int nsp1);
void reshListPrint(FILE *fp);
int reshGetTxCode(cdiResH resH);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef TAXIS_H
#define TAXIS_H

#include <stdbool.h>


#ifndef RESOURCE_HANDLE_H
#endif

typedef struct
{
  int self;
  int type;  // time type
  int calendar;
  int unit;  // time units
  int numavg;
  CdiDateTime sDateTime;     // start date/time
  CdiDateTime vDateTime;     // verification date/time
  CdiDateTime rDateTime;     // reference date/time
  CdiDateTime fDateTime;     // forecast reference date/time
  CdiDateTime vDateTime_lb;  // lower bounds of verification date/time
  CdiDateTime vDateTime_ub;  // upper bounds of verification date/time
  double fc_period;          // forecast time period
  int fc_unit;               // forecast time unit
  char *name;
  char *longname;
  char *units;
  bool climatology;
  bool hasBounds;
  cdi_keys_t keys;
} taxis_t;

//      taxisInqSdatetime: Get the start date/time
CdiDateTime taxisInqSdatetime(int taxisID);

void ptaxisInit(taxis_t *taxis);
void ptaxisCopy(taxis_t *dest, taxis_t *source);
taxis_t *taxis_to_pointer(int taxisID);
void cdi_set_forecast_period(double timevalue, taxis_t *taxis);
CdiDateTime cdi_decode_timeval(double timevalue, const taxis_t *taxis);
double cdi_encode_timeval(CdiDateTime datetime, taxis_t *taxis);

void ptaxisDefDatatype(taxis_t *taxisptr, int datatype);
void ptaxisDefName(taxis_t *taxisptr, const char *name);
void ptaxisDefLongname(taxis_t *taxisptr, const char *longname);
void ptaxisDefUnits(taxis_t *taxisptr, const char *units);
char *ptaxisAllocUnits(taxis_t *taxisptr, size_t len);
void taxisDestroyKernel(taxis_t *taxisptr);
#ifndef SX
extern const resOps taxisOps;
#endif

int taxisUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context,
                int checkForSameID);

enum
{
  TAXIS_MAX_UNIT_STR_LEN = 9
};

#endif /* TAXIS_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _SERVICE_H
#define _SERVICE_H

#include <stdlib.h>

typedef struct
{
  int checked;
  int byteswap;
  int header[8];
  int hprec; /* header precision */
  int dprec; /* data   precision */
  size_t datasize;
  size_t buffersize;
  void *buffer;
} srvrec_t;

const char *srvLibraryVersion(void);

void srvDebug(int debug);

int srvCheckFiletype(int fileID, int *swap);

void *srvNew(void);
void srvDelete(void *srv);

int srvRead(int fileID, void *srv);
void srvWrite(int fileID, void *srv);

int srvInqHeader(void *srv, int *header);
int srvInqDataFP32(void *srv, float *data);
int srvInqDataFP64(void *srv, double *data);

int srvDefHeader(void *srv, const int *header);
int srvDefDataFP32(void *srv, const float *data);
int srvDefDataFP64(void *srv, const double *data);

#endif /* _SERVICE_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _EXTRA_H
#define _EXTRA_H

#ifdef HAVE_CONFIG_H
#endif

#include <stdlib.h>

enum
{
  EXT_REAL = 1,
  EXT_COMP = 2,
};

typedef struct
{
  int checked;
  int byteswap;
  int header[4];
  int prec;    // single or double precison
  int number;  // real or complex
  size_t datasize;
  size_t buffersize;
  void *buffer;
} extrec_t;

const char *extLibraryVersion(void);

void extDebug(int debug);

int extCheckFiletype(int fileID, int *swap);

void *extNew(void);
void extDelete(void *ext);

int extRead(int fileID, void *ext);
int extWrite(int fileID, void *ext);

int extInqHeader(void *ext, int *header);
int extInqDataFP32(void *ext, float *data);
int extInqDataFP64(void *ext, double *data);

int extDefHeader(void *ext, const int *header);
int extDefDataFP32(void *ext, const float *data);
int extDefDataFP64(void *ext, const double *data);

#ifdef HAVE__FLOAT16
int extInqDataFP16(void *ext, _Float16 *data);
int extDefDataFP16(void *ext, const _Float16 *data);
#endif

#endif /* _EXTRA_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _IEG_H
#define _IEG_H

#include <stdlib.h>

// clang-format off

/* Level Types */
#define  IEG_LTYPE_SURFACE               1
#define  IEG_LTYPE_99                   99
#define  IEG_LTYPE_ISOBARIC            100
#define  IEG_LTYPE_MEANSEA             102
#define  IEG_LTYPE_ALTITUDE            103
#define  IEG_LTYPE_HEIGHT              105
#define  IEG_LTYPE_SIGMA               107
#define  IEG_LTYPE_HYBRID              109
#define  IEG_LTYPE_HYBRID_LAYER        110
#define  IEG_LTYPE_LANDDEPTH           111
#define  IEG_LTYPE_LANDDEPTH_LAYER     112
#define  IEG_LTYPE_SEADEPTH            160

/*
 *  Data representation type (Grid Type) [Table 6]
 */
#define  IEG_GTYPE_LATLON             0  /*  latitude/longitude                       */
#define  IEG_GTYPE_LATLON_ROT        10  /*  rotated latitude/longitude               */

#define  IEG_P_CodeTable(x)   (x[ 5])  /*  Version number of code table                 */
#define  IEG_P_Parameter(x)   (x[ 6])  /*  Parameter indicator                          */
#define  IEG_P_LevelType(x)   (x[ 7])  /*  Type of level indicator                      */
#define  IEG_P_Level1(x)      (x[ 8])  /*  Level 1                                      */
#define  IEG_P_Level2(x)      (x[ 9])  /*  Level 2                                      */
#define  IEG_P_Year(x)        (x[10])  /*  Year of century (YY)                         */
#define  IEG_P_Month(x)       (x[11])  /*  Month (MM)                                   */
#define  IEG_P_Day(x)         (x[12])  /*  Day (DD)                                     */
#define  IEG_P_Hour(x)        (x[13])  /*  Hour (HH)                                    */
#define  IEG_P_Minute(x)      (x[14])  /*  Minute (MM)                                  */

/*
 *  Macros for the grid definition section ( Section 2 )
 */
#define  IEG_G_Size(x)        (x[ 0])
#define  IEG_G_NumVCP(x)      (x[3] == 10 ? (x[0]-42)/4 : (x[0]-32)/4)
#define  IEG_G_GridType(x)    (x[ 3])  /*  Data representation type */
#define  IEG_G_NumLon(x)      (x[ 4])  /*  Number of points along a parallel (Ni)       */
#define  IEG_G_NumLat(x)      (x[ 5])  /*  Number of points along a meridian (Nj)       */
#define  IEG_G_FirstLat(x)    (x[ 6])  /*  Latitude of the first grid point             */
#define  IEG_G_FirstLon(x)    (x[ 7])  /*  Longitude of the first grid point            */
#define  IEG_G_ResFlag(x)     (x[ 8])  /*  Resolution flag: 128 regular grid            */
#define  IEG_G_LastLat(x)     (x[ 9])  /*  Latitude of the last grid point              */
#define  IEG_G_LastLon(x)     (x[10])  /*  Longitude of the last grid point             */
#define  IEG_G_LonIncr(x)     (x[11])  /*  i direction increment                        */
#define  IEG_G_LatIncr(x)     (x[12])  /*  j direction increment                        */
#define  IEG_G_ScanFlag(x)    (x[13])
#define  IEG_G_LatSP(x)       (x[16])  /*  Latitude of the southern pole of rotation    */
#define  IEG_G_LonSP(x)       (x[17])  /*  Longitude of the southern pole of rotation   */
#define  IEG_G_ResFac(x)      (x[18])  /*  Resolution factor                            */

// clang-format on

typedef struct
{
  int checked;
  int byteswap;
  int dprec; /* data   precision */
  int ipdb[37];
  double refval;
  int igdb[22];
  double vct[100];
  size_t datasize;
  size_t buffersize;
  void *buffer;
} iegrec_t;

const char *iegLibraryVersion(void);

void iegDebug(int debug);
int iegCheckFiletype(int fileID, int *swap);

void *iegNew(void);
void iegDelete(void *ieg);
void iegInitMem(void *ieg);

int iegRead(int fileID, void *ieg);
int iegWrite(int fileID, void *ieg);

void iegCopyMeta(void *dieg, void *sieg);
int iegInqHeader(void *ieg, int *header);
int iegInqDataFP32(void *ieg, float *data);
int iegInqDataFP64(void *ieg, double *data);

int iegDefHeader(void *ieg, const int *header);
int iegDefDataFP32(void *ieg, const float *data);
int iegDefDataFP64(void *ieg, const double *data);

#endif /* _IEG_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDI_INT_H
#define CDI_INT_H

// strdup() from string.h
#ifdef __STDC_ALLOC_LIB__
#define __STDC_WANT_LIB_EXT2__ 1
#else
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif

#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBFDB5
#endif

#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <sys/types.h>


#ifdef HAVE_LIBPTHREAD
#include <pthread.h>
extern pthread_mutex_t CDI_IO_Mutex;
#define CDI_IO_LOCK() pthread_mutex_lock(&CDI_IO_Mutex)
#define CDI_IO_UNLOCK() pthread_mutex_unlock(&CDI_IO_Mutex)
#else
#define CDI_IO_LOCK()
#define CDI_IO_UNLOCK()
#endif

// Base file types

#define CDI_FILETYPE_GRIB 100    // File type GRIB
#define CDI_FILETYPE_NETCDF 101  // File type NetCDF

// dummy use of unused parameters to silence compiler warnings
#ifndef UNUSED
#define UNUSED(x) (void) x
#endif

char *str_to_lower(char *str);
bool strStartsWith(const char *vstr, const char *cstr);

static inline bool
str_is_equal(const char *x, const char *y)
{
  return (*x == *y) && (strcmp(x, y) == 0);
}

#ifndef M_PI
#define M_PI 3.14159265358979323846 /* pi */
#endif

#ifndef ERROR_H
#endif
#ifndef _BASETIME_H
#endif
#ifndef JULIAN_DATE_H
#endif
#ifndef TAXIS_H
#endif
#ifndef CDI_LIMITS_H
#endif
#ifndef _SERVICE_H
#endif
#ifndef _EXTRA_H
#endif
#ifndef _IEG_H
#endif
#ifndef RESOURCE_HANDLE_H
#endif

#define check_parg(arg) \
  if (arg == 0) Warning("Argument '" #arg "' not allocated!")

#ifdef __xlC__ /* performance problems on IBM */
#ifndef DBL_IS_NAN
#define DBL_IS_NAN(x) ((x) != (x))
#endif
#else
#ifndef DBL_IS_NAN
#if defined(HAVE_DECL_ISNAN)
#define DBL_IS_NAN(x) (isnan(x))
#elif defined(FP_NAN)
#define DBL_IS_NAN(x) (fpclassify(x) == FP_NAN)
#else
#define DBL_IS_NAN(x) ((x) != (x))
#endif
#endif
#endif

#ifndef DBL_IS_EQUAL
// #define DBL_IS_EQUAL(x,y) (!(x < y || y < x))
#define DBL_IS_EQUAL(x, y) (DBL_IS_NAN(x) || DBL_IS_NAN(y) ? (DBL_IS_NAN(x) && DBL_IS_NAN(y)) : !(x < y || y < x))
#endif

#ifndef IS_EQUAL
#define IS_NOT_EQUAL(x, y) (x < y || y < x)
#define IS_EQUAL(x, y) (!IS_NOT_EQUAL(x, y))
#endif

enum
{
  TYPE_REC,
  TYPE_VAR,
};

enum
{
  MEMTYPE_DOUBLE = 1,
  MEMTYPE_FLOAT,
};

typedef struct
{
  void *buffer;       // gribapi, cgribex
  size_t buffersize;  // gribapi, cgribex
  off_t position;     // file position
  int param;
  int ilevel;
  int vdate;
  int vtime;
  int gridID;
  int varID;
  int levelID;
  int prec;       // ext, srv
  void *objectp;  // pointer to ieg, ext, srv or cgribex objects
} Record;

// data structure specifying tile-related meta-data. structure contains "-1" if this is no tile-variable.
typedef struct
{
  int tileindex, totalno_of_tileattr_pairs, tileClassification, numberOfTiles, numberOfAttributes, attribute;
} var_tile_t;

typedef struct
{
  short perturbationNumber;
  short typeOfGeneratingProcess;
} VarScanKeys;

static inline void
varScanKeysInit(VarScanKeys *s)
{
  memset(s, 0, sizeof(VarScanKeys));
}

static inline bool
varScanKeysIsEqual(const VarScanKeys *s1, const VarScanKeys *s2)
{
  return memcmp(s1, s2, sizeof(VarScanKeys)) == 0;
}

typedef struct
{
  int levelID;
  short varID;
  short used;
} recinfo_t;

typedef struct
{
  off_t position;
  size_t size;
  size_t gridsize;
  int param;
  int ilevel;
  int ilevel2;
  short ltype;
  short tsteptype;
#ifdef HAVE_LIBGRIB
  int zip;
  VarScanKeys scanKeys;
  var_tile_t tiles;  // tile-related meta-data, currently for GRIB-API only.
#ifdef HAVE_LIBGRIB_API
  char varname[32];
#endif
#endif
#ifdef HAVE_LIBFDB5
  int fdbItemIndex;
#endif
} record_t;

typedef struct
{
  int *recIDs;  // IDs of non constant records
  recinfo_t *recinfo;
  record_t *records;
  int recordSize;   // number of allocated records
  int nrecs;        // number of used records
                    // tsID=0 nallrecs
                    // tsID>0 number of non constant records
  int nallrecs;     // number of all records
  int curRecID;     // current record ID
  int ncStepIndex;  // NetCDF timestep index
  off_t position;   // timestep file position
  taxis_t taxis;
  bool next;
} tsteps_t;

typedef struct
{
  int nlevs;
  int subtypeIndex;  // corresponding tile in subtype_t structure (subtype->self)
  int *recordID;     // record IDs: [nlevs]
  int *lindex;       // level index
} sleveltable_t;

typedef struct
{
  int ilev;
  int mlev;
  int ilevID;
  int mlevID;
} VCT;

#ifdef HAVE_LIBNETCDF
enum cdfIDIdx
{
  CDF_DIMID_E,  // 3rd dimID of cube sphere grid (len=6)
  CDF_DIMID_X,
  CDF_DIMID_Y,
  CDF_DIMID_RP,  // reducedPoints
  CDF_VARID_X,
  CDF_VARID_Y,
  CDF_VARID_RP,  // reducedPoints
  CDF_VARID_A,
  CDF_SIZE_NCID,
};

typedef struct
{
  void *buffer;
  size_t start[MAX_DIMENSIONS];
  size_t count[MAX_DIMENSIONS];
  size_t bufferSize;
  size_t cacheSize;
  size_t numSteps;
  size_t maxSteps;
  int numDims;
} CdfCache;

typedef struct
{
  int ncIdList[CDF_SIZE_NCID];
  int gridID;
  long start;
  long count;
} CdfGrid;

typedef struct
{
  int zaxisID;
  long start;
  long count;
} CdfZaxis;

typedef struct
{
  CdfGrid cdfGridList[MAX_GRIDS_PS];
  CdfZaxis cdfZaxisList[MAX_ZAXES_PS];
  size_t chunkSizeDimT;
  size_t chunkSizeDimZ;
  size_t ncDimLenList[MAX_DIMS_PS];
  int ncDimIdList[MAX_DIMS_PS];
  int zaxisIdList[MAX_ZAXES_PS];  // Warning: synchronous array to vlist_to_pointer(vlistID)->zaxisIDs
  int ncZvarIdList[MAX_ZAXES_PS];
  int ncNumDims;
  int complexFloatId;
  int complexDoubleId;
  VCT vct;
} CdfInfo;
#endif

typedef struct
{
#ifdef HAVE_LIBNETCDF
  CdfCache *cdfCache;
#endif
  sleveltable_t *recordTable;  // record IDs for each subtype
  int ncvarid;
  int subtypeSize;
  bool defmiss;  // true: if missval is defined in file
  bool isUsed;

  int gridID;
  int zaxisID;
  int tsteptype;  // TSTEP_*
  int subtypeID;  // subtype ID, e.g. for tile-related meta-data (currently for GRIB-API only).
} svarinfo_t;

typedef struct
{
  int self;
  int accesstype;  // TYPE_REC or TYPE_VAR
  int accessmode;
  int filetype;
  int byteorder;
  int fileID;
  int filemode;
  int nrecs;  // number of records
  SizeType numvals;
  char *filename;
  Record *record;
  struct CdiQuery *query;
  svarinfo_t *vars;
  int nvars;  // number of variables
  int varsAllocated;
  int curTsID;   // current timestep ID
  int rtsteps;   // number of tsteps accessed
  long ntsteps;  // number of tsteps : only set if all records accessed
  int maxSteps;  // max. number of timesteps (needed for CDI_FILETYPE_NCZARR)
  tsteps_t *tsteps;
  int tstepsTableSize;
  int tstepsNextID;
  basetime_t basetime;
  int ncmode;
  int vlistID;
#ifdef HAVE_LIBNETCDF
  CdfInfo cdfInfo;
#endif
  long maxGlobalRecs;
  int globalatts;
  int localatts;
  int unreduced;
  int have_missval;
  int shuffle;
  // netcdf4/HDF5 filter
  char *filterSpec;

  int comptype;   // compression type
  int complevel;  // compression level
  bool sortname;
  bool lockIO;

  void *gribContainers;

  int numWorker;
  int nextGlobalRecId;
  int cachedTsID;
  void *jobs;
  void *jobManager;

  int protocol;
  void *protocolData;

#ifdef HAVE_LIBFDB5
  int fdbNumItems;
  fdbKeyValueEntry *fdbKeyValueList;
#endif
} stream_t;

// Length of optional keyword/value pair list
#define MAX_OPT_GRIB_ENTRIES 500

enum cdi_convention
{
  CDI_CONVENTION_ECHAM,
  CDI_CONVENTION_CF
};

// Data type specification for optional key/value pairs (GRIB)
typedef enum
{
  t_double = 0,
  t_int = 1,
  t_intarr = 2,
  t_doublearr = 3
} key_val_pair_datatype;

// Data structure holding optional key/value pairs for GRIB
typedef struct
{
  char *keyword;  // keyword string
  bool update;
  key_val_pair_datatype data_type;  // data type of this key/value pair
  double dbl_val;                   // double value (data_type == t_double)
  int int_val;                      // integer value (data_type == t_int)
  int *int_arr;                     // pointer to integer array (if data_type == t_intarr)
  double *dbl_arr;                  // pointer to double array (if data_type == t_doublearr)
  size_t arr_len;                   // length of array (if array type)
  int subtype_index;                // tile index for this key-value pair
} opt_key_val_pair_t;

// enum for differenciating between the different times that we handle
typedef enum
{
  kCdiTimeType_referenceTime,
  kCdiTimeType_startTime,
  kCdiTimeType_endTime
} CdiTimeType;

#define CDI_FILETYPE_UNDEF -1  // Unknown/not yet defined file type

extern int cdiDebugExt;
extern int CDI_Debug;  // If set to 1, debuggig (default 0)
extern int CDI_Recopt;
extern bool CDI_gribapi_debug;
extern bool CDI_gribapi_grib1;
extern double CDI_Default_Missval;
extern double CDI_Grid_Missval;
extern int CDI_Default_InstID;
extern int CDI_Default_ModelID;
extern int CDI_Default_TableID;
extern int cdiDefaultLeveltype;
extern int CDI_Default_Calendar;
// extern int cdiNcMissingValue;
extern int CDI_Netcdf_Chunksizehint;
extern int CDI_ChunkType;
extern int CDI_Test;
extern int CDI_Split_Ltype105;
extern bool CDI_Lock_IO;
extern bool CDI_Threadsafe;
extern int cdiDataUnreduced;
extern int cdiSortName;
extern int cdiHaveMissval;
extern bool CDI_Ignore_Att_Coordinates;
extern bool CDI_Coordinates_Lon_Lat;
extern bool CDI_Ignore_Valid_Range;
extern int CDI_Skip_Records;
extern const char *CDI_GRIB1_Template;
extern const char *CDI_GRIB2_Template;
extern int CDI_Convention;
extern int CDI_Inventory_Mode;
extern int CDI_Query_Abort;
extern int CDI_Version_Info;
extern int CDI_Convert_Cubesphere;
extern int CDI_Read_Cell_Center;
extern int CDI_Read_Cell_Corners;
extern int CDI_CMOR_Mode;
extern int CDI_Reduce_Dim;
extern int CDI_Shuffle;
extern size_t CDI_Netcdf_Hdr_Pad;
extern bool CDI_Copy_ChunkSpec;
extern bool CDI_Remove_ChunkSpec;
extern bool CDI_Cache_Info;
extern bool CDI_Cache_NC4;
extern bool CDI_Cache_NCZ;
extern long CDI_CacheSize_In;
extern long CDI_CacheSize_Out;
extern size_t CDI_CacheSize_Max;
extern bool CDI_Netcdf_Lazy_Grid_Load;
extern int STREAM_Debug;

extern char *cdiPartabPath;
extern int cdiPartabIntern;
extern const resOps streamOps;

static inline stream_t *
stream_to_pointer(int idx)
{
  return (stream_t *) reshGetVal(idx, &streamOps);
}

static inline void
stream_check_ptr(const char *caller, stream_t *streamptr)
{
  if (streamptr == NULL) Errorc("stream undefined!");
}

int streamInqFileID(int streamID);

void gridDefHasDims(int gridID, int hasdims);
int gridInqHasDims(int gridID);
int zaxisInqLevelID(int zaxisID, double level);

void streamCheckID(const char *caller, int streamID);

void streamDefineTaxis(int streamID);

int streamsNewEntry(int filetype);
void streamsInitEntry(int streamID);
void cdiStreamSetupVlist(stream_t *streamptr, int vlistID);
// default implementation of the overridable function
void cdiStreamSetupVlist_(stream_t *streamptr, int vlistID);
int stream_new_var(stream_t *streamptr, int gridID, int zaxisID, int tilesetID);

int tstepsNewEntry(stream_t *streamptr);

const char *strfiletype(int filetype);

void cdi_generate_vars(stream_t *streamptr);

void vlist_check_contents(int vlistID);

void cdi_create_records(stream_t *streamptr, int tsID, bool allocRecords);

void streamFCopyRecord(stream_t *streamptr2, stream_t *streamptr1, const char *container_name);

int recordNewEntry(stream_t *streamptr, int tsID);

void cdi_create_timesteps(size_t numTimesteps, stream_t *streamptr);

void recinfoInitEntry(recinfo_t *recinfo);

void cdiCheckZaxis(int zaxisID);

void stream_def_accesstype(stream_t *s, int type);

int getByteswap(int byteorder);

void cdiStreamGetIndexList(unsigned numIDs, int IDs[]);

void cdiInitialize(void);

char *cdiEscapeSpaces(const char *string);
char *cdiUnescapeSpaces(const char *string, const char **outStringEnd);

enum
{
  CDI_UNIT_PA = 1,
  CDI_UNIT_HPA,
  CDI_UNIT_MM,
  CDI_UNIT_CM,
  CDI_UNIT_DM,
  CDI_UNIT_M,
};

struct streamAssoc
{
  int streamID, vlistID;
};

struct streamAssoc streamUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context);

int cdiStreamOpenDefaultDelegate(const char *filename, char filemode, int filetype, stream_t *streamptr,
                                 int recordBufIsToBeCreated);

int streamOpenID(const char *filename, char filemode, int filetype, int resH);

void cdiStreamDefVlist_(int streamID, int vlistID);

int cdiStreamWriteVar_(int streamID, int varID, int memtype, const void *data, SizeType numMissVals);

void cdiStreamWriteVarChunk_(int streamID, int varID, int memtype, const int rect[][2], const void *data, SizeType numMissVals);
void cdiStreamCloseDefaultDelegate(stream_t *streamptr, int recordBufIsToBeDeleted);

int cdiStreamDefTimestep_(stream_t *streamptr, int tsID);

void cdiStreamSync_(stream_t *streamptr);

const char *cdiUnitNamePtr(int cdi_unit);

enum
{
  // 8192 is known to work on most systems (4096 isn't on Alpha)
  commonPageSize = 8192,
};

size_t cdiGetPageSize(bool largePageAlign);

void zaxisGetIndexList(int nzaxis, int *zaxisIndexList);

// clang-format off

#ifdef __cplusplus
extern "C" {
#endif

// functions used in CDO !!!

void cdiDefTableID(int tableID);

void gridGenXvals(size_t xsize, double xfirst, double xlast, double xinc, double *xvals);
void gridGenYvals(int gridtype, size_t ysize, double yfirst, double ylast, double yinc, double *yvals);

static inline
void cdi_check_gridsize_int_limit(const char *format, SizeType gridsize)
{
  if (gridsize > INT_MAX) Error("%s format grid size (%zu) limit exceeded (%zu)!", format, gridsize, INT_MAX);
}

bool cdiFiletypeIsExse(int filetype);
int cdiBaseFiletype(int filetype);

#ifdef __cplusplus
}
#endif

// clang-format on

#endif /* CDI_INT_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDF_INT_H
#define CDF_INT_H

const char *cdf_strerror(int cdfErrorNo);

#ifdef HAVE_LIBNETCDF

#include <netcdf.h>
#include <stdint.h>  // int64_t

void cdf__create(const char *path, int cmode, int *idp);
void cdf_create(const char *path, int cmode, int *idp);
int cdf_open(const char *path, int omode, int *idp);
void cdf_close(int ncid);

void cdf_redef(int ncid);
void cdf_enddef(int ncid, int streamID);
void cdf__enddef(int ncid, int streamID, size_t hdr_pad);
void cdf_sync(int ncid);

void cdf_inq(int ncid, int *ndimsp, int *nvarsp, int *ngattsp, int *unlimdimidp);

void cdf_def_dim(int ncid, const char *name, size_t len, int *idp);
void cdf_inq_dimid(int ncid, const char *name, int *dimidp);
void cdf_inq_dim(int ncid, int dimid, char *name, size_t *lengthp);
void cdf_inq_dimname(int ncid, int dimid, char *name);
void cdf_inq_dimlen(int ncid, int dimid, size_t *lengthp);
void cdf_def_var(int ncid, const char *name, nc_type xtype, int ndims, const int dimids[], int *varidp);
void cdf_def_var_serial(int ncid, const char *name, nc_type xtype, int ndims, const int dimids[], int *varidp);
void cdf_inq_varid(int ncid, const char *name, int *varidp);
void cdf_inq_nvars(int ncid, int *nvarsp);
void cdf_inq_var(int ncid, int varid, char *name, nc_type *xtypep, int *ndimsp, int dimids[], int *nattsp);
void cdf_inq_varname(int ncid, int varid, char *name);
void cdf_inq_vartype(int ncid, int varid, nc_type *xtypep);
void cdf_inq_varndims(int ncid, int varid, int *ndimsp);
void cdf_inq_vardimid(int ncid, int varid, int dimids[]);
void cdf_inq_varnatts(int ncid, int varid, int *nattsp);

void cdf_copy_att(int ncid_in, int varid_in, const char *name, int ncid_out, int varid_out);
void cdf_put_var_text(int ncid, int varid, const char *tp);
void cdf_put_var_uchar(int ncid, int varid, const unsigned char *up);
void cdf_put_var_schar(int ncid, int varid, const signed char *cp);
void cdf_put_var_short(int ncid, int varid, const short *sp);
void cdf_put_var_int(int ncid, int varid, const int *ip);
void cdf_put_var_int64(int ncid, int varid, const int64_t *ip);
void cdf_put_var_long(int ncid, int varid, const long *lp);
void cdf_put_var_float(int ncid, int varid, const float *fp);
void cdf_put_var_double(int ncid, int varid, const double *dp);

void cdf_get_var_text(int ncid, int varid, char *tp);
void cdf_get_var_uchar(int ncid, int varid, unsigned char *up);
void cdf_get_var_schar(int ncid, int varid, signed char *cp);
void cdf_get_var_short(int ncid, int varid, short *sp);
void cdf_get_var_int(int ncid, int varid, int *ip);
void cdf_get_var_int64(int ncid, int varid, int64_t *ip);
void cdf_get_var_long(int ncid, int varid, long *lp);
void cdf_get_var_float(int ncid, int varid, float *fp);
void cdf_get_var_double(int ncid, int varid, double *dp);

void cdf_get_var1_text(int ncid, int varid, const size_t index[], char *tp);

void cdf_get_var1_double(int ncid, int varid, const size_t index[], double *dp);
void cdf_put_var1_double(int ncid, int varid, const size_t index[], const double *dp);

void cdf_get_vara_uchar(int ncid, int varid, const size_t start[], const size_t count[], unsigned char *tp);
void cdf_get_vara_text(int ncid, int varid, const size_t start[], const size_t count[], char *tp);

void cdf_get_vara_double(int ncid, int varid, const size_t start[], const size_t count[], double *dp);
void cdf_put_vara_double(int ncid, int varid, const size_t start[], const size_t count[], const double *dp);

void cdf_get_vara_float(int ncid, int varid, const size_t start[], const size_t count[], float *fp);
void cdf_put_vara_float(int ncid, int varid, const size_t start[], const size_t count[], const float *fp);

void cdf_get_vara_int(int ncid, int varid, const size_t start[], const size_t count[], int *ip);
void cdf_get_vara_int64(int ncid, int varid, const size_t start[], const size_t count[], int64_t *ip);

void cdf_get_vara(int ncid, int varid, const size_t start[], const size_t count[], void *cp);
void cdf_put_vara(int ncid, int varid, const size_t start[], const size_t count[], const void *cp);

void cdf_put_att_text(int ncid, int varid, const char *name, size_t len, const char *tp);
void cdf_put_att_int(int ncid, int varid, const char *name, nc_type xtype, size_t len, const int *ip);
void cdf_put_att_float(int ncid, int varid, const char *name, nc_type xtype, size_t len, const float *dp);
void cdf_put_att_double(int ncid, int varid, const char *name, nc_type xtype, size_t len, const double *dp);

void cdf_get_att_string(int ncid, int varid, const char *name, char **tp);
void cdf_get_att_text(int ncid, int varid, const char *name, char *tp);
void cdf_get_att_int(int ncid, int varid, const char *name, int *ip);
void cdf_get_att_longlong(int ncid, int varid, const char *name, long long *llp);
void cdf_get_att_double(int ncid, int varid, const char *name, double *dp);

void cdf_inq_att(int ncid, int varid, const char *name, nc_type *xtypep, size_t *lenp);
void cdf_inq_atttype(int ncid, int varid, const char *name, nc_type *xtypep);
void cdf_inq_attlen(int ncid, int varid, const char *name, size_t *lenp);
void cdf_inq_attname(int ncid, int varid, int attnum, char *name);
void cdf_inq_attid(int ncid, int varid, const char *name, int *attnump);

void cdf_def_var_chunking(int ncid, int varid, int storage, const size_t *chunksizesp);

typedef int (*cdi_nc__create_funcp)(const char *path, int cmode, size_t initialsz, size_t *chunksizehintp, int *ncidp);

typedef void (*cdi_cdf_def_var_funcp)(int ncid, const char *name, nc_type xtype, int ndims, const int dimids[], int *varidp);

int cdi_nc_enddef_serial(int ncid, int streamID);
int cdi_nc__enddef_serial(int ncid, int streamID, size_t hdr_pad, size_t v_align, size_t v_minfree, size_t r_align);
typedef int (*cdi_nc_enddef_funcp)(int ncid, int streamID);
typedef int (*cdi_nc__enddef_funcp)(int ncid, int streamID, size_t hdr_pad, size_t v_align, size_t v_minfree, size_t r_align);

size_t cdf_xtype_to_numbytes(nc_type xtype);

#endif

#endif /* CDF_INT_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <ctype.h>


#ifdef HAVE_LIBNETCDF
const char *
cdfLibraryVersion(void)
{
  return nc_inq_libvers();
}

int CDF_Debug = 0;  // If set to 1, debugging

void
cdfDebug(int debug)
{
  CDF_Debug = debug;

  if (CDF_Debug) Message("debug level %d", debug);
}

static void
cdfComment(int ncid)
{
  static char comment[256] = "Climate Data Interface version ";
  static bool init = false;

  if (!init)
  {
    init = true;
    const char *libvers = cdiLibraryVersion();

    if (!isdigit((int) *libvers))
      strcat(comment, "??");
    else
      strcat(comment, libvers);
    strcat(comment, " (https://mpimet.mpg.de/cdi)");
  }

  cdf_put_att_text(ncid, NC_GLOBAL, "CDI", strlen(comment), comment);
}

static bool
has_uri_scheme(const char *uri)
{
  const char *pos = strstr(uri, "://");
  if (pos)
  {
    size_t len = (size_t) (pos - uri);
    if (strncmp(uri, "file", len) == 0 || strncmp(uri, "https", len) == 0 || strncmp(uri, "s3", len) == 0) return true;
  }

  return false;
}

static int
cdf_open_read(const char *filename, int *filetype)
{
  int ncid = -1;
  int readmode = NC_NOWRITE;
  int status = cdf_open(filename, readmode, &ncid);
  if (status > 0 && ncid < 0) ncid = CDI_ESYSTEM;
  if (status < -1000 && ncid == -1)
    ncid = status;
  else
  {
    int format = -1;
    status = nc_inq_format(ncid, &format);
    if (status == NC_NOERR && format == NC_FORMAT_NETCDF4_CLASSIC) *filetype = CDI_FILETYPE_NC4C;

#ifdef NC_FORMATX_NCZARR
    int modeNC;
    status = nc_inq_format_extended(ncid, &format, &modeNC);
    if (status == NC_NOERR && format == NC_FORMATX_NCZARR) *filetype = CDI_FILETYPE_NCZARR;
#endif
  }

  return ncid;
}

static int
cdf_open_write(const char *filename, int *filetype)
{
  int ncid = -1;
  int writemode = NC_CLOBBER;

#ifdef NC_64BIT_OFFSET
  if (*filetype == CDI_FILETYPE_NC2) writemode |= NC_64BIT_OFFSET;
#endif
#ifdef NC_64BIT_DATA
  if (*filetype == CDI_FILETYPE_NC5) writemode |= NC_64BIT_DATA;
#endif
  if (*filetype == CDI_FILETYPE_NC4C) writemode |= (NC_NETCDF4 | NC_CLASSIC_MODEL);
  if (*filetype == CDI_FILETYPE_NC4) writemode |= NC_NETCDF4;
  if (*filetype == CDI_FILETYPE_NCZARR) writemode |= NC_NETCDF4;
  if (*filetype == CDI_FILETYPE_NCZARR)
  {
    if (!has_uri_scheme(filename))
    {
      fprintf(stderr, "URI scheme is missing in NCZarr path!\n");
      return CDI_EINVAL;
    }

    cdf_create(filename, writemode, &ncid);
  }
  else
  {
    if (has_uri_scheme(filename)) fprintf(stderr, "URI scheme defined for non NCZarr Data Model!\n");

    cdf__create(filename, writemode, &ncid);
  }

  return ncid;
}

static int
cdfOpenFile(const char *filename, const char *mode, int *filetype)
{
  int ncid = -1;

  if (filename == NULL) { ncid = CDI_EINVAL; }
  else
  {
    int fmode = tolower(*mode);
    switch (fmode)
    {
      case 'r': ncid = cdf_open_read(filename, filetype); break;
      case 'w':
        ncid = cdf_open_write(filename, filetype);
        if (ncid != CDI_EINVAL)
        {
          if (CDI_Version_Info) cdfComment(ncid);
          cdf_put_att_text(ncid, NC_GLOBAL, "Conventions", 6, "CF-1.6");
        }
        break;
      case 'a': cdf_open(filename, NC_WRITE, &ncid); break;
      default: ncid = CDI_EINVAL;
    }
  }

  return ncid;
}

int
cdfOpen(const char *filename, const char *mode, int filetype)
{
  int fileID = -1;
  bool open_file = true;

  if (CDF_Debug) Message("Open %s with mode %c", filename, *mode);

#ifndef NC_64BIT_OFFSET
  if (filetype == CDI_FILETYPE_NC2) open_file = false;
#endif
#ifndef NC_64BIT_DATA
  if (filetype == CDI_FILETYPE_NC5) open_file = false;
#endif

  if (open_file)
  {
    fileID = cdfOpenFile(filename, mode, &filetype);

    if (CDF_Debug) Message("File %s opened with id %d", filename, fileID);
  }
  else { fileID = CDI_ELIBNAVAIL; }

  return fileID;
}

int
cdf4Open(const char *filename, const char *mode, int *filetype)
{
  if (CDF_Debug) Message("Open %s with mode %c", filename, *mode);

  int fileID = cdfOpenFile(filename, mode, filetype);
  if (CDF_Debug) Message("File %s opened with id %d", filename, fileID);
  return fileID;
}

static void
cdfCloseFile(int fileID)
{

  if (CDF_Debug) Message("Closing cdf file: %d", fileID);
  cdf_close(fileID);
}

void
cdfClose(int fileID)
{
  cdfCloseFile(fileID);
}

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef NAMESPACE_H
#define NAMESPACE_H

#ifdef HAVE_CONFIG_H
#endif

typedef struct
{
  int idx;
  int nsp;
} namespaceTuple_t;

enum namespaceSwitch
{
  NSSWITCH_NO_SUCH_SWITCH = -1,
  NSSWITCH_ABORT,
  NSSWITCH_WARNING,
  NSSWITCH_SERIALIZE_GET_SIZE,
  NSSWITCH_SERIALIZE_PACK,
  NSSWITCH_SERIALIZE_UNPACK,
  NSSWITCH_FILE_OPEN,
  NSSWITCH_FILE_WRITE,
  NSSWITCH_FILE_CLOSE,
  NSSWITCH_STREAM_OPEN_BACKEND,
  NSSWITCH_STREAM_DEF_VLIST_,
  NSSWITCH_STREAM_SETUP_VLIST,
  NSSWITCH_STREAM_WRITE_VAR_,
  NSSWITCH_STREAM_WRITE_VAR_CHUNK_,
  NSSWITCH_STREAM_WRITE_VAR_PART_,
  NSSWITCH_STREAM_WRITE_SCATTERED_VAR_PART_,
  NSSWITCH_STREAM_CLOSE_BACKEND,
  NSSWITCH_STREAM_DEF_TIMESTEP_,
  NSSWITCH_STREAM_SYNC,
  NSSWITCH_VLIST_DESTROY_,
#ifdef HAVE_LIBNETCDF
  NSSWITCH_NC__CREATE,
  NSSWITCH_CDF_DEF_VAR,
  NSSWITCH_NC_ENDDEF,
  NSSWITCH_NC__ENDDEF,
  NSSWITCH_CDF_DEF_TIMESTEP,
  NSSWITCH_CDF_STREAM_SETUP,
  NSSWITCH_CDF_POSTDEFACTION_GRID_PROP,
#endif
  NUM_NAMESPACE_SWITCH,
};

union namespaceSwitchValue
{
  void *data;
  void (*func)(void);
};

#define NSSW_FUNC(p) ((union namespaceSwitchValue){ .func = (void (*)(void))(p) })
#define NSSW_DATA(p) ((union namespaceSwitchValue){ .data = (void *) (p) })

// int              namespaceNew();
// void             namespaceDelete(int namespaceID);
void namespaceCleanup(void);
int namespaceGetNumber(void);
// void             namespaceSetActive(int namespaceID);
// int              namespaceGetActive    ( void );
int namespaceIdxEncode(namespaceTuple_t);
int namespaceIdxEncode2(int, int);
namespaceTuple_t namespaceResHDecode(int);
int namespaceAdaptKey(int originResH, int originNamespace);
int namespaceAdaptKey2(int);
void namespaceSwitchSet(int sw, union namespaceSwitchValue value);
union namespaceSwitchValue namespaceSwitchGet(int sw);
/* reserve new dynamic key */
int cdiNamespaceSwitchNewKey(void);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <sys/stat.h>


const char *
cdf_strerror(int cdfErrorNo)
{
#ifdef HAVE_LIBNETCDF
  return nc_strerror(cdfErrorNo);
#else
  return NULL;
#endif
}

#ifdef HAVE_LIBNETCDF

void
cdf_create(const char *path, int cmode, int *ncidp)
{
  int status = nc_create(path, cmode, ncidp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  mode=%d  file=%s", *ncidp, cmode, path);
  if (status != NC_NOERR) Error("%s: %s", path, nc_strerror(status));

  int oldfill;
  status = nc_set_fill(*ncidp, NC_NOFILL, &oldfill);
  if (status != NC_NOERR) Error("%s: %s", path, nc_strerror(status));
}

void
cdf__create(const char *path, int cmode, int *ncidp)
{
  int status = -1;
  size_t chunksizehint = 0;
  size_t initialsz = 0;

#if defined(__SX__) || defined(ES)
  chunksizehint = 16777216;  // 16 MB
#endif

  if (CDI_Netcdf_Chunksizehint != CDI_UNDEFID) chunksizehint = (size_t) CDI_Netcdf_Chunksizehint;

  cdi_nc__create_funcp my_nc__create = (cdi_nc__create_funcp) namespaceSwitchGet(NSSWITCH_NC__CREATE).func;
  status = my_nc__create(path, cmode, initialsz, &chunksizehint, ncidp);

  if (status != NC_NOERR)
  {
    if (CDF_Debug) Message("ncid=%d  mode=%d  chunksizehint=%zu  file=%s", *ncidp, cmode, chunksizehint, path);
    Error("%s: %s", path, nc_strerror(status));
  }

  int oldfill;
  status = nc_set_fill(*ncidp, NC_NOFILL, &oldfill);

  if (status != NC_NOERR) Error("%s: %s", path, nc_strerror(status));
}

int
cdf_open(const char *path, int omode, int *ncidp)
{
  int status = 0;

  if (strstr(path, ":/"))  // ESDM and DAP
  {
    status = nc_open(path, omode, ncidp);
  }
  else
  {
    struct stat filestat;
    if (stat(path, &filestat) != 0) SysError(path);

    size_t chunksizehint = 0;
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
    chunksizehint = (size_t) filestat.st_blksize * 4;
    if (chunksizehint > (size_t) filestat.st_size) chunksizehint = (size_t) filestat.st_size;
#endif
    // if (chunksizehint < ChunkSizeMin) chunksizehint = ChunkSizeMin;
    if (CDI_Netcdf_Chunksizehint != CDI_UNDEFID) chunksizehint = (size_t) CDI_Netcdf_Chunksizehint;

    // FIXME: parallel part missing
    status = nc__open(path, omode, &chunksizehint, ncidp);

    if (CDF_Debug) Message("chunksizehint %zu", chunksizehint);
  }

  if (CDF_Debug) Message("ncid=%d  mode=%d  file=%s", *ncidp, omode, path);

  if (CDF_Debug && status != NC_NOERR) Message("%s", nc_strerror(status));
  if (status != NC_NOERR && status < 0) status -= 1000;

  return status;
}

void
cdf_close(int ncid)
{
  int status = nc_close(ncid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_redef(int ncid)
{
  int status = nc_redef(ncid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

int
cdi_nc_enddef_serial(int ncid, int streamID)
{
  (void) streamID;
  return nc_enddef(ncid);
}

int
cdi_nc__enddef_serial(int ncid, int streamID, size_t hdr_pad, size_t v_align, size_t v_minfree, size_t r_align)
{
  (void) streamID;
  return nc__enddef(ncid, hdr_pad, v_align, v_minfree, r_align);
}

void
cdf_enddef(int ncid, int streamID)
{
  cdi_nc_enddef_funcp my_nc_enddef = (cdi_nc_enddef_funcp) namespaceSwitchGet(NSSWITCH_NC_ENDDEF).func;
  int status = my_nc_enddef(ncid, streamID);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf__enddef(int ncid, int streamID, size_t hdr_pad)
{
  size_t v_align = 4UL;    // [B] Alignment of beginning of data section for fixed variables
  size_t v_minfree = 0UL;  // [B] Pad at end of data section for fixed size variables
  size_t r_align = 4UL;    // [B] Alignment of beginning of data section for record variables

  // nc_enddef(ncid) is equivalent to nc__enddef(ncid, 0, 4, 0, 4)
  cdi_nc__enddef_funcp my_nc__enddef = (cdi_nc__enddef_funcp) namespaceSwitchGet(NSSWITCH_NC__ENDDEF).func;
  int status = my_nc__enddef(ncid, streamID, hdr_pad, v_align, v_minfree, r_align);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_sync(int ncid)
{
  int status = nc_sync(ncid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq(int ncid, int *ndimsp, int *nvarsp, int *ngattsp, int *unlimdimidp)
{
  int status = nc_inq(ncid, ndimsp, nvarsp, ngattsp, unlimdimidp);
  if (CDF_Debug || status != NC_NOERR)
    Message("ncid=%d  ndims=%d  nvars=%d  ngatts=%d  unlimid=%d", ncid, *ndimsp, *nvarsp, *ngattsp, *unlimdimidp);

  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_def_dim(int ncid, const char *name, size_t len, int *dimidp)
{
  int status = nc_def_dim(ncid, name, len, dimidp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  name=%s  len=%zu", ncid, name, len);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_dimid(int ncid, const char *name, int *dimidp)
{
  int status = nc_inq_dimid(ncid, name, dimidp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  name=%s  dimid=%d", ncid, name, *dimidp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_dim(int ncid, int dimid, char *name, size_t *lengthp)
{
  int status = nc_inq_dim(ncid, dimid, name, lengthp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  dimid=%d  length=%zu  name=%s", ncid, dimid, *lengthp, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_dimname(int ncid, int dimid, char *name)
{
  int status = nc_inq_dimname(ncid, dimid, name);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  dimid=%d  name=%s", ncid, dimid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_dimlen(int ncid, int dimid, size_t *lengthp)
{
  int status = nc_inq_dimlen(ncid, dimid, lengthp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  dimid=%d  length=%zu", ncid, dimid, *lengthp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_def_var(int ncid, const char *name, nc_type xtype, int ndims, const int dimids[], int *varidp)
{
  cdi_cdf_def_var_funcp my_cdf_def_var = (cdi_cdf_def_var_funcp) namespaceSwitchGet(NSSWITCH_CDF_DEF_VAR).func;
  my_cdf_def_var(ncid, name, xtype, ndims, dimids, varidp);
}

void
cdf_def_var_serial(int ncid, const char *name, nc_type xtype, int ndims, const int dimids[], int *varidp)
{
  int status = nc_def_var(ncid, name, xtype, ndims, dimids, varidp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  name=%s  xtype=%d  ndims=%d  varid=%d", ncid, name, xtype, ndims, *varidp);
  if (status == NC_NOERR)
  {
    int fileFormat;
    status = nc_inq_format(ncid, &fileFormat);
    if (status == NC_NOERR && (fileFormat == NC_FORMAT_NETCDF4 || fileFormat == NC_FORMAT_NETCDF4_CLASSIC))
      status = nc_def_var_fill(ncid, *varidp, 1, NULL);
  }
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_varid(int ncid, const char *name, int *varidp)
{
  int status = nc_inq_varid(ncid, name, varidp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  name=%s  varid=%d", ncid, name, *varidp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_nvars(int ncid, int *nvarsp)
{
  int status = nc_inq_nvars(ncid, nvarsp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  nvars=%d", ncid, *nvarsp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_var(int ncid, int varid, char *name, nc_type *xtypep, int *ndimsp, int dimids[], int *nattsp)
{
  int status = nc_inq_var(ncid, varid, name, xtypep, ndimsp, dimids, nattsp);
  if (CDF_Debug || status != NC_NOERR)
    Message("ncid=%d  varid=%d  ndims=%d  xtype=%d  natts=%d  name=%s", ncid, varid, *ndimsp, *xtypep, *nattsp, name);

  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_varname(int ncid, int varid, char *name)
{
  int status = nc_inq_varname(ncid, varid, name);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  name=%s", ncid, varid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_vartype(int ncid, int varid, nc_type *xtypep)
{
  int status = nc_inq_vartype(ncid, varid, xtypep);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  xtype=%s", ncid, varid, *xtypep);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_varndims(int ncid, int varid, int *ndimsp)
{
  int status = nc_inq_varndims(ncid, varid, ndimsp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_vardimid(int ncid, int varid, int dimids[])
{
  int status = nc_inq_vardimid(ncid, varid, dimids);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_varnatts(int ncid, int varid, int *nattsp)
{
  int status = nc_inq_varnatts(ncid, varid, nattsp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  nattsp=%d", ncid, varid, *nattsp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_text(int ncid, int varid, const char *tp)
{
  int status = nc_put_var_text(ncid, varid, tp);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %s", ncid, varid, tp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_short(int ncid, int varid, const short *sp)
{
  int status = nc_put_var_short(ncid, varid, sp);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %hd", ncid, varid, *sp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_int(int ncid, int varid, const int *ip)
{
  int status = nc_put_var_int(ncid, varid, ip);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %d", ncid, varid, *ip);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_int64(int ncid, int varid, const int64_t *ip)
{
  const long long *llp = (const long long *) ip;
  int status = nc_put_var_longlong(ncid, varid, llp);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %lld", ncid, varid, *llp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_long(int ncid, int varid, const long *lp)
{
  int status = nc_put_var_long(ncid, varid, lp);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %ld", ncid, varid, *lp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_float(int ncid, int varid, const float *fp)
{
  int status = nc_put_var_float(ncid, varid, fp);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %f", ncid, varid, *fp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

static const char *
cdf_var_type(nc_type xtype)
{
  if (xtype == NC_BYTE) return "NC_BYTE";
  if (xtype == NC_CHAR) return "NC_CHAR";
  if (xtype == NC_SHORT) return "NC_SHORT";
  if (xtype == NC_INT) return "NC_INT";
  if (xtype == NC_FLOAT) return "NC_FLOAT";
  if (xtype == NC_DOUBLE) return "NC_DOUBLE";
  if (xtype == NC_UBYTE) return "NC_UBYTE";
  if (xtype == NC_LONG) return "NC_LONG";
  if (xtype == NC_USHORT) return "NC_USHORT";
  if (xtype == NC_UINT) return "NC_UINT";
  if (xtype == NC_INT64) return "NC_INT64";
  if (xtype == NC_UINT64) return "NC_UINT64";

  return "unknown";
}

static void
minmaxval(size_t nvals, const double *array, double *minval, double *maxval)
{
  double minv = array[0];
  double maxv = array[0];
  for (size_t i = 0; i < nvals; ++i)
  {
    minv = (array[i] < minv) ? array[i] : minv;
    maxv = (array[i] > maxv) ? array[i] : maxv;
  }

  *minval = minv;
  *maxval = maxv;
}

static void
minmaxvalf(size_t nvals, const float *array, double *minval, double *maxval)
{
  float minv = array[0];
  float maxv = array[0];
  for (size_t i = 0; i < nvals; ++i)
  {
    minv = (array[i] < minv) ? array[i] : minv;
    maxv = (array[i] > maxv) ? array[i] : maxv;
  }

  *minval = minv;
  *maxval = maxv;
}

void
cdf_put_vara_double(int ncid, int varid, const size_t start[], const size_t count[], const double *dp)
{
  int status = nc_put_vara_double(ncid, varid, start, count, dp);
  if (CDF_Debug || status != NC_NOERR)
  {
    char name[256];
    nc_inq_varname(ncid, varid, name);
    nc_type xtype;
    nc_inq_vartype(ncid, varid, &xtype);
    int ndims;
    nc_inq_varndims(ncid, varid, &ndims);
    double minval = 0.0, maxval = 0.0;
    size_t nvals = 1;
    for (int i = 0; i < ndims; ++i) nvals *= count[i];
    minmaxval(nvals, dp, &minval, &maxval);
    Message("name=%s  type=%s  minval=%f  maxval=%f", name, cdf_var_type(xtype), minval, maxval);
  }

  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_vara_float(int ncid, int varid, const size_t start[], const size_t count[], const float *fp)
{
  int status = nc_put_vara_float(ncid, varid, start, count, fp);
  if (CDF_Debug || status != NC_NOERR)
  {
    char name[256];
    nc_inq_varname(ncid, varid, name);
    nc_type xtype;
    nc_inq_vartype(ncid, varid, &xtype);
    int ndims;
    nc_inq_varndims(ncid, varid, &ndims);
    double minval = 0.0, maxval = 0.0;
    size_t nvals = 1;
    for (int i = 0; i < ndims; ++i) nvals *= count[i];
    minmaxvalf(nvals, fp, &minval, &maxval);
    Message("name=%s  type=%s  minval=%f  maxval=%f", name, cdf_var_type(xtype), minval, maxval);
  }

  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_vara(int ncid, int varid, const size_t start[], const size_t count[], const void *cp)
{
  int status = nc_put_vara(ncid, varid, start, count, cp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara(int ncid, int varid, const size_t start[], const size_t count[], void *cp)
{
  int status = nc_get_vara(ncid, varid, start, count, cp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara_int(int ncid, int varid, const size_t start[], const size_t count[], int *ip)
{
  int status = nc_get_vara_int(ncid, varid, start, count, ip);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara_int64(int ncid, int varid, const size_t start[], const size_t count[], int64_t *ip)
{
  long long *llp = (long long *) ip;
  int status = nc_get_vara_longlong(ncid, varid, start, count, llp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara_double(int ncid, int varid, const size_t start[], const size_t count[], double *dp)
{
  int status = nc_get_vara_double(ncid, varid, start, count, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  start[0]=%zu  count[0]=%zu", ncid, varid, start[0], count[0]);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara_float(int ncid, int varid, const size_t start[], const size_t count[], float *fp)
{
  int status = nc_get_vara_float(ncid, varid, start, count, fp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  start[0]=%zu  count[0]=%zu", ncid, varid, start[0], count[0]);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara_text(int ncid, int varid, const size_t start[], const size_t count[], char *tp)
{
  int status = nc_get_vara_text(ncid, varid, start, count, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_vara_uchar(int ncid, int varid, const size_t start[], const size_t count[], unsigned char *tp)
{
  int status = nc_get_vara_uchar(ncid, varid, start, count, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var_double(int ncid, int varid, const double *dp)
{
  int status = nc_put_var_double(ncid, varid, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  val0=%f", ncid, varid, *dp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var1_text(int ncid, int varid, const size_t index[], char *tp)
{
  int status = nc_get_var1_text(ncid, varid, index, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var1_double(int ncid, int varid, const size_t index[], double *dp)
{
  int status = nc_get_var1_double(ncid, varid, index, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_var1_double(int ncid, int varid, const size_t index[], const double *dp)
{
  int status = nc_put_var1_double(ncid, varid, index, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  val=%f", ncid, varid, *dp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_text(int ncid, int varid, char *tp)
{
  int status = nc_get_var_text(ncid, varid, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_short(int ncid, int varid, short *sp)
{
  int status = nc_get_var_short(ncid, varid, sp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_int(int ncid, int varid, int *ip)
{
  int status = nc_get_var_int(ncid, varid, ip);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_int64(int ncid, int varid, int64_t *ip)
{
  long long *llp = (long long *) ip;
  int status = nc_get_var_longlong(ncid, varid, llp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_long(int ncid, int varid, long *lp)
{
  int status = nc_get_var_long(ncid, varid, lp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_float(int ncid, int varid, float *fp)
{
  int status = nc_get_var_float(ncid, varid, fp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d", ncid, varid);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_var_double(int ncid, int varid, double *dp)
{
  int status = nc_get_var_double(ncid, varid, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  val[0]=%f", ncid, varid, *dp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_copy_att(int ncid_in, int varid_in, const char *name, int ncid_out, int varid_out)
{
  int status = nc_copy_att(ncid_in, varid_in, name, ncid_out, varid_out);
  if (CDF_Debug || status != NC_NOERR) Message("%d %d %s %d %d", ncid_in, varid_out, name, ncid_out, varid_out);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_att_text(int ncid, int varid, const char *name, size_t len, const char *tp)
{
  int status = nc_put_att_text(ncid, varid, name, len, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  text='%.*s'", ncid, varid, name, (int) len, tp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_att_int(int ncid, int varid, const char *name, nc_type xtype, size_t len, const int *ip)
{
  int status = nc_put_att_int(ncid, varid, name, xtype, len, ip);
  if (status == NC_ERANGE) status = NC_NOERR;
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  val=%d", ncid, varid, name, *ip);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_att_float(int ncid, int varid, const char *name, nc_type xtype, size_t len, const float *dp)
{
  int status = nc_put_att_float(ncid, varid, name, xtype, len, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  val=%g", ncid, varid, name, *dp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_put_att_double(int ncid, int varid, const char *name, nc_type xtype, size_t len, const double *dp)
{
  int status = nc_put_att_double(ncid, varid, name, xtype, len, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  val=%g", ncid, varid, name, *dp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_att_text(int ncid, int varid, const char *name, char *tp)
{
  int status = nc_get_att_text(ncid, varid, name, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  name=%s", ncid, varid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_att_string(int ncid, int varid, const char *name, char **tp)
{
  int status = nc_get_att_string(ncid, varid, name, tp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  name=%s", ncid, varid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_att_int(int ncid, int varid, const char *name, int *ip)
{
  int status = nc_get_att_int(ncid, varid, name, ip);
  if (status == NC_ERANGE) status = NC_NOERR;
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  val=%d", ncid, varid, name, *ip);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_att_longlong(int ncid, int varid, const char *name, long long *llp)
{
  int status = nc_get_att_longlong(ncid, varid, name, llp);
  if (status == NC_ERANGE) status = NC_NOERR;
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  val=%lld", ncid, varid, name, *llp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_get_att_double(int ncid, int varid, const char *name, double *dp)
{
  int status = nc_get_att_double(ncid, varid, name, dp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  val=%.9g", ncid, varid, name, *dp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_att(int ncid, int varid, const char *name, nc_type *xtypep, size_t *lenp)
{
  int status = nc_inq_att(ncid, varid, name, xtypep, lenp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s", ncid, varid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_atttype(int ncid, int varid, const char *name, nc_type *xtypep)
{
  int status = nc_inq_atttype(ncid, varid, name, xtypep);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s", ncid, varid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_attlen(int ncid, int varid, const char *name, size_t *lenp)
{
  int status = nc_inq_attlen(ncid, varid, name, lenp);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s  len=%zu", ncid, varid, name, *lenp);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_attname(int ncid, int varid, int attnum, char *name)
{
  int status = nc_inq_attname(ncid, varid, attnum, name);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  attnum=%d  att=%s", ncid, varid, attnum, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_inq_attid(int ncid, int varid, const char *name, int *attnump)
{
  int status = nc_inq_attid(ncid, varid, name, attnump);
  if (CDF_Debug || status != NC_NOERR) Message("ncid=%d  varid=%d  att=%s", ncid, varid, name);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

void
cdf_def_var_chunking(int ncid, int varid, int storage, const size_t *chunksizesp)
{
  int status = nc_def_var_chunking(ncid, varid, storage, chunksizesp);
  if (CDF_Debug || status != NC_NOERR)
    Message("chunks=%zu/%zu/%zu/%zu", chunksizesp[0], chunksizesp[1], chunksizesp[2], chunksizesp[3]);
  if (status != NC_NOERR) Error("%s", nc_strerror(status));
}

size_t
cdf_xtype_to_numbytes(nc_type xtype)
{
  if (xtype == NC_BYTE || xtype == NC_CHAR || xtype == NC_UBYTE) return 1;
  if (xtype == NC_SHORT || xtype == NC_USHORT) return 2;
  if (xtype == NC_INT || xtype == NC_FLOAT || xtype == NC_LONG || xtype == NC_UINT) return 4;

  return 8;
}

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDF_UTIL_H_
#define CDF_UTIL_H_

#include <stdlib.h>
#include <stdbool.h>

bool xtypeIsText(int xtype);

int get_time_units(size_t len, const char *ptu);

bool is_time_units(const char *timeunits);
bool is_timeaxis_units(const char *timeunits);

bool is_height_units(const char *units);
bool is_pressure_units(const char *units);
bool is_DBL_axis(/*const char *units,*/ const char *longname);
bool is_depth_axis(const char *stdname, const char *longname);
bool is_height_axis(const char *stdname, const char *longname);
bool is_altitude_axis(const char *stdname, const char *longname);
bool is_reference_axis(const char *stdname, const char *longname);

bool is_lon_axis(const char *units, const char *stdname);
bool is_lat_axis(const char *units, const char *stdname);

bool is_x_axis(const char *units, const char *stdname);
bool is_y_axis(const char *units, const char *stdname);

void cdf_set_gridtype(const char *attstring, int *gridtype);
void cdf_set_zaxistype(const char *attstring, int *zaxistype);
int attribute_to_calendar(const char *attstring);

#endif
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <string.h>
#include <ctype.h>


char *
str_to_lower(char *str)
{
  if (str)
    for (size_t i = 0; str[i]; ++i) str[i] = (char) tolower((int) str[i]);

  return str;
}

bool
strStartsWith(const char *vstr, const char *cstr)
{
  bool is_equal = false;
  if (vstr && cstr)
  {
    size_t clen = strlen(cstr);
    size_t vlen = strlen(vstr);
    if (clen <= vlen) is_equal = (memcmp(vstr, cstr, clen) == 0);
  }
  return is_equal;
}

int
get_time_units(size_t len, const char *ptu)
{
  while (isspace(*ptu) && len)
  {
    ptu++;
    len--;
  }

  // clang-format off
  if (len > 2)
  {
    if (strStartsWith(ptu, "sec"))            return TUNIT_SECOND;
    if (strStartsWith(ptu, "minute"))         return TUNIT_MINUTE;
    if (strStartsWith(ptu, "hour"))           return TUNIT_HOUR;
    if (strStartsWith(ptu, "day"))            return TUNIT_DAY;
    if (strStartsWith(ptu, "month"))          return TUNIT_MONTH;
    if (strStartsWith(ptu, "calendar_month")) return TUNIT_MONTH;
    if (strStartsWith(ptu, "year"))           return TUNIT_YEAR;
  }
  else if (len == 1 && ptu[0] == 's')         return TUNIT_SECOND;
  // clang-format on

  return -1;
}

bool
is_time_units(const char *timeunits)
{
  while (isspace(*timeunits)) timeunits++;

  // clang-format off
  return (strStartsWith(timeunits, "sec")
       || strStartsWith(timeunits, "minute")
       || strStartsWith(timeunits, "hour")
       || strStartsWith(timeunits, "day")
       || strStartsWith(timeunits, "month")
       || strStartsWith(timeunits, "calendar_month")
       || strStartsWith(timeunits, "year"));
  // clang-format on
}

bool
is_timeaxis_units(const char *timeunits)
{
  bool status = false;

  size_t len = strlen(timeunits);
  char *tu = (char *) malloc((len + 1) * sizeof(char));

  memcpy(tu, timeunits, len);
  tu[len] = 0;
  str_to_lower(tu);

  int timeunit = get_time_units(len, tu);
  if (timeunit != -1)
  {
    size_t pos = 0;
    while (!isspace(tu[pos]) && tu[pos] != 0) pos++;
    if (tu[pos])
    {
      while (isspace(tu[pos])) { pos++; }
      status = strStartsWith(tu + pos, "as") || strStartsWith(tu + pos, "since");
    }
  }

  free(tu);

  return status;
}

bool
is_height_units(const char *units)
{
  int u0 = units[0];
  // clang-format off
  return ((u0=='m' && (!units[1] || strStartsWith(units, "meter")))
       || (!units[2] && units[1]=='m' && (u0=='c' || u0=='d' || u0=='k'))
       || (strStartsWith(units, "decimeter"))
       || (strStartsWith(units, "centimeter"))
       || (strStartsWith(units, "millimeter"))
       || (strStartsWith(units, "kilometer")));
  // clang-format on
}

bool
is_pressure_units(const char *units)
{
  // clang-format off
  return (strStartsWith(units, "millibar")
       || strStartsWith(units, "mb")
       || strStartsWith(units, "hectopas")
       || strStartsWith(units, "hPa")
       || strStartsWith(units, "pa")
       || strStartsWith(units, "Pa"));
  // clang-format on
}

bool
is_DBL_axis(const char *longname)
{
  // clang-format off
  return (str_is_equal(longname, "depth below land")
       || str_is_equal(longname, "depth_below_land")
       || str_is_equal(longname, "levels below the surface"));
  // clang-format on
}

bool
is_depth_axis(const char *stdname, const char *longname)
{
  // clang-format off
  return (str_is_equal(stdname, "depth")
       || str_is_equal(longname, "depth_below_sea")
       || str_is_equal(longname, "depth below sea"));
  // clang-format on
}

bool
is_height_axis(const char *stdname, const char *longname)
{
  // clang-format off
  return (str_is_equal(stdname, "height")
       || str_is_equal(longname, "height")
       || str_is_equal(longname, "height above the surface"));
  // clang-format on
}

bool
is_altitude_axis(const char *stdname, const char *longname)
{
  // clang-format off
  return (str_is_equal(stdname, "altitude")
       || str_is_equal(longname, "altitude"));
  // clang-format on
}

bool
is_reference_axis(const char *stdname, const char *longname)
{
  // clang-format off
  return ((str_is_equal(longname, "generalized_height") || str_is_equal(longname, "generalized height"))
        && str_is_equal(stdname, "height"));
  // clang-format on
}

bool
is_lon_axis(const char *units, const char *stdname)
{
  bool status = false;
  char lc_units[16];

  memcpy(lc_units, units, 15);
  lc_units[15] = 0;
  str_to_lower(lc_units);

  if ((strStartsWith(lc_units, "degree") || strStartsWith(lc_units, "radian"))
      && (strStartsWith(stdname, "grid_longitude") || strStartsWith(stdname, "longitude")))
  {
    status = true;
  }
  else if (strStartsWith(lc_units, "degree") && !strStartsWith(stdname, "grid_latitude") && !strStartsWith(stdname, "latitude"))
  {
    int ioff = 6;
    if (lc_units[ioff] == 's') ioff++;
    if (lc_units[ioff] == ' ') ioff++;
    if (lc_units[ioff] == '_') ioff++;
    if (lc_units[ioff] == 'e') status = true;
  }

  return status;
}

bool
is_lat_axis(const char *units, const char *stdname)
{
  bool status = false;
  char lc_units[16];

  memcpy(lc_units, units, 15);
  lc_units[15] = 0;
  str_to_lower(lc_units);

  if ((strStartsWith(lc_units, "degree") || strStartsWith(lc_units, "radian"))
      && (strStartsWith(stdname, "grid_latitude") || strStartsWith(stdname, "latitude")))
  {
    status = true;
  }
  else if (strStartsWith(lc_units, "degree") && !strStartsWith(stdname, "grid_longitude") && !strStartsWith(stdname, "longitude"))
  {
    int ioff = 6;
    if (lc_units[ioff] == 's') ioff++;
    if (lc_units[ioff] == ' ') ioff++;
    if (lc_units[ioff] == '_') ioff++;
    if (lc_units[ioff] == 'n' || lc_units[ioff] == 's') status = true;
  }

  return status;
}

bool
is_x_axis(const char *units, const char *stdname)
{
  (void) units;
  return (str_is_equal(stdname, "projection_x_coordinate"));
}

bool
is_y_axis(const char *units, const char *stdname)
{
  (void) units;
  return (str_is_equal(stdname, "projection_y_coordinate"));
}

void
cdf_set_gridtype(const char *attstring, int *gridtype)
{
  // clang-format off
  if      (str_is_equal(attstring, "gaussian_reduced")) *gridtype = GRID_GAUSSIAN_REDUCED;
  else if (str_is_equal(attstring, "gaussian"))         *gridtype = GRID_GAUSSIAN;
  else if (strStartsWith(attstring, "spectral"))        *gridtype = GRID_SPECTRAL;
  else if (strStartsWith(attstring, "fourier"))         *gridtype = GRID_FOURIER;
  else if (str_is_equal(attstring, "trajectory"))       *gridtype = GRID_TRAJECTORY;
  else if (str_is_equal(attstring, "generic"))          *gridtype = GRID_GENERIC;
  else if (str_is_equal(attstring, "cell"))             *gridtype = GRID_UNSTRUCTURED;
  else if (str_is_equal(attstring, "unstructured"))     *gridtype = GRID_UNSTRUCTURED;
  else if (str_is_equal(attstring, "curvilinear")) ;
  else if (str_is_equal(attstring, "characterxy"))      *gridtype = GRID_CHARXY;
  else if (str_is_equal(attstring, "sinusoidal")) ;
  else if (str_is_equal(attstring, "laea")) ;
  else if (str_is_equal(attstring, "lcc2")) ;
  else if (str_is_equal(attstring, "linear")) ; // ignore grid type linear
  else
    {
      static bool warn = true;
      if (warn)
        {
          warn = false;
          Warning("NetCDF attribute grid_type='%s' unsupported!", attstring);
        }
    }
  // clang-format on
}

void
cdf_set_zaxistype(const char *attstring, int *zaxistype)
{
  // clang-format off
  if      (str_is_equal(attstring, "toa"))              *zaxistype = ZAXIS_TOA;
  else if (str_is_equal(attstring, "tropopause"))       *zaxistype = ZAXIS_TROPOPAUSE;
  else if (str_is_equal(attstring, "cloudbase"))        *zaxistype = ZAXIS_CLOUD_BASE;
  else if (str_is_equal(attstring, "cloudtop"))         *zaxistype = ZAXIS_CLOUD_TOP;
  else if (str_is_equal(attstring, "isotherm0"))        *zaxistype = ZAXIS_ISOTHERM_ZERO;
  else if (str_is_equal(attstring, "seabottom"))        *zaxistype = ZAXIS_SEA_BOTTOM;
  else if (str_is_equal(attstring, "lakebottom"))       *zaxistype = ZAXIS_LAKE_BOTTOM;
  else if (str_is_equal(attstring, "sedimentbottom"))   *zaxistype = ZAXIS_SEDIMENT_BOTTOM;
  else if (str_is_equal(attstring, "sedimentbottomta")) *zaxistype = ZAXIS_SEDIMENT_BOTTOM_TA;
  else if (str_is_equal(attstring, "sedimentbottomtw")) *zaxistype = ZAXIS_SEDIMENT_BOTTOM_TW;
  else if (str_is_equal(attstring, "mixlayer"))         *zaxistype = ZAXIS_MIX_LAYER;
  else if (str_is_equal(attstring, "atmosphere"))       *zaxistype = ZAXIS_ATMOSPHERE;
  else
    {
      static bool warn = true;
      if (warn)
        {
          warn = false;
          Warning("NetCDF attribute level_type='%s' unsupported!", attstring);
        }
    }
  // clang-format on
}

int
attribute_to_calendar(const char *attstring)
{
  // clang-format off
  if (strStartsWith(attstring, "standard"))  return CALENDAR_STANDARD;
  if (strStartsWith(attstring, "gregorian")) return CALENDAR_GREGORIAN;
  if (strStartsWith(attstring, "none"))      return CALENDAR_NONE;
  if (strStartsWith(attstring, "proleptic")) return CALENDAR_PROLEPTIC;
  if (strStartsWith(attstring, "360"))       return CALENDAR_360DAYS;
  if (strStartsWith(attstring, "365") ||
      strStartsWith(attstring, "noleap"))    return CALENDAR_365DAYS;
  if (strStartsWith(attstring, "366") ||
      strStartsWith(attstring, "all_leap"))  return CALENDAR_366DAYS;
  // clang-format on

  static bool warn = true;
  if (warn)
  {
    warn = false;
    Warning("Calendar >%s< unsupported!", attstring);
  }

  return CALENDAR_STANDARD;
}
#ifndef CDI_ATT_H
#define CDI_ATT_H

#ifdef HAVE_CONFIG_H
#endif

#ifndef CDI_LIMITS_H
#endif

#include <stdio.h>

// CDI attribute
// clang-format off
typedef struct
{
  size_t    xsz;	  // amount of space at xvalue
  size_t    namesz;       // size of name
  char     *name;         // attribute name
  int       indtype;	  // internal data type of xvalue (INT, FLT or TXT)
  int       exdtype;      // external data type
                          // indtype    exdtype
                          // TXT        TXT
                          // INT        INT16, INT32
                          // FLT        FLT32, FLT64
  size_t    nelems;    	  // number of elements
  void     *xvalue;       // the actual data
} cdi_att_t;
// clang-format on

// clang-format off
typedef struct
{
  size_t     nalloc;		// number allocated >= nelems
  size_t     nelems;		// length of the array
  cdi_att_t  value[MAX_ATTRIBUTES];
} cdi_atts_t;
// clang-format on

int cdiDeleteAtts(int vlistID, int varID);
int cdiAttsGetSize(void *p, int varID, void *context);
void cdiAttsPack(void *p, int varID, void *buf, int size, int *position, void *context);
void cdiAttsUnpack(int cdiID, int varID, void *buf, int size, int *position, void *context);

int cdi_att_compare(cdi_atts_t *attspa, cdi_atts_t *attspb, int attnum);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef GRID_H
#define GRID_H

#include <stdbool.h>


typedef unsigned char mask_t;

typedef struct grid_t grid_t;

enum gridPropInq
{
  GRID_PROP_MASK,
  GRID_PROP_MASK_GME,
  GRID_PROP_XVALS,
  GRID_PROP_YVALS,
  GRID_PROP_AREA,
  GRID_PROP_XBOUNDS,
  GRID_PROP_YBOUNDS,
};

struct gridVirtTable
{
  void (*destroy)(grid_t *gridptr);
  grid_t *(*copy)(grid_t *gridptr);
  void (*copyScalarFields)(grid_t *gridptrOrig, grid_t *gridptrDup);
  void (*copyArrayFields)(grid_t *gridptrOrig, grid_t *gridptrDup);
  void (*defIndices)(grid_t *gridptr, const int64_t *indices);
  SizeType (*inqIndices)(grid_t *gridptr, int64_t *indices);
  const int64_t *(*inqIndicesPtr)(grid_t *gridptr);
  void (*defXVals)(grid_t *gridptr, const double *xvals);
  void (*defYVals)(grid_t *gridptr, const double *yvals);
  void (*defMask)(grid_t *gridptr, const int *mask);
  void (*defMaskGME)(grid_t *gridptr, const int *mask);
  void (*defXBounds)(grid_t *gridptr, const double *xbounds);
  void (*defYBounds)(grid_t *gridptr, const double *ybounds);
  void (*defArea)(grid_t *gridptr, const double *area);
  double (*inqXVal)(grid_t *gridptr, SizeType index);
  double (*inqYVal)(grid_t *gridptr, SizeType index);
  SizeType (*inqXVals)(grid_t *gridptr, double *xvals);
  SizeType (*inqXValsPart)(grid_t *gridptr, int start, SizeType length, double *xvals);
  SizeType (*inqYVals)(grid_t *gridptr, double *yvals);
  SizeType (*inqYValsPart)(grid_t *gridptr, int start, SizeType length, double *yvals);
  const double *(*inqXValsPtr)(grid_t *gridptr);
  const double *(*inqYValsPtr)(grid_t *gridptr);
#ifndef USE_MPI
  int (*inqXIsc)(grid_t *gridptr);
  int (*inqYIsc)(grid_t *gridptr);
  SizeType (*inqXCvals)(grid_t *gridptr, char **xcvals);
  SizeType (*inqYCvals)(grid_t *gridptr, char **ycvals);
  const char **(*inqXCvalsPtr)(grid_t *gridptr);
  const char **(*inqYCvalsPtr)(grid_t *gridptr);
#endif
  double (*inqXInc)(grid_t *gridptr);
  double (*inqYInc)(grid_t *gridptr);
  // return true if for both grids, any xval and all yval differ
  bool (*compareXYFull)(grid_t *gridRef, grid_t *gridTest);
  // return if for both grids, x[0], y[0], x[size-1] and y[size-1] are respectively equal
  bool (*compareXYAO)(grid_t *gridRef, grid_t *gridTest);
  void (*inqArea)(grid_t *gridptr, double *area);
  const double *(*inqAreaPtr)(grid_t *gridptr);
  /* return 1 if inq property is set */
  int (*inqPropPresence)(grid_t *gridptr, enum gridPropInq inq);
  SizeType (*inqMask)(grid_t *gridptr, int *mask);
  int (*inqMaskGME)(grid_t *gridptr, int *mask_gme);
  SizeType (*inqXBounds)(grid_t *gridptr, double *xbounds);
  SizeType (*inqYBounds)(grid_t *gridptr, double *ybounds);
  const double *(*inqXBoundsPtr)(grid_t *gridptr);
  const double *(*inqYBoundsPtr)(grid_t *gridptr);
  int txCode;
  int (*getPackSize)(grid_t *gridptr, void *context);
  int (*getPackSizeScalars)(grid_t *gridptr, void *context);
  int (*getPackSizeArrays)(grid_t *gridptr, void *context);
  int (*unpack)(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id);
  grid_t *(*unpackScalars)(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context,
                           int force_id, int *memberMaskP);
  void (*unpackArrays)(grid_t *gridptr, int memberMask, char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos,
                       int originNamespace, void *context);
  void (*pack)(grid_t *gridptr, void *packBuffer, int packBufferSize, int *packBufferPos, void *context);
  /* return member mask */
  int (*packScalars)(grid_t *gridptr, void *packBuffer, int packBufferSize, int *packBufferPos, void *context);
  void (*packArrays)(grid_t *gridptr, int memberMask, void *packBuffer, int packBufferSize, int *packBufferPos, void *context);
};

struct gridaxis_t
{
  size_t size;  // number of values
  short flag;   // 0: undefined 1:vals 2:first+inc
  double first, last, inc;
  double *vals;
  double *bounds;
  cdi_keys_t keys;
#ifndef USE_MPI
  int clength;
  char **cvals;
#endif
};

// GME Grid
struct grid_gme_t
{
  int nd, ni, ni2, ni3;  // parameter for GRID_GME
};

struct grid_t
{
  char *name;
  int self;
  size_t size;
  int type;      // grid type
  int datatype;  // grid data type (used only internal in gridComplete())
  int proj;      // grid projection
  int projtype;  // grid projection type
  mask_t *mask;
  mask_t *mask_gme;
  int64_t *indices;
  double *area;
  struct grid_gme_t gme;
  int trunc;  // parameter for GRID_SPECTRAL
  int nvertex;
  int *reducedPoints;
  int reducedPointsSize;
  int np;                // number of parallels between a pole and the equator
  signed char isCyclic;  // three possible states:
                         // -1 if unknown,
                         //  0 if found not cyclic, or
                         //  1 for global cyclic grids
  bool lcomplex;
  bool hasdims;
  struct gridaxis_t x;
  struct gridaxis_t y;
  const struct gridVirtTable *vtable;
  cdi_keys_t keys;
  cdi_atts_t atts;
  void *extraData;
};

void grid_init(grid_t *gridptr);
void cdiGridTypeInit(grid_t *gridptr, int gridtype, size_t size);
void grid_free(grid_t *gridptr);
grid_t *grid_to_pointer(int gridID);
extern const struct gridVirtTable cdiGridVtable;

unsigned cdiGridCount(void);

void gridVerifyProj(int gridID);

double gridInqXincInMeter(int gridID);
double gridInqYincInMeter(int gridID);

// const double *gridInqXvalsPtr(int gridID);
// const double *gridInqYvalsPtr(int gridID);

const char **gridInqXCvalsPtr(int gridID);
const char **gridInqYCvalsPtr(int gridID);

// const double *gridInqXboundsPtr(int gridID);
// const double *gridInqYboundsPtr(int gridID);
const double *gridInqAreaPtr(int gridID);

int gridInqPropPresence(int gridID, enum gridPropInq inq);

int gridGenerate(const grid_t *grid);

// int gridIsEqual(int gridID1, int gridID2);

void cdiGridGetIndexList(unsigned, int *);

int gridUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id);

/* apply func to each grid */
enum cdiApplyRet cdiGridApply(enum cdiApplyRet (*func)(int id, void *res, void *data), void *data);

struct addIfNewRes
{
  int Id;
  int isNew;
};

struct addIfNewRes cdiVlistAddGridIfNew(int vlistID, grid_t *grid, int mode);

int gridVerifyProjParamsLCC(struct CDI_GridProjParams *gpp);
int gridVerifyProjParamsSTERE(struct CDI_GridProjParams *gpp);
int gridVerifyProjParamsHEALPIX(struct CDI_GridProjParams *gpp);

bool isGaussianLatitudes(size_t nlats, const double *latitudes);
void gaussianLatitudes(size_t nlats, double *latitudes, double *weights);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _STREAM_CDF_H
#define _STREAM_CDF_H


enum
{
  POSITIVE_UP = 1,
  POSITIVE_DOWN = 2,
};

enum
{
  CDF_MAX_TIME_UNIT_STR     /* maximum length of time unit string */
  = TAXIS_MAX_UNIT_STR_LEN  /* longest result from tunitNamePtr */
    + 7                     /* room for " since " */
    + 7 + 1 + 2 + 1 + 2     /* room for year with 7 digits,
                             * dashes and 2 digits for month and day */
    + 1 + 2 + 1 + 2 + 1 + 2 /* room for " " and 2 digit hour, minute,
                             *                          second */
    + 1                     /* and terminating '\0' */
};

int cdfDefVar(stream_t *streamptr, int varID);
void cdfDefCoordinateVars(stream_t *streamptr);
void cdfDefTimestep(stream_t *streamptr, int tsID, size_t valCount);
int cdfInqTimestep(stream_t *streamptr, int tsID);
int cdfInqContents(stream_t *streamptr);

void cdfEndDef(stream_t *streamptr);
void cdfDefField(stream_t *streamptr);

void cdfCopyField(stream_t *streamptr2, stream_t *streamptr1);

void cdfDefineAttributes(int filetype, int vlistID, int varID, int fileID, int ncvarID);

void cdf_read_field(stream_t *streamptr, int memType, void *data, size_t *numMissVals);
void cdf_write_field(stream_t *streamptr, int memType, const void *data, size_t numMissVals);

void cdf_read_var(stream_t *streamptr, int varID, int memType, void *data, size_t *numMissVals);
void cdf_write_var(stream_t *streamptr, int varID, int memType, const void *data, size_t numMissVals);

void cdf_read_var_slice(stream_t *streamptr, int varID, int levelID, int memType, void *data, size_t *numMissVals);
void cdf_write_var_slice(stream_t *streamptr, int varID, int levelID, int memType, const void *data, size_t numMissVals);

void cdf_read_var_part(stream_t *streamptr, int varID, int memType, int varType, int startpoint, size_t length, void *data,
                       size_t *numMissVals);
void cdf_read_var_slice_part(stream_t *streamptr, int varID, int levelID, int memType, int varType, int startpoint, size_t length,
                             void *data, size_t *numMissVals);

void cdf_write_var_chunk(stream_t *streamptr, int varID, int memType, const int rect[][2], const void *data, size_t numMissVals);

void cdfDefVarDeflate(int ncid, int ncvarid, int shuffle, int deflateLevel);
void cdfDefTime(stream_t *streamptr);

void cdf_scale_add(size_t size, double *data, double addoffset, double scalefactor);

int cdfDefDatatype(int datatype, stream_t *streamptr);

void cdf_create_records(stream_t *streamptr, size_t tsID);

#define ChunkSizeMax 65536
#define ChunkSizeLim 16777216
size_t calc_chunksize_x(int chunkType, long chunkSize, size_t xsize, bool yIsUndefined);
size_t calc_chunksize_y(int chunkType, size_t gridsize, size_t xsize, size_t ysize);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDF_LAZY_GRID_H_
#define CDF_LAZY_GRID_H_

#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_MMAP
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>
#endif

#include <string.h>


struct xyValGet
{
  double scalefactor, addoffset;
  size_t start[3], count[3], size, dimsize;
  int datasetNCId, varNCId;
  short ndims;
};

struct cdfLazyGridIds
{
  int datasetNCId, varNCId;
};

struct cdfLazyGrid
{
  grid_t base;
  const struct gridVirtTable *baseVtable;
  struct cdfLazyGridIds cellAreaGet, xBoundsGet, yBoundsGet;
  struct xyValGet xValsGet, yValsGet;
#ifdef HAVE_LIBPTHREAD
  pthread_mutex_t loadSerialize;
#endif
};

extern double *cdfPendingLoad;

void cdfLazyGridRenew(struct cdfLazyGrid *restrict *restrict gridpptr, int gridtype);
void cdfBaseGridRenew(struct cdfLazyGrid *restrict *restrict gridpptr, int gridtype);

void cdfLazyGridDestroy(struct cdfLazyGrid *lazyGrid);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF

static struct gridVirtTable cdfLazyGridVtable;
double *cdfPendingLoad;
#ifdef HAVE_LIBPTHREAD
static pthread_once_t cdfLazyInitialized = PTHREAD_ONCE_INIT;
#else
static bool cdfLazyInitialized;
#endif

#ifdef HAVE_LIBPTHREAD
#define lock_lazy_load(plGrid) pthread_mutex_lock(&((plGrid)->loadSerialize))
#define unlock_lazy_load(plGrid) pthread_mutex_unlock(&((plGrid)->loadSerialize))
#define destroy_lazy_load_lock(plGrid) pthread_mutex_destroy(&((plGrid)->loadSerialize))
#define init_lazy_load_lock(plGrid) pthread_mutex_init(&((plGrid)->loadSerialize), NULL)
#else
#define lock_lazy_load(plGrid)
#define unlock_lazy_load(plGrid)
#define destroy_lazy_load_lock(plGrid)
#define init_lazy_load_lock(plGrid)
#endif

void
cdfLazyGridDestroy(struct cdfLazyGrid *lazyGrid)
{
  lazyGrid->base.extraData = NULL;
  if (lazyGrid->base.area == cdfPendingLoad) lazyGrid->base.area = NULL;
  if (lazyGrid->base.x.vals == cdfPendingLoad) lazyGrid->base.x.vals = NULL;
  if (lazyGrid->base.y.vals == cdfPendingLoad) lazyGrid->base.y.vals = NULL;
  if (lazyGrid->base.x.bounds == cdfPendingLoad) lazyGrid->base.x.bounds = NULL;
  if (lazyGrid->base.y.bounds == cdfPendingLoad) lazyGrid->base.y.bounds = NULL;
  destroy_lazy_load_lock(lazyGrid);
}

static void
cdfLazyGridDelete(grid_t *grid)
{
  struct cdfLazyGrid *cdfGrid = (struct cdfLazyGrid *) grid;
  void (*baseDestroy)(grid_t * grid) = cdfGrid->baseVtable->destroy;
  cdfLazyGridDestroy(cdfGrid);
  baseDestroy(grid);
}

static void
cdfLazyGridDestroyOnce(void)
{
  /*
#ifdef HAVE_MMAP
  size_t pgSize = cdiGetPageSize(false);
  munmap(cdfPendingLoad, pgSize);
#endif
  */
}

static void
cdfLazyGridDefArea(grid_t *grid, const double *area)
{
  struct cdfLazyGrid *cdfGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(cdfGrid);
  if (grid->area == cdfPendingLoad) grid->area = NULL;
  cdfGrid->cellAreaGet.datasetNCId = -1;
  cdfGrid->cellAreaGet.varNCId = -1;
  cdfGrid->baseVtable->defArea(grid, area);
  unlock_lazy_load(cdfGrid);
}

static const double *
cdfLazyGridInqAreaPtr(grid_t *grid)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  if (grid->area == cdfPendingLoad)
  {
    grid->area = (double *) Malloc(grid->size * sizeof(double));
    cdf_get_var_double(lazyGrid->cellAreaGet.datasetNCId, lazyGrid->cellAreaGet.varNCId, grid->area);
  }
  unlock_lazy_load(lazyGrid);
  return lazyGrid->baseVtable->inqAreaPtr(grid);
}

static void
cdfLazyGridInqArea(grid_t *grid, double *area)
{
  grid->vtable->inqAreaPtr(grid);
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lazyGrid->baseVtable->inqArea(grid, area);
}

static void
cdfLazyLoadXYVals(struct xyValGet *valsGet, double **valsp)
{
  double *grid_vals = (double *) Malloc(valsGet->size * sizeof(double));
  *valsp = grid_vals;
  if (valsGet->ndims == 3)
    cdf_get_vara_double(valsGet->datasetNCId, valsGet->varNCId, valsGet->start, valsGet->count, grid_vals);
  else
    cdf_get_var_double(valsGet->datasetNCId, valsGet->varNCId, grid_vals);
  cdf_scale_add(valsGet->size, grid_vals, valsGet->addoffset, valsGet->scalefactor);
}

static const double *
cdfLazyGridInqXValsPtr(grid_t *grid)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  if (grid->x.vals == cdfPendingLoad) cdfLazyLoadXYVals(&lazyGrid->xValsGet, &grid->x.vals);
  unlock_lazy_load(lazyGrid);
  return lazyGrid->baseVtable->inqXValsPtr(grid);
}

static const double *
cdfLazyGridInqYValsPtr(grid_t *grid)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  if (grid->y.vals == cdfPendingLoad) cdfLazyLoadXYVals(&lazyGrid->yValsGet, &grid->y.vals);
  unlock_lazy_load(lazyGrid);
  return lazyGrid->baseVtable->inqYValsPtr(grid);
}

static double
cdfLazyGridInqXYVal(grid_t *grid, size_t index, const struct xyValGet *valsGet, double *vals,
                    const double *(*inqValsPtr)(grid_t *gridptr))
{
  size_t size = valsGet->size;
  double v;
  if (vals == cdfPendingLoad)
  {
    // prevent full load if only first/last values get inspected
    if (index == 0 || index == size - 1)
    {
      size_t indexND[3];
      if (valsGet->ndims == 3)
      {
        indexND[0] = 0;
        indexND[1] = index / valsGet->count[2];
        indexND[2] = index % valsGet->count[2];
      }
      else if (valsGet->ndims == 2)
      {
        indexND[0] = index / grid->x.size;
        indexND[1] = index % grid->x.size;
      }
      else
        indexND[0] = index;
      cdf_get_var1_double(valsGet->datasetNCId, valsGet->varNCId, indexND, &v);
    }
    else
    {
      const double *grid_vals = inqValsPtr(grid);
      v = grid_vals[index];
    }
  }
  else if (vals)
    v = vals[index];
  else
    v = 0.0;

  return v;
}

static void
cdfLazyGridDefXVals(grid_t *grid, const double *vals)
{
  struct cdfLazyGrid *cdfGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(cdfGrid);
  if (grid->x.vals == cdfPendingLoad) grid->x.vals = NULL;
  cdfGrid->xValsGet.datasetNCId = -1;
  cdfGrid->xValsGet.varNCId = -1;
  cdfGrid->baseVtable->defXVals(grid, vals);
  unlock_lazy_load(cdfGrid);
}

static void
cdfLazyGridDefYVals(grid_t *grid, const double *vals)
{
  struct cdfLazyGrid *cdfGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(cdfGrid);
  if (grid->y.vals == cdfPendingLoad) grid->y.vals = NULL;
  cdfGrid->yValsGet.datasetNCId = -1;
  cdfGrid->yValsGet.varNCId = -1;
  cdfGrid->baseVtable->defYVals(grid, vals);
  unlock_lazy_load(cdfGrid);
}

static double
cdfLazyGridInqXVal(grid_t *grid, SizeType index)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  double rv = cdfLazyGridInqXYVal(grid, (size_t) index, &lazyGrid->xValsGet, grid->x.vals, grid->vtable->inqXValsPtr);
  unlock_lazy_load(lazyGrid);
  return rv;
}

static double
cdfLazyGridInqYVal(grid_t *grid, SizeType index)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  double rv = cdfLazyGridInqXYVal(grid, (size_t) index, &lazyGrid->yValsGet, grid->y.vals, grid->vtable->inqYValsPtr);
  unlock_lazy_load(lazyGrid);
  return rv;
}

static bool
cdfLazyXYValGetCompare(struct cdfLazyGrid *lazyGridRef, struct cdfLazyGrid *lazyGridTest)
{
  struct xyValGet *valsGetXRef = &lazyGridRef->xValsGet, *valsGetYRef = &lazyGridRef->yValsGet,
                  *valsGetXTest = &lazyGridTest->xValsGet, *valsGetYTest = &lazyGridTest->yValsGet;
  if (valsGetXRef->datasetNCId == -1 || valsGetXTest->datasetNCId == -1 || valsGetYRef->datasetNCId == -1
      || valsGetYTest->datasetNCId == -1)
    return lazyGridRef->baseVtable->compareXYFull(&lazyGridRef->base, &lazyGridTest->base);

  return valsGetXRef->datasetNCId != valsGetXTest->datasetNCId || valsGetXRef->varNCId != valsGetXTest->varNCId
         || valsGetYRef->datasetNCId != valsGetYTest->datasetNCId || valsGetYRef->varNCId != valsGetYTest->varNCId;
}

static bool
cdfLazyCompareXYFull(grid_t *gridRef, grid_t *gridTest)
{
  bool diff;
  struct cdfLazyGrid *lazyGridRef = (struct cdfLazyGrid *) gridRef;
  if (gridTest->vtable == &cdfLazyGridVtable)
    diff = cdfLazyXYValGetCompare(lazyGridRef, (struct cdfLazyGrid *) gridTest);
  else
    diff = lazyGridRef->baseVtable->compareXYFull(gridRef, gridTest);
  return diff;
}

static bool
cdfLazyCompareXYAO(grid_t *gridRef, grid_t *gridTest)
{
  bool diff;
  struct cdfLazyGrid *lazyGridRef = (struct cdfLazyGrid *) gridRef;
  if (gridTest->vtable == &cdfLazyGridVtable)
    diff = cdfLazyXYValGetCompare(lazyGridRef, (struct cdfLazyGrid *) gridTest);
  else
    diff = lazyGridRef->baseVtable->compareXYAO(gridRef, gridTest);
  return diff;
}

static const double *
cdfLazyGridInqXBoundsPtr(grid_t *grid)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  if (grid->x.bounds == cdfPendingLoad)
  {
    grid->x.bounds = (double *) Malloc((size_t) grid->nvertex * grid->size * sizeof(double));
    cdf_get_var_double(lazyGrid->xBoundsGet.datasetNCId, lazyGrid->xBoundsGet.varNCId, grid->x.bounds);
  }
  unlock_lazy_load(lazyGrid);
  return lazyGrid->baseVtable->inqXBoundsPtr(grid);
}

static void
cdfLazyGridDefXBounds(grid_t *grid, const double *xbounds)
{
  struct cdfLazyGrid *cdfGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(cdfGrid);
  if (grid->x.bounds == cdfPendingLoad) grid->x.bounds = NULL;
  cdfGrid->xBoundsGet.datasetNCId = -1;
  cdfGrid->xBoundsGet.varNCId = -1;
  cdfGrid->baseVtable->defXBounds(grid, xbounds);
  unlock_lazy_load(cdfGrid);
}

static void
cdfLazyGridDefYBounds(grid_t *grid, const double *ybounds)
{
  struct cdfLazyGrid *cdfGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(cdfGrid);
  if (grid->y.bounds == cdfPendingLoad) grid->y.bounds = NULL;
  cdfGrid->yBoundsGet.datasetNCId = -1;
  cdfGrid->yBoundsGet.varNCId = -1;
  cdfGrid->baseVtable->defYBounds(grid, ybounds);
  unlock_lazy_load(cdfGrid);
}

static const double *
cdfLazyGridInqYBoundsPtr(grid_t *grid)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *) grid;
  lock_lazy_load(lazyGrid);
  if (grid->y.bounds == cdfPendingLoad)
  {
    grid->y.bounds = (double *) Malloc((size_t) grid->nvertex * grid->size * sizeof(double));
    cdf_get_var_double(lazyGrid->yBoundsGet.datasetNCId, lazyGrid->yBoundsGet.varNCId, grid->y.bounds);
  }
  unlock_lazy_load(lazyGrid);
  return lazyGrid->baseVtable->inqYBoundsPtr(grid);
}

static void
cdfLazyGridCopyScalarFields(grid_t *gridptrOrig, grid_t *gridptrDup)
{
  struct cdfLazyGrid *lazyGridDup = (struct cdfLazyGrid *) gridptrDup, *lazyGridOrig = (struct cdfLazyGrid *) gridptrOrig;
  lazyGridOrig->baseVtable->copyScalarFields(gridptrOrig, &lazyGridDup->base);
  lazyGridDup->baseVtable = lazyGridOrig->baseVtable;
  lazyGridDup->cellAreaGet = lazyGridOrig->cellAreaGet;
  lazyGridDup->xBoundsGet = lazyGridOrig->xBoundsGet;
  lazyGridDup->yBoundsGet = lazyGridOrig->yBoundsGet;
  lazyGridDup->xValsGet = lazyGridOrig->xValsGet;
  lazyGridDup->yValsGet = lazyGridOrig->yValsGet;
  init_lazy_load_lock(lazyGridDup);
}

static void
cdfLazyGridCopyArrayFields(grid_t *gridptrOrig, grid_t *gridptrDup)
{
  size_t reducedPointsSize = (size_t) gridptrOrig->reducedPointsSize;
  size_t gridsize = gridptrOrig->size;
  int gridtype = gridptrOrig->type;
  int irregular = (gridtype == GRID_CURVILINEAR || gridtype == GRID_UNSTRUCTURED);

  if (reducedPointsSize)
  {
    gridptrDup->reducedPoints = (int *) Malloc(reducedPointsSize * sizeof(int));
    memcpy(gridptrDup->reducedPoints, gridptrOrig->reducedPoints, reducedPointsSize * sizeof(int));
  }

  if (gridptrOrig->x.vals != NULL && gridptrOrig->x.vals != cdfPendingLoad)
  {
    size_t size = irregular ? gridsize : gridptrOrig->x.size;
    gridptrDup->x.vals = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->x.vals, gridptrOrig->x.vals, size * sizeof(double));
  }

  if (gridptrOrig->y.vals != NULL && gridptrOrig->y.vals != cdfPendingLoad)
  {
    size_t size = irregular ? gridsize : gridptrOrig->y.size;
    gridptrDup->y.vals = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->y.vals, gridptrOrig->y.vals, size * sizeof(double));
  }

  if (gridptrOrig->x.bounds != NULL && gridptrOrig->x.bounds != cdfPendingLoad)
  {
    size_t size = (irregular ? gridsize : gridptrOrig->x.size) * (size_t) gridptrOrig->nvertex;
    gridptrDup->x.bounds = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->x.bounds, gridptrOrig->x.bounds, size * sizeof(double));
  }

  if (gridptrOrig->y.bounds != NULL && gridptrOrig->y.bounds != cdfPendingLoad)
  {
    size_t size = (irregular ? gridsize : gridptrOrig->y.size) * (size_t) gridptrOrig->nvertex;
    gridptrDup->y.bounds = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->y.bounds, gridptrOrig->y.bounds, size * sizeof(double));
  }

  {
    if (gridptrOrig->area != NULL && gridptrOrig->area != cdfPendingLoad)
    {
      size_t size = gridsize;
      gridptrDup->area = (double *) Malloc(size * sizeof(double));
      memcpy(gridptrDup->area, gridptrOrig->area, size * sizeof(double));
    }
  }

  if (gridptrOrig->mask != NULL)
  {
    size_t size = gridsize;
    gridptrDup->mask = (mask_t *) Malloc(size * sizeof(mask_t));
    memcpy(gridptrDup->mask, gridptrOrig->mask, size * sizeof(mask_t));
  }

  if (gridptrOrig->mask_gme != NULL)
  {
    size_t size = gridsize;
    gridptrDup->mask_gme = (mask_t *) Malloc(size * sizeof(mask_t));
    memcpy(gridptrDup->mask_gme, gridptrOrig->mask_gme, size * sizeof(mask_t));
  }
}

static grid_t *
cdfLazyGridCopy(grid_t *gridptrOrig)
{
  struct cdfLazyGrid *lazyGridDup = (struct cdfLazyGrid *) Malloc(sizeof(*lazyGridDup));
  gridptrOrig->vtable->copyScalarFields(gridptrOrig, &lazyGridDup->base);
  gridptrOrig->vtable->copyArrayFields(gridptrOrig, &lazyGridDup->base);
  return &lazyGridDup->base;
}

static void
cdfLazyGridInitOnce(void)
{
  cdfLazyGridVtable = cdiGridVtable;
  cdfLazyGridVtable.destroy = cdfLazyGridDelete;
  cdfLazyGridVtable.copy = cdfLazyGridCopy;
  cdfLazyGridVtable.copyScalarFields = cdfLazyGridCopyScalarFields;
  cdfLazyGridVtable.copyArrayFields = cdfLazyGridCopyArrayFields;
  cdfLazyGridVtable.defArea = cdfLazyGridDefArea;
  cdfLazyGridVtable.inqAreaPtr = cdfLazyGridInqAreaPtr;
  cdfLazyGridVtable.inqArea = cdfLazyGridInqArea;
  cdfLazyGridVtable.inqXValsPtr = cdfLazyGridInqXValsPtr;
  cdfLazyGridVtable.inqYValsPtr = cdfLazyGridInqYValsPtr;
  cdfLazyGridVtable.inqXVal = cdfLazyGridInqXVal;
  cdfLazyGridVtable.inqYVal = cdfLazyGridInqYVal;
  cdfLazyGridVtable.defXVals = cdfLazyGridDefXVals;
  cdfLazyGridVtable.defYVals = cdfLazyGridDefYVals;
  cdfLazyGridVtable.compareXYFull = cdfLazyCompareXYFull;
  cdfLazyGridVtable.compareXYAO = cdfLazyCompareXYAO;
  cdfLazyGridVtable.defXBounds = cdfLazyGridDefXBounds;
  cdfLazyGridVtable.defYBounds = cdfLazyGridDefYBounds;
  cdfLazyGridVtable.inqXBoundsPtr = cdfLazyGridInqXBoundsPtr;
  cdfLazyGridVtable.inqYBoundsPtr = cdfLazyGridInqYBoundsPtr;
  /* create inaccessible memory area, if possible, this serves as
   * dummy value for pointers to data not yet loaded */
  /*
#ifdef HAVE_MMAP
  {
    size_t pgSize = cdiGetPageSize(false);
    static const char devZero[] = "/dev/zero";
    int fd = open(devZero, O_RDWR);
    if (fd == -1)
      SysError("Could not open %s to map anonymous memory", devZero);
    void *cdfInvalid = mmap(NULL, pgSize, PROT_NONE, MAP_PRIVATE, fd, 0);
    if (cdfInvalid == MAP_FAILED)
      SysError("Could not mmap anonymous memory");
    cdfPendingLoad = cdfInvalid;
    int rc = close(fd);
    if (rc == -1)
      SysError("Could not close %s file handle %d after mapping anonymous"
               " memory", devZero, fd);
  }
#else
  */
  cdfPendingLoad = (double *) &cdfPendingLoad;
  // #endif
  atexit(cdfLazyGridDestroyOnce);
#ifndef HAVE_LIBPTHREAD
  cdfLazyInitialized = true;
#endif
}

static void
cdfBaseGridInit(grid_t *grid, int gridtype)
{
  grid_init(grid);
  cdiGridTypeInit(grid, gridtype, 0);
}

static void
cdfLazyGridInit(struct cdfLazyGrid *grid, int gridtype)
{
#ifdef HAVE_LIBPTHREAD
  pthread_once(&cdfLazyInitialized, cdfLazyGridInitOnce);
#else
  if (!cdfLazyInitialized) cdfLazyGridInitOnce();
#endif
  cdfBaseGridInit(&grid->base, gridtype);
  grid->baseVtable = grid->base.vtable;
  grid->cellAreaGet.datasetNCId = -1;
  grid->cellAreaGet.varNCId = -1;
  grid->xValsGet.datasetNCId = -1;
  grid->xValsGet.varNCId = -1;
  grid->yValsGet.datasetNCId = -1;
  grid->yValsGet.varNCId = -1;
  grid->xBoundsGet.datasetNCId = -1;
  grid->xBoundsGet.varNCId = -1;
  grid->yBoundsGet.datasetNCId = -1;
  grid->yBoundsGet.varNCId = -1;
  grid->base.vtable = &cdfLazyGridVtable;
  init_lazy_load_lock(grid);
}

void
cdfLazyGridRenew(struct cdfLazyGrid *restrict *restrict gridpptr, int gridtype)
{
  struct cdfLazyGrid *restrict grid = *gridpptr;
  if (!grid) *gridpptr = grid = (struct cdfLazyGrid *) Malloc(sizeof(*grid));
  cdfLazyGridInit(grid, gridtype);
}

void
cdfBaseGridRenew(struct cdfLazyGrid *restrict *restrict gridpptr, int gridtype)
{
  struct cdfLazyGrid *restrict grid = *gridpptr;
  if (!grid) *gridpptr = grid = (struct cdfLazyGrid *) Malloc(sizeof(grid_t));
  cdfBaseGridInit((grid_t *) grid, gridtype);
}

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDI_CKSUM_H_
#define CDI_CKSUM_H_

#include <inttypes.h>

/* single checksum computation over one array */
uint32_t cdiCheckSum(int type, int count, const void *data);

/* composable check-sum computation,
 * 0. datatype,
 * 1. init,
 * 2. partial, appendable computation, and
 * 3. final checksum-computation
 */
struct cdiCheckSumState
{
  uint32_t sum;
  off_t len;
};

void cdiCheckSumRStart(struct cdiCheckSumState *state);
void cdiCheckSumRAdd(struct cdiCheckSumState *state, int type, int count, const void *data);
uint32_t cdiCheckSumRValue(struct cdiCheckSumState state);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <inttypes.h>
#include <sys/types.h>

void memcrc_r(uint32_t *state, const unsigned char *block, size_t block_len);

void memcrc_r_eswap(uint32_t *state, const unsigned char *elems, size_t num_elems, size_t elem_size);

uint32_t memcrc_finish(uint32_t *state, off_t total_size);

uint32_t memcrc(const unsigned char *b, size_t n);

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifndef SERIALIZE_H
#define SERIALIZE_H

#include <string.h>

#ifndef CDI_CKSUM_H_
#endif
#ifndef CDI_KEY_H_
#endif
#ifndef ERROR_H
#endif

/*
 * Generic interfaces for (de-)marshalling
 */
int serializeGetSize(int count, int datatype, void *context);
void serializePack(const void *data, int count, int datatype, void *buf, int buf_size, int *position, void *context);
void serializeUnpack(const void *buf, int buf_size, int *position, void *data, int count, int datatype, void *context);

/*
 * (de-)marshalling function for key/value structures
 */
static inline int
serializeKeysGetPackSize(const cdi_keys_t *keysp, void *context)
{
  int packBuffSize = 0;

  int nelems = (int) keysp->nelems;
  packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, context);
  for (int keyid = 0; keyid < nelems; keyid++)
  {
    const cdi_key_t *keyp = &(keysp->value[keyid]);
    int type = keyp->type;
    packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, context);  // key
    packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, context);  // type
    if (type == KeyBytes)
    {
      int length = keyp->length;
      packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, context) + serializeGetSize(length, CDI_DATATYPE_TXT, context);
    }
    else if (type == KeyInt) { packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, context); }
    else if (type == KeyFloat) { packBuffSize += serializeGetSize(1, CDI_DATATYPE_FLT64, context); }
  }
  packBuffSize += serializeGetSize(1, CDI_DATATYPE_UINT32, context);
  return packBuffSize;
}

static inline void
serializeKeysPack(const cdi_keys_t *keysp, void *buf, int buf_size, int *position, void *context)
{
  uint32_t d = 0;

  int nelems = (int) keysp->nelems;
  serializePack(&nelems, 1, CDI_DATATYPE_INT, buf, buf_size, position, context);
  for (int keyid = 0; keyid < nelems; keyid++)
  {
    const cdi_key_t *keyp = &(keysp->value[keyid]);
    int key = keyp->key;
    int type = keyp->type;
    serializePack(&key, 1, CDI_DATATYPE_INT, buf, buf_size, position, context);
    serializePack(&type, 1, CDI_DATATYPE_INT, buf, buf_size, position, context);
    if (type == KeyBytes)
    {
      int length = keyp->length;
      serializePack(&length, 1, CDI_DATATYPE_INT, buf, buf_size, position, context);
      serializePack(keyp->v.s, length, CDI_DATATYPE_TXT, buf, buf_size, position, context);
      d ^= cdiCheckSum(CDI_DATATYPE_TXT, length, keyp->v.s);
    }
    else if (type == KeyInt) { serializePack(&keyp->v.i, 1, CDI_DATATYPE_INT, buf, buf_size, position, context); }
    else if (type == KeyFloat) { serializePack(&keyp->v.d, 1, CDI_DATATYPE_FLT64, buf, buf_size, position, context); }
  }

  serializePack(&d, 1, CDI_DATATYPE_UINT32, buf, buf_size, position, context);
}

static inline void
serializeKeysUnpack(const void *buf, int buf_size, int *position, cdi_keys_t *keysp, void *context)
{
  uint32_t d, d2 = 0;
  void *buffer = NULL;
  int buffersize = 0;

  int nelems;
  serializeUnpack(buf, buf_size, position, &nelems, 1, CDI_DATATYPE_INT, context);
  for (int i = 0; i < nelems; ++i)
  {
    int key, type;
    serializeUnpack(buf, buf_size, position, &key, 1, CDI_DATATYPE_INT, context);
    serializeUnpack(buf, buf_size, position, &type, 1, CDI_DATATYPE_INT, context);
    if (type == KeyBytes)
    {
      int length;
      serializeUnpack(buf, buf_size, position, &length, 1, CDI_DATATYPE_INT, context);
      if (length > buffersize)
      {
        buffersize = length;
        buffer = Realloc(buffer, (size_t) buffersize);
      }
      serializeUnpack(buf, buf_size, position, buffer, length, CDI_DATATYPE_TXT, context);
      cdiDefVarKeyBytes(keysp, key, (unsigned char *) buffer, length);
      d2 ^= cdiCheckSum(CDI_DATATYPE_TXT, length, buffer);
    }
    else if (type == KeyInt)
    {
      int ival;
      serializeUnpack(buf, buf_size, position, &ival, 1, CDI_DATATYPE_INT, context);
      cdiDefVarKeyInt(keysp, key, ival);
    }
    else if (type == KeyFloat)
    {
      double dval;
      serializeUnpack(buf, buf_size, position, &dval, 1, CDI_DATATYPE_FLT64, context);
      cdiDefVarKeyFloat(keysp, key, dval);
    }
  }
  serializeUnpack(buf, buf_size, position, &d, 1, CDI_DATATYPE_UINT32, context);
  xassert(d == d2);
  if (buffer) Free(buffer);
}

/*
 * (de-)marshalling function for common data structures
 */
static inline int
serializeStrTabGetPackSize(const char **strTab, int numStr, void *context)
{
  xassert(numStr >= 0);
  int packBuffSize = 0;
  for (size_t i = 0; i < (size_t) numStr; ++i)
  {
    size_t len = strlen(strTab[i]);
    packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, context) + serializeGetSize((int) len, CDI_DATATYPE_TXT, context);
  }
  packBuffSize += serializeGetSize(1, CDI_DATATYPE_UINT32, context);
  return packBuffSize;
}

static inline void
serializeStrTabPack(const char **strTab, int numStr, void *buf, int buf_size, int *position, void *context)
{
  uint32_t d = 0;
  xassert(numStr >= 0);
  for (size_t i = 0; i < (size_t) numStr; ++i)
  {
    int len = (int) strlen(strTab[i]);
    serializePack(&len, 1, CDI_DATATYPE_INT, buf, buf_size, position, context);
    serializePack(strTab[i], len, CDI_DATATYPE_TXT, buf, buf_size, position, context);
    d ^= cdiCheckSum(CDI_DATATYPE_TXT, len, strTab[i]);
  }
  serializePack(&d, 1, CDI_DATATYPE_UINT32, buf, buf_size, position, context);
}

static inline void
serializeStrTabUnpack(const void *buf, int buf_size, int *position, char **strTab, int numStr, void *context)
{
  uint32_t d, d2 = 0;
  xassert(numStr >= 0);
  for (size_t i = 0; i < (size_t) numStr; ++i)
  {
    int len;
    serializeUnpack(buf, buf_size, position, &len, 1, CDI_DATATYPE_INT, context);
    serializeUnpack(buf, buf_size, position, strTab[i], len, CDI_DATATYPE_TXT, context);
    strTab[i][len] = '\0';
    d2 ^= cdiCheckSum(CDI_DATATYPE_TXT, len, strTab[i]);
  }
  serializeUnpack(buf, buf_size, position, &d, 1, CDI_DATATYPE_UINT32, context);
  xassert(d == d2);
}

/*
 * Interfaces for marshalling within a single memory domain
 */
int serializeGetSizeInCore(int count, int datatype, void *context);
void serializePackInCore(const void *data, int count, int datatype, void *buf, int buf_size, int *position, void *context);
void serializeUnpackInCore(const void *buf, int buf_size, int *position, void *data, int count, int datatype, void *context);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <inttypes.h>
#include <sys/types.h>
#include <stdlib.h>


uint32_t
cdiCheckSum(int type, int count, const void *buffer)
{
  uint32_t s = 0U;
  xassert(count >= 0);
  size_t elemSize = (size_t) serializeGetSizeInCore(1, type, NULL);
  memcrc_r_eswap(&s, (const unsigned char *) buffer, (size_t) count, elemSize);
  s = memcrc_finish(&s, (off_t) (elemSize * (size_t) count));
  return s;
}

void
cdiCheckSumRStart(struct cdiCheckSumState *state)
{
  state->sum = 0U;
  state->len = 0;
}

void
cdiCheckSumRAdd(struct cdiCheckSumState *state, int type, int count, const void *buffer)
{
  size_t elemSize = (size_t) serializeGetSizeInCore(1, type, NULL);
  memcrc_r_eswap(&state->sum, (const unsigned char *) buffer, (size_t) count, elemSize);
  state->len += (off_t) (elemSize * (size_t) count);
}

uint32_t
cdiCheckSumRValue(struct cdiCheckSumState state)
{
  return memcrc_finish(&state.sum, state.len);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>
#include <string.h>
#include <errno.h>

const char *
cdiStringError(int cdiErrno)
{
  // clang-format off
  static const char UnknownError[] = "Unknown Error";
  static const char _ETMOF[]       = "Too many open files";
  static const char _EINVAL[]      = "Invalid argument";
  static const char _EISDIR[]      = "Is a directory";
  static const char _EISEMPTY[]    = "File is empty";
  static const char _EUFTYPE[]     = "Unsupported file type";
  static const char _ELIBNAVAIL[]  = "Unsupported file type (library support not compiled in)";
  static const char _EUFSTRUCT[]   = "Unsupported file structure";
  static const char _EUNC4[]       = "Unsupported NetCDF4 structure";
  static const char _EDIMSIZE[]    = "Invalid dimension size";
  static const char _EQENF[]       = "Query entries not found";
  static const char _EQNAVAIL[]    = "Query not available for file type";
  static const char _ELIMIT[]      = "Internal limits exceeded";

  if (cdiErrno < -1000)
    {
      const char *cdfErrorString = cdf_strerror(cdiErrno + 1000);
      if (cdfErrorString) return cdfErrorString;
    }

  switch (cdiErrno) {
  case CDI_ESYSTEM:
    {
      const char *cp = strerror(errno);
      if (cp == NULL) break;
      return cp;
    }
  case CDI_ETMOF:      return _ETMOF;
  case CDI_EINVAL:     return _EINVAL;
  case CDI_EISDIR:     return _EISDIR;
  case CDI_EISEMPTY:   return _EISEMPTY;
  case CDI_EUFTYPE:    return _EUFTYPE;
  case CDI_ELIBNAVAIL: return _ELIBNAVAIL;
  case CDI_EUFSTRUCT:  return _EUFSTRUCT;
  case CDI_EUNC4:      return _EUNC4;
  case CDI_EDIMSIZE:   return _EDIMSIZE;
  case CDI_EQENF:      return _EQENF;
  case CDI_EQNAVAIL:   return _EQNAVAIL;
  case CDI_ELIMIT:     return _ELIMIT;
  }
  // clang-format on
  return UnknownError;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>
#include <stdlib.h>

// ==================================================================
#ifdef __cplusplus
extern "C"
{
#endif

  // clang-format off
void
cdiDecodeDate(int date, int *year, int *month, int *day)
{
  int iyear = date / 10000;
  *year = iyear;
  int idate = date - iyear * 10000;
  if (idate < 0) idate = -idate;
  int imonth = idate / 100;
  *month = imonth;
  *day = idate - imonth * 100;
}

int
cdiEncodeDate(int year, int month, int day)
{
  int iyear = abs(year);
  int date = iyear * 10000 + month * 100 + day;
  if (year < 0) date = -date;

  return date;
}

void
cdiDecodeTime(int time, int *hour, int *minute, int *second)
{
  int ihour = time / 10000, itime = time - ihour * 10000, iminute = itime / 100;
  *hour = ihour;
  *minute = iminute;
  *second = itime - iminute * 100;
}

int
cdiEncodeTime(int hour, int minute, int second)
{
  return hour * 10000 + minute * 100 + second;
}
  // clang-format on

#ifdef __cplusplus
}
#endif
// ==================================================================

CdiDate
cdiDate_set(int64_t date)
{
  int64_t iyear = date / 10000;
  int year = (int) iyear;
  int64_t idate = date - iyear * 10000;
  if (idate < 0) idate = -idate;
  int64_t imonth = idate / 100;
  int month = (int) imonth;
  int day = (int) (idate - imonth * 100);

  CdiDate cdiDate;
  cdiDate.year = year;
  cdiDate.month = (short) month;
  cdiDate.day = (short) day;

  return cdiDate;
}

CdiTime
cdiTime_set(int time)
{
  int hour, minute, second, ms = 0;
  cdiDecodeTime(time, &hour, &minute, &second);

  CdiTime cdiTime;
  cdiTime.hour = (short) hour;
  cdiTime.minute = (short) minute;
  cdiTime.second = (short) second;
  cdiTime.ms = (short) ms;

  return cdiTime;
}

CdiDateTime
cdiDateTime_set(int64_t date, int time)
{
  CdiDateTime cdiDateTime;
  cdiDateTime.date = cdiDate_set(date);
  cdiDateTime.time = cdiTime_set(time);

  return cdiDateTime;
}

int64_t
cdiDate_get(CdiDate cdiDate)
{
  int64_t iyear = abs(cdiDate.year);
  int64_t date = iyear * 10000 + cdiDate.month * 100 + cdiDate.day;
  if (cdiDate.year < 0) date = -date;

  return date;
}

int
cdiTime_get(CdiTime cdiTime)
{
  return cdiEncodeTime(cdiTime.hour, cdiTime.minute, cdiTime.second);
}

CdiDate
cdiDate_encode(int year, int month, int day)
{
  CdiDate cdiDate;
  cdiDate.year = year;
  cdiDate.month = (short) month;
  cdiDate.day = (short) day;

  return cdiDate;
}

void
cdiDate_decode(CdiDate cdiDate, int *year, int *month, int *day)
{
  *year = cdiDate.year;
  *month = cdiDate.month;
  *day = cdiDate.day;
}

CdiTime
cdiTime_encode(int hour, int minute, int second, int ms)
{
  CdiTime cdiTime;
  cdiTime.hour = (short) hour;
  cdiTime.minute = (short) minute;
  cdiTime.second = (short) second;
  cdiTime.ms = (short) ms;

  return cdiTime;
}

void
cdiTime_decode(CdiTime cdiTime, int *hour, int *minute, int *second, int *ms)
{
  *hour = cdiTime.hour;
  *minute = cdiTime.minute;
  *second = cdiTime.second;
  *ms = cdiTime.ms;
}

void
cdiDate_init(CdiDate *cdiDate)
{
  cdiDate->year = 0;
  cdiDate->month = 0;
  cdiDate->day = 0;
}

void
cdiTime_init(CdiTime *cdiTime)
{
  cdiTime->hour = 0;
  cdiTime->minute = 0;
  cdiTime->second = 0;
  cdiTime->ms = 0;
}

void
cdiDateTime_init(CdiDateTime *cdiDateTime)
{
  cdiDate_init(&cdiDateTime->date);
  cdiTime_init(&cdiDateTime->time);
}

bool
cdiDate_isEQ(CdiDate cdiDate1, CdiDate cdiDate2)
{
  // clang-format off
  return (cdiDate1.year  == cdiDate2.year
       && cdiDate1.month == cdiDate2.month
       && cdiDate1.day   == cdiDate2.day);
  // clang-format on
}

bool
cdiTime_isEQ(CdiTime cdiTime1, CdiTime cdiTime2)
{
  // clang-format off
  return (cdiTime1.hour   == cdiTime2.hour
       && cdiTime1.minute == cdiTime2.minute
       && cdiTime1.second == cdiTime2.second
       && cdiTime1.ms     == cdiTime2.ms);
  // clang-format on
}

bool
cdiDateTime_isEQ(CdiDateTime cdiDateTime1, CdiDateTime cdiDateTime2)
{
  // clang-format off
  return (cdiDateTime1.date.year   == cdiDateTime2.date.year
       && cdiDateTime1.date.month  == cdiDateTime2.date.month
       && cdiDateTime1.date.day    == cdiDateTime2.date.day
       && cdiDateTime1.time.hour   == cdiDateTime2.time.hour
       && cdiDateTime1.time.minute == cdiDateTime2.time.minute
       && cdiDateTime1.time.second == cdiDateTime2.time.second
       && cdiDateTime1.time.ms     == cdiDateTime2.time.ms);
  // clang-format on
}

bool
cdiDateTime_isNE(CdiDateTime cdiDateTime1, CdiDateTime cdiDateTime2)
{
  return !cdiDateTime_isEQ(cdiDateTime1, cdiDateTime2);
}

bool
cdiDateTime_isLT(CdiDateTime cdiDateTime1, CdiDateTime cdiDateTime2)
{
  int64_t date1 = cdiDate_get(cdiDateTime1.date);
  int64_t date2 = cdiDate_get(cdiDateTime2.date);
  int time1 = cdiTime_get(cdiDateTime1.time);
  int time2 = cdiTime_get(cdiDateTime2.time);
  return (date1 < date2 || (date1 == date2 && time1 < time2));
}

bool
cdiDateTime_isNull(CdiDateTime cdiDateTime)
{
  // clang-format off
  return (cdiDateTime.date.year == 0
       && cdiDateTime.date.month == 0
       && cdiDateTime.date.day == 0
       && cdiDateTime.time.hour == 0
       && cdiDateTime.time.minute == 0
       && cdiDateTime.time.second == 0
       && cdiDateTime.time.ms == 0);
  // clang-format on
}

#define DATE_FORMAT "%5.4d-%2.2d-%2.2d"
#define TIME_FORMAT "%2.2d:%2.2d:%2.2d"

const char *
CdiDateTime_string(CdiDateTime cdiDateTime)
{
  int year, month, day;
  cdiDate_decode(cdiDateTime.date, &year, &month, &day);
  int hour, minute, second, ms;
  cdiTime_decode(cdiDateTime.time, &hour, &minute, &second, &ms);

  static char datetimeString[64];
  snprintf(datetimeString, sizeof(datetimeString), DATE_FORMAT "T" TIME_FORMAT, year, month, day, hour, minute, second);

  return datetimeString;
}
#ifndef CDF_CONFIG_H
#define CDF_CONFIG_H

#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF

#include <netcdf.h>
#include <netcdf_meta.h>

#ifdef NC_FORMAT_64BIT_DATA
#define HAVE_NETCDF5 1
#endif

#if defined NC_HAS_NC2 && NC_HAS_NC2
#define HAVE_NETCDF2 1
#endif

#if defined NC_HAS_NCZARR && NC_HAS_NCZARR
#define HAVE_NCZARR 1
#endif

#if ((NC_VERSION_MAJOR == 4 && NC_VERSION_MINOR >= 9) || NC_VERSION_MAJOR >= 5)
#define HAVE_NC4FILTER 1
#endif

#endif  // HAVE_LIBNETCDF

#endif  // CDF_CONFIG_H
#ifndef GRIBAPI_H
#define GRIBAPI_H

#ifdef HAVE_LIBGRIB_API
#include <grib_api.h>
#ifndef ERROR_H
#endif
#endif

#ifndef CDI_INT_H
#endif

// clang-format off

#define  GRIBAPI_MISSVAL  -9.E33

// GRIB2 Level Types
#define  GRIB2_LTYPE_SURFACE               1
#define  GRIB2_LTYPE_CLOUD_BASE            2
#define  GRIB2_LTYPE_CLOUD_TOP             3
#define  GRIB2_LTYPE_ISOTHERM0             4
#define  GRIB2_LTYPE_TROPOPAUSE            7
#define  GRIB2_LTYPE_TOA                   8
#define  GRIB2_LTYPE_SEA_BOTTOM            9
#define  GRIB2_LTYPE_ATMOSPHERE           10
#define  GRIB2_LTYPE_ISOBARIC            100
#define  GRIB2_LTYPE_MEANSEA             101
#define  GRIB2_LTYPE_ALTITUDE            102
#define  GRIB2_LTYPE_HEIGHT              103
#define  GRIB2_LTYPE_SIGMA               104
#define  GRIB2_LTYPE_HYBRID              105
#define  GRIB2_LTYPE_LANDDEPTH           106
#define  GRIB2_LTYPE_ISENTROPIC          107
#define  GRIB2_LTYPE_SNOW                114
#define  GRIB2_LTYPE_REFERENCE           150
#define  GRIB2_LTYPE_SEADEPTH            160  // Depth Below Sea Level
#define  GRIB2_LTYPE_LAKE_BOTTOM         162  // Lake or River Bottom
#define  GRIB2_LTYPE_SEDIMENT_BOTTOM     163  // Bottom Of Sediment Layer
#define  GRIB2_LTYPE_SEDIMENT_BOTTOM_TA  164  // Bottom Of Thermally Active Sediment Layer
#define  GRIB2_LTYPE_SEDIMENT_BOTTOM_TW  165  // Bottom Of Sediment Layer Penetrated By Thermal Wave
#define  GRIB2_LTYPE_MIX_LAYER           166  // Mixing Layer

// GRIB2 Data representation type (Grid Type)
#define  GRIB2_GTYPE_LATLON                0  // Latitude/longitude (or equidistant cylindrical, or Plate Carree)
#define  GRIB2_GTYPE_LATLON_ROT            1  // Rotated Latitude/longitude
#define  GRIB2_GTYPE_LATLON_STR            2  // Stretched Latitude/longitude
#define  GRIB2_GTYPE_LATLON_ROTSTR         3  // Stretched and Rotated Latitude/longitude
#define  GRIB2_GTYPE_STERE                20  // Polar stereographic projection
#define  GRIB2_GTYPE_LCC                  30  // Lambert conformal
#define  GRIB2_GTYPE_LLAM                 33  // Lambert LAM
#define  GRIB2_GTYPE_GAUSSIAN             40  // Gaussian latitude/longitude
#define  GRIB2_GTYPE_GAUSSIAN_ROT         41  // Rotated Gaussian latitude/longitude
#define  GRIB2_GTYPE_GAUSSIAN_STR         42  // Stretched Gaussian latitude/longitude
#define  GRIB2_GTYPE_GAUSSIAN_ROTSTR      43  // Stretched and rotated Gaussian latitude/longitude
#define  GRIB2_GTYPE_SPECTRAL             50  // Spherical harmonic coefficients
#define  GRIB2_GTYPE_GME                 100  // Triangular grid based on an icosahedron (GME)
#define  GRIB2_GTYPE_UNSTRUCTURED        101  // General Unstructured Grid
#define  GRIB2_GTYPE_HEALPIX             150  // HEALPix Grid

const char *gribapiLibraryVersionString(void);
void gribContainersNew(stream_t *streamptr);
void gribContainersDelete(stream_t *streamptr);

#ifdef HAVE_LIBGRIB_API

#ifdef ECCODES_VERSION
#if ECCODES_VERSION >= 23000
#define HAVE_GRIBAPI_FLOAT_INTERFACE 1
#endif
#endif

static inline int have_gribapi_float_interface(void)
{
#ifdef HAVE_GRIBAPI_FLOAT_INTERFACE
  return 1;
#else
  return 0;
#endif
}

static inline int my_grib_set_double(grib_handle* h, const char* key, double val)
{
  if (CDI_gribapi_debug)
    fprintf(stderr, "grib_set_double(\tgrib_handle* h, \"%s\", %f)\n", key, val);

  int retVal = grib_set_double(h, key, val);
  if (retVal != 0)
    fprintf(stderr, "!!! failed call to grib_set_double(\tgrib_handle* h, \"%s\", %f) !!!\n", key, val);
  return retVal;
}

static inline int my_grib_set_long(grib_handle* h, const char* key, long val)
{
  if (CDI_gribapi_debug)
    fprintf(stderr, "grib_set_long(  \tgrib_handle* h, \"%s\", %ld)\n", key, val);

  int retVal = grib_set_long(h, key, val);
  if (retVal != 0)
    fprintf(stderr, "!!! failed call to grib_set_long(  \tgrib_handle* h, \"%s\", %ld) !!!\n", key, val);
  return retVal;
}

static inline int my_grib_set_string(grib_handle* h, const char* key, const char* val, size_t* length)
{
  if (CDI_gribapi_debug)
    fprintf(stderr, "grib_set_string(\tgrib_handle* h, \"%s\", \"%s\")\n", key, val);

  int ret_val = grib_set_string(h, key, val, length);
  if (ret_val != 0)
    fprintf(stderr, "!!! grib_set_string(\tgrib_handle* h, \"%s\", \"%s\") !!!\n", key, val);
  return ret_val;
}
#endif

typedef struct {
  bool init;
  void *gribHandle;
}
gribContainer_t;

// clang-format on

#endif /* GRIBAPI_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CGRIBEX_H
#define CGRIBEX_H

// clang-format off

#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>

#define  GRIB_MISSVAL  -9.E33

// GRIB1 Level Types
#define  GRIB1_LTYPE_SURFACE               1
#define  GRIB1_LTYPE_CLOUD_BASE            2
#define  GRIB1_LTYPE_CLOUD_TOP             3
#define  GRIB1_LTYPE_ISOTHERM0             4
#define  GRIB1_LTYPE_TROPOPAUSE            7
#define  GRIB1_LTYPE_TOA                   8
#define  GRIB1_LTYPE_SEA_BOTTOM            9
#define  GRIB1_LTYPE_ATMOSPHERE           10
#define  GRIB1_LTYPE_99                   99
#define  GRIB1_LTYPE_ISOBARIC            100
#define  GRIB1_LTYPE_ISOBARIC_PA         210
#define  GRIB1_LTYPE_MEANSEA             102
#define  GRIB1_LTYPE_ALTITUDE            103
#define  GRIB1_LTYPE_HEIGHT              105
#define  GRIB1_LTYPE_SIGMA               107
#define  GRIB1_LTYPE_SIGMA_LAYER         108
#define  GRIB1_LTYPE_HYBRID              109
#define  GRIB1_LTYPE_HYBRID_LAYER        110
#define  GRIB1_LTYPE_LANDDEPTH           111
#define  GRIB1_LTYPE_LANDDEPTH_LAYER     112
#define  GRIB1_LTYPE_ISENTROPIC          113
#define  GRIB1_LTYPE_SEADEPTH            160  // Depth Below Sea Level
#define  GRIB1_LTYPE_LAKE_BOTTOM         162  // Lake or River Bottom
#define  GRIB1_LTYPE_SEDIMENT_BOTTOM     163  // Bottom Of Sediment Layer
#define  GRIB1_LTYPE_SEDIMENT_BOTTOM_TA  164  // Bottom Of Thermally Active Sediment Layer
#define  GRIB1_LTYPE_SEDIMENT_BOTTOM_TW  165  // Bottom Of Sediment Layer Penetrated By Thermal Wave
#define  GRIB1_LTYPE_MIX_LAYER           166  // Mixing Layer

// GRIB1 Data representation type (Grid Type) [Table 6]
#define  GRIB1_GTYPE_LATLON                0  //  latitude/longitude
#define  GRIB1_GTYPE_LATLON_ROT           10  //  rotated latitude/longitude
#define  GRIB1_GTYPE_LATLON_STR           20  //  stretched latitude/longitude
#define  GRIB1_GTYPE_LATLON_ROTSTR        30  //  rotated and stretched latitude/longitude
#define  GRIB1_GTYPE_GAUSSIAN              4  //  gaussian grid
#define  GRIB1_GTYPE_GAUSSIAN_ROT         14  //  rotated gaussian grid
#define  GRIB1_GTYPE_GAUSSIAN_STR         24  //  stretched gaussian grid
#define  GRIB1_GTYPE_GAUSSIAN_ROTSTR      34  //  rotated and stretched gaussian grid
#define  GRIB1_GTYPE_LCC                   3  //  Lambert conformal
#define  GRIB1_GTYPE_SPECTRAL             50  //  spherical harmonics
#define  GRIB1_GTYPE_GME                 192  //  hexagonal GME grid

// Macros for the indicator section ( Section 0 )
#define  ISEC0_GRIB_Len             (isec0[ 0])  //  Number of octets in the GRIB message
#define  ISEC0_GRIB_Version         (isec0[ 1])  //  GRIB edition number


// Macros for the product definition section ( Section 1 )
#define  ISEC1_TABLE4_MINUTE      0
#define  ISEC1_TABLE4_HOUR        1
#define  ISEC1_TABLE4_DAY         2
#define  ISEC1_TABLE4_3HOURS     10
#define  ISEC1_TABLE4_6HOURS     11
#define  ISEC1_TABLE4_12HOURS    12
#define  ISEC1_TABLE4_QUARTER    13
#define  ISEC1_TABLE4_30MINUTES  14


#define  ISEC1_CodeTable            (isec1[ 0])  //  Version number of code table
#define  ISEC1_CenterID             (isec1[ 1])  //  Identification of centre
#define  ISEC1_ModelID              (isec1[ 2])  //  Identification of model
#define  ISEC1_GridDefinition       (isec1[ 3])  //  Grid definition
#define  ISEC1_Sec2Or3Flag          (isec1[ 4])  //  Section 2 or 3 included
#define  ISEC1_Parameter            (isec1[ 5])  //  Parameter indicator
#define  ISEC1_LevelType            (isec1[ 6])  //  Type of level indicator
#define  ISEC1_Level1               (isec1[ 7])  //  Level 1
#define  ISEC1_Level2               (isec1[ 8])  //  Level 2
#define  ISEC1_Year                 (isec1[ 9])  //  Year of century (YY)
#define  ISEC1_Month                (isec1[10])  //  Month (MM)
#define  ISEC1_Day                  (isec1[11])  //  Day (DD)
#define  ISEC1_Hour                 (isec1[12])  //  Hour (HH)
#define  ISEC1_Minute               (isec1[13])  //  Minute (MM)
#define  ISEC1_TimeUnit             (isec1[14])  //  Time unit indicator
#define  ISEC1_TimePeriod1          (isec1[15])  //  P1 Time period
#define  ISEC1_TimePeriod2          (isec1[16])  //  P2 Time period
#define  ISEC1_TimeRange            (isec1[17])  //  Time range indicator
#define  ISEC1_AvgNum               (isec1[18])  //  Number of products included in an average
#define  ISEC1_AvgMiss              (isec1[19])  //  Number of products missing from an average
#define  ISEC1_Century              (isec1[20])  //  Century
#define  ISEC1_SubCenterID          (isec1[21])  //  Subcenter identifier
#define  ISEC1_DecScaleFactor       (isec1[22])  //  Decimal scale factor
#define  ISEC1_LocalFLag            (isec1[23])  //  Flag field to indicate local use in isec1

#define  ISEC1_ECMWF_LocalExtension (isec1[36])
#define  ISEC1_ECMWF_Class          (isec1[37])


// Macros for the grid definition section ( Section 2 )
#define  ISEC2_GridType             (isec2[ 0])  // Data representation type

// Triangular grids

#define  ISEC2_GME_NI2              (isec2[ 1])  //  Number of factor 2 in factorisation of Ni
#define  ISEC2_GME_NI3              (isec2[ 2])  //  Number of factor 3 in factorisation of Ni
#define  ISEC2_GME_ND               (isec2[ 3])  //  Nubmer of diamonds
#define  ISEC2_GME_NI               (isec2[ 4])  //  Number of tri. subdiv. of the icosahedron
#define  ISEC2_GME_AFlag            (isec2[ 5])  //  Flag for orientation of diamonds (Table A)
#define  ISEC2_GME_LatPP            (isec2[ 6])  //  Latitude of pole point
#define  ISEC2_GME_LonPP            (isec2[ 7])  //  Longitude of pole point
#define  ISEC2_GME_LonMPL           (isec2[ 8])  //  Longitude of the first diamond
#define  ISEC2_GME_BFlag            (isec2[ 9])  //  Flag for storage sequence (Table B)

// Spherical harmonic coeficients

#define  ISEC2_PentaJ               (isec2[ 1])  //  J pentagonal resolution parameter
#define  ISEC2_PentaK               (isec2[ 2])  //  K pentagonal resolution parameter
#define  ISEC2_PentaM               (isec2[ 3])  //  M pentagonal resolution parameter
#define  ISEC2_RepType              (isec2[ 4])  //  Representation type
#define  ISEC2_RepMode              (isec2[ 5])  //  Representation mode

// Gaussian grids

#define  ISEC2_NumLon               (isec2[ 1])  //  Number of points along a parallel (Ni)
#define  ISEC2_NumLat               (isec2[ 2])  //  Number of points along a meridian (Nj)
#define  ISEC2_FirstLat             (isec2[ 3])  //  Latitude of the first grid point
#define  ISEC2_FirstLon             (isec2[ 4])  //  Longitude of the first grid point
#define  ISEC2_ResFlag              (isec2[ 5])  //  Resolution flag: 128 regular grid
#define  ISEC2_LastLat              (isec2[ 6])  //  Latitude of the last grid point
#define  ISEC2_LastLon              (isec2[ 7])  //  Longitude of the last grid point
#define  ISEC2_LonIncr              (isec2[ 8])  //  i direction increment
#define  ISEC2_LatIncr              (isec2[ 9])  //  j direction increment
#define  ISEC2_NumPar               (isec2[ 9])  //  Number of parallels between a pole and the E.
#define  ISEC2_ScanFlag             (isec2[10])  //  Scanning mode flags
#define  ISEC2_NumVCP               (isec2[11])  //  Number of vertical coordinate parameters

// Lambert

#define  ISEC2_Lambert_Lov          (isec2[ 6])  //  Orientation of the grid
#define  ISEC2_Lambert_dx           (isec2[ 8])  //  X-direction grid length
#define  ISEC2_Lambert_dy           (isec2[ 9])  //  Y-direction grid length
#define  ISEC2_Lambert_ProjFlag     (isec2[12])  //  Projection centre flag
#define  ISEC2_Lambert_LatS1        (isec2[13])  //  First lat at which the secant cone cuts the sphere
#define  ISEC2_Lambert_LatS2        (isec2[14])  //  Second lat at which the secant cone cuts the sphere
#define  ISEC2_Lambert_LatSP        (isec2[19])  //  Latitude of the southern pole
#define  ISEC2_Lambert_LonSP        (isec2[20])  //  Longitude of the southern pole


#define  ISEC2_Reduced              (isec2[16])  // 0: regular, 1: reduced grid

#define  ISEC2_ReducedPointsPtr     (&isec2[22])
#define  ISEC2_ReducedPoints(i)     (isec2[22+i]) // Number of points along each parallel


#define  ISEC2_LatSP                (isec2[12])  // Latitude of the southern pole of rotation
#define  ISEC2_LonSP                (isec2[13])  // Longitude of the southern pole of rotation

#define  FSEC2_RotAngle             (fsec2[ 0])  // Angle of rotation
#define  FSEC2_StrFact              (fsec2[ 1])  // Stretching factor

// Macros for the bit map section ( Section 3 )

#define  ISEC3_PredefBitmap         (isec3[ 0])  // Predefined bitmap
#define  ISEC3_MissVal              (isec3[ 1])  // Missing data value for integers
#define  FSEC3_MissVal              (fsec3[ 1])  // Missing data value for floats

// Macros for the binary data section ( Section 4 )

#define  ISEC4_NumValues            (isec4[ 0])  // Number of data values for encode/decode
#define  ISEC4_NumBits              (isec4[ 1])  // Number of bits used for each encoded value
#define  ISEC4_NumNonMissValues     (isec4[20])  // Number of non-missing values


#ifdef __cplusplus
extern "C" {
#endif


void  gribFixZSE(int flag);     // 1: Fix ZeroShiftError of simple packed spherical harmonics
void  gribSetConst(int flag);   // 1: Don't pack constant fields on regular grids
void  gribSetDebug(int debug);  // 1: Debugging
void  gribSetRound(int round);
void  gribSetRefDP(double refval);
void  gribSetRefSP(float  refval);
void  gribSetValueCheck(int vcheck);


void  gribExSP(int *isec0, int *isec1, int *isec2, float *fsec2, int *isec3,
               float *fsec3, int *isec4, float *fsec4, int klenp, int *kgrib,
               int kleng, int *kword, const char *hoper, int *kret);

void  gribExDP(int *isec0, int *isec1, int *isec2, double *fsec2, int *isec3,
               double *fsec3, int *isec4, double *fsec4, int klenp, int *kgrib,
               int kleng, int *kword, const char *hoper, int *kret);


const char *cgribexLibraryVersion(void);

void  gribDebug(int debug);
void  gribSetCalendar(int calendar);

void  gribDateTimeX(int *isec1, int *date, int *time, int *startDate, int *startTime);
void  gribDateTime(int *isec1, int *date, int *time);
int   gribRefDate(const int *isec1);
int   gribRefTime(const int *isec1);

bool  gribTimeIsFC(const int *isec1);

void  gribPrintSec0(int *isec0);
void  gribPrintSec1(int *isec0, int *isec1);
void  gribPrintSec2DP(int *isec0, int *isec2, double *fsec2);
void  gribPrintSec2SP(int *isec0, int *isec2, float  *fsec2);
void  gribPrintSec3DP(int *isec0, int *isec3, double *fsec3);
void  gribPrintSec3SP(int *isec0, int *isec3, float  *fsec3);
void  gribPrintSec4DP(int *isec0, int *isec4, double *fsec4);
void  gribPrintSec4SP(int *isec0, int *isec4, float  *fsec4);
void  gribPrintSec4Wave(int *isec4);

void  gribPrintALL(int nrec, long offset, long recpos, long recsize, unsigned char *gribbuffer);
void  gribPrintPDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer);
void  gribPrintGDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer);
void  gribPrintBMS(int nrec, long recpos, long recsize, unsigned char *gribbuffer);
void  gribPrintBDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer);
void  gribCheck1(int nrec, long recpos, long recsize, unsigned char *gribbuffer);
void  gribRepair1(int nrec, long recsize, unsigned char *gribbuffer);

int   gribGetZip(size_t recsize, unsigned char *gribbuffer, size_t *urecsize);

int   gribBzip(unsigned char *dbuf, long dbufsize, unsigned char *sbuf, long sbufsize);
int   gribZip(unsigned char *dbuf, long dbufsize, unsigned char *sbuf, long sbufsize);
int   gribUnzip(unsigned char *dbuf, long dbufsize, unsigned char *sbuf, long sbufsize);

int   gribOpen(const char *filename, const char *mode);
void  gribClose(int fileID);

int   gribRead(int fileID, void *buffer, size_t *buffersize);
int   gribWrite(int fileID, void *buffer, size_t buffersize);
off_t gribGetPos(int fileID);
size_t gribGetSize(int fileID);
int   gribCheckSeek(int fileID, long *offset, int *version);
int   gribFileSeek(int fileID, long *offset);
size_t gribReadSize(int fileID);
int   gribVersion(unsigned char *buffer, size_t buffersize);

int   grib_info_for_grads(off_t recpos, long recsize, unsigned char *gribbuffer, int *intnum, float *fltnum, off_t *bignum);

double calculate_pfactor_float(const float* spectralField, long fieldTruncation, long subsetTruncation);
double calculate_pfactor_double(const double* spectralField, long fieldTruncation, long subsetTruncation);

int grib1Sections(unsigned char *gribbuffer, long gribbufsize, unsigned char **pdsp, unsigned char **gdsp, unsigned char **bmsp,
                  unsigned char **bdsp, long *gribrecsize);

#ifdef  __cplusplus
}
#endif

// clang-format on

#endif /* CGRIBEX_H */
#ifdef HAVE_CONFIG_H
#endif

#include <ctype.h>


#ifdef HAVE_LIBCGRIBEX
#endif

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>
pthread_mutex_t CDI_IO_Mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

int CDI_Default_Calendar = CALENDAR_PROLEPTIC;

int CDI_Default_InstID = CDI_UNDEFID;
int CDI_Default_ModelID = CDI_UNDEFID;
int CDI_Default_TableID = CDI_UNDEFID;
// int cdiNcMissingValue  = CDI_UNDEFID;
int CDI_Netcdf_Chunksizehint = CDI_UNDEFID;
int CDI_Split_Ltype105 = CDI_UNDEFID;

bool CDI_Ignore_Att_Coordinates = false;
bool CDI_Coordinates_Lon_Lat = false;
bool CDI_Ignore_Valid_Range = false;
int CDI_Skip_Records = 0;
const char *CDI_GRIB1_Template = NULL;
const char *CDI_GRIB2_Template = NULL;
int CDI_Convention = CDI_CONVENTION_ECHAM;
int CDI_Inventory_Mode = 1;
int CDI_Version_Info = 1;
int CDI_Query_Abort = 1;
int CDI_Convert_Cubesphere = 1;
int CDI_Read_Cell_Center = 1;
int CDI_Read_Cell_Corners = 1;
int CDI_CMOR_Mode = 0;
int CDI_Reduce_Dim = 0;
int CDI_Shuffle = 0;
int CDI_Test = 0;
size_t CDI_Netcdf_Hdr_Pad = 0UL;
bool CDI_Copy_ChunkSpec = false;
bool CDI_Remove_ChunkSpec = false;
bool CDI_Cache_Info = false;
bool CDI_Cache_NC4 = false;
bool CDI_Cache_NCZ = true;
long CDI_CacheSize_In = -1L;
long CDI_CacheSize_Out = -1L;
size_t CDI_CacheSize_Max = 0UL;
bool CDI_Netcdf_Lazy_Grid_Load = false;

char *cdiPartabPath = NULL;
int cdiPartabIntern = 1;

double CDI_Default_Missval = -9.E33;
double CDI_Grid_Missval = -9999.;

// clang-format off
static const char *Filetypes[] = {
  "UNKNOWN",
  "GRIB",
  "GRIB2",
  "NetCDF",
  "NetCDF2",
  "NetCDF4",
  "NetCDF4c",
  "NetCDF5",
  "SERVICE",
  "EXTRA",
  "IEG",
  "NCZarr",
  "HDF5",
};
// clang-format on

int CDI_Debug = 0;  // If set to 1, debugging
int CDI_Recopt = 0;

bool CDI_gribapi_debug = false;
bool CDI_gribapi_grib1 = false;
bool CDI_Lock_IO = false;
bool CDI_Threadsafe = false;
int cdiDefaultLeveltype = -1;
int cdiDataUnreduced = 0;
int cdiSortName = 0;
int cdiHaveMissval = 0;

static long
cdi_getenv_int(const char *envName)
{
  long envValue = -1;

  char *envString = getenv(envName);
  if (envString)
  {
    long fact = 1;
    int len = (int) strlen(envString);
    for (int loop = 0; loop < len; loop++)
    {
      if (!isdigit((int) envString[loop]))
      {
        switch (tolower((int) envString[loop]))
        {
          case 'k': fact = 1024; break;
          case 'm': fact = 1048576; break;
          case 'g': fact = 1073741824; break;
          default:
            fact = 0;
            Warning("Invalid number string in %s: %s", envName, envString);
            Warning("%s must comprise only digits [0-9].", envName);
            break;
        }
        break;
      }
    }

    if (fact) envValue = fact * atol(envString);

    if (CDI_Debug) Message("set %s to %ld", envName, envValue);
  }

  return envValue;
}

static void
cdiPrintDefaults(void)
{
  fprintf(stderr,
          "default instID     :  %d\n"
          "default modelID    :  %d\n"
          "default tableID    :  %d\n"
          "default missval    :  %g\n",
          CDI_Default_InstID, CDI_Default_ModelID, CDI_Default_TableID, CDI_Default_Missval);
}

#ifdef HAVE_LIBFDB5
#include <fdb5/fdb5_config.h>
#endif

void
cdiPrintVersion(void)
{
  fprintf(stdout, "     CDI library version : %s\n", cdiLibraryVersion());
#ifdef HAVE_LIBCGRIBEX
  fprintf(stdout, " cgribex library version : %s\n", cgribexLibraryVersion());
#endif
#ifdef HAVE_LIBGRIB_API
  fprintf(stdout, " ecCodes library version : %s\n", gribapiLibraryVersionString());
#endif
#ifdef HAVE_LIBNETCDF
  fprintf(stdout, "  NetCDF library version : %s\n", cdfLibraryVersion());
#endif
#ifdef HAVE_LIBSERVICE
  fprintf(stdout, "    exse library version : %s\n", srvLibraryVersion());
#endif
  fprintf(stdout, "    FILE library version : %s\n", fileLibraryVersion());
#ifdef HAVE_LIBFDB5
  fprintf(stdout, "    FDB5 library version : %s\n", fdb5_version());
#endif
}

static void
cdiPrintDatatypes(void)
{
#define XSTRING(x) #x
#define STRING(x) XSTRING(x)

  fprintf(stderr,
          "+-------------+-------+\n"
          "| types       | bytes |\n"
          "+-------------+-------+\n"
          "| void *      |   %3d |\n"
          "+-------------+-------+\n"
          "| char        |   %3d |\n"
          "+-------------+-------+\n"
          "| bool        |   %3d |\n"
          "| short       |   %3d |\n"
          "| int         |   %3d |\n"
          "| long        |   %3d |\n"
          "| long long   |   %3d |\n"
          "| size_t      |   %3d |\n"
          "| off_t       |   %3d |\n"
          "+-------------+-------+\n"
          "| float       |   %3d |\n"
          "| double      |   %3d |\n"
          "| long double |   %3d |\n"
          "+-------------+-------+\n\n",
          (int) sizeof(void *), (int) sizeof(char), (int) sizeof(bool), (int) sizeof(short), (int) sizeof(int), (int) sizeof(long),
          (int) sizeof(long long), (int) sizeof(size_t), (int) sizeof(off_t), (int) sizeof(float), (int) sizeof(double),
          (int) sizeof(long double));

  fprintf(stderr,
          "+-------------+-----------+\n"
          "| INT32       | %-9s |\n"
          "| INT64       | %-9s |\n"
          "| FLT32       | %-9s |\n"
          "| FLT64       | %-9s |\n"
          "| SizeType    | %-9s |\n"
          "+-------------+-----------+\n",
          STRING(INT32), STRING(INT64), STRING(FLT32), STRING(FLT64), STRING(CDI_SIZE_TYPE));

  fprintf(stderr, "\n  byte ordering is %s\n\n",
          ((HOST_ENDIANNESS == CDI_BIGENDIAN)
               ? "BIGENDIAN"
               : ((HOST_ENDIANNESS == CDI_LITTLEENDIAN) ? "LITTLEENDIAN" : "Unhandled endianness!")));

#undef STRING
#undef XSTRING
}

void
cdiDebug(int level)
{
  unsigned ulevel = (level == 1) ? (1U << 16) : (unsigned) level;

  if (ulevel & 2) CDI_Debug = 1;
  if (ulevel & 4) memDebug(1);
  if (ulevel & 8) fileDebug(1);

  if (ulevel & 16)
  {
#ifdef HAVE_LIBCGRIBEX
    gribSetDebug(1);
#endif
#ifdef HAVE_LIBNETCDF
    cdfDebug(1);
#endif
#ifdef HAVE_LIBSERVICE
    srvDebug(1);
#endif
#ifdef HAVE_LIBEXTRA
    extDebug(1);
#endif
#ifdef HAVE_LIBIEG
    iegDebug(1);
#endif
  }

  if (CDI_Debug)
  {
    cdiPrintDefaults();
    cdiPrintDatatypes();
  }
}

int
cdiHaveFiletype(int filetype)
{
  switch (filetype)
  {
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: return 1;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: return 1;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: return 1;
#endif
#ifdef HAVE_LIBGRIB
#if defined HAVE_LIBGRIB_API || defined HAVE_LIBCGRIBEX
    case CDI_FILETYPE_GRB: return 1;
#endif
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRB2: return 1;
#endif
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NC: return 1;
#ifdef HAVE_NETCDF2
    case CDI_FILETYPE_NC2: return 1;
#endif
    case CDI_FILETYPE_NC4: return 1;
    case CDI_FILETYPE_NC4C: return 1;
#ifdef HAVE_NCZARR
    case CDI_FILETYPE_NCZARR: return 1;
#endif
#ifdef HAVE_NETCDF5
    case CDI_FILETYPE_NC5: return 1;
#endif
#endif
  }

  return 0;
}

void
cdiDefTableID(int tableID)
{
  CDI_Default_TableID = tableID;
  int modelID = CDI_Default_ModelID = tableInqModel(tableID);
  CDI_Default_InstID = modelInqInstitut(modelID);
}

void
cdiSetEccodesGrib1(bool value)
{
#ifndef HAVE_LIBGRIB_API
  if (value)
  {
    Warning("ecCodes support not compiled in, used CGRIBEX to decode/encode GRIB1 records!");
    value = false;
  }
#endif
  CDI_gribapi_grib1 = value;
}

void
cdiInitialize(void)
{
  static bool Init_CDI = false;

  if (!Init_CDI)
  {
    Init_CDI = true;

#ifdef HAVE_LIBCGRIBEX
    gribFixZSE(1);    // 1: Fix ZeroShiftError of simple packed spherical harmonics
    gribSetConst(1);  // 1: Don't pack constant fields on regular grids
#endif
#ifdef HAVE_LIBGRIB_API
    grib_multi_support_off(NULL);
#endif

    {
      long value = cdi_getenv_int("CDI_DEBUG");
      if (value >= 0) { CDI_Debug = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_GRIBAPI_DEBUG");
      if (value >= 0) { CDI_gribapi_debug = (bool) value; }
    }

    {
      long value = cdi_getenv_int("CDI_ECCODES_DEBUG");
      if (value >= 0) { CDI_gribapi_debug = (bool) value; }
    }

    {
      long value = cdi_getenv_int("CDI_ECCODES_GRIB1");
      if (value >= 0) { cdiSetEccodesGrib1((bool) value); }
    }

    {
      long value = cdi_getenv_int("CDI_LOCK_IO");
      if (value >= 0) { CDI_Lock_IO = (bool) value; }
    }

    {
      long value = cdi_getenv_int("CDI_READ_CELL_CENTER");
      if (value >= 0) { CDI_Read_Cell_Center = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_READ_CELL_CORNERS");
      if (value >= 0) { CDI_Read_Cell_Corners = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_RECOPT");
      if (value >= 0) { CDI_Recopt = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_REGULARGRID");
      if (value >= 0) { cdiDataUnreduced = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_SORTNAME");
      if (value >= 0) { cdiSortName = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_HAVE_MISSVAL");
      if (value >= 0) { cdiHaveMissval = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_LEVELTYPE");
      if (value >= 0) { cdiDefaultLeveltype = (int) value; }
    }

    {
      long value = cdi_getenv_int("CDI_NETCDF_HDR_PAD");
      if (value >= 0) { CDI_Netcdf_Hdr_Pad = (size_t) value; }
    }

    {
      long value = cdi_getenv_int("CDI_COPY_CHUNKSPEC");
      if (value >= 0) { CDI_Copy_ChunkSpec = (value > 0); }
    }

    {
      long value = cdi_getenv_int("CDI_REMOVE_CHUNKSPEC");
      if (value >= 0) { CDI_Remove_ChunkSpec = (value > 0); }
    }

    {
      long value = cdi_getenv_int("CDI_CACHE_INFO");
      if (value >= 0) { CDI_Cache_Info = (value > 0); }
    }

    {
      long value = cdi_getenv_int("CDI_CACHE_NC");
      if (value >= 0)
      {
        CDI_Cache_NC4 = (value > 0);
        CDI_Cache_NCZ = (value > 0);
      }
    }

    {
      long value = cdi_getenv_int("CDI_CACHE_NC4");
      if (value >= 0) { CDI_Cache_NC4 = (value > 0); }
    }

    {
      long value = cdi_getenv_int("CDI_CACHE_NCZ");
      if (value >= 0) { CDI_Cache_NCZ = (value > 0); }
    }

    {
      long value = cdi_getenv_int("CDI_CACHESIZE_IN");
      if (value >= 0) { CDI_CacheSize_In = value; }
    }

    {
      long value = cdi_getenv_int("CDI_CACHESIZE_OUT");
      if (value >= 0) { CDI_CacheSize_Out = value; }
    }

    {
      long value = cdi_getenv_int("CDI_CACHESIZE_MAX");
      if (value >= 0) { CDI_CacheSize_Max = (size_t) value; }
    }

    {
      long value = cdi_getenv_int("CDI_TEST");
      if (value >= 0) { CDI_Test = (int) value; }
    }

    char *envstr;
    envstr = getenv("CDI_GRIB1_TEMPLATE");
    if (envstr) CDI_GRIB1_Template = envstr;

    envstr = getenv("CDI_GRIB2_TEMPLATE");
    if (envstr) CDI_GRIB2_Template = envstr;

    envstr = getenv("CDI_SHUFFLE");
    if (envstr) CDI_Shuffle = atoi(envstr);

    envstr = getenv("CDI_MISSVAL");
    if (envstr) CDI_Default_Missval = atof(envstr);
    /*
    envstr = getenv("NC_MISSING_VALUE");
    if ( envstr ) cdiNcMissingValue = atoi(envstr);
    */
    envstr = getenv("NC_CHUNKSIZEHINT");
    if (envstr) CDI_Netcdf_Chunksizehint = atoi(envstr);

    envstr = getenv("SPLIT_LTYPE_105");
    if (envstr) CDI_Split_Ltype105 = atoi(envstr);

    envstr = getenv("IGNORE_ATT_COORDINATES");
    if (envstr) CDI_Ignore_Att_Coordinates = atoi(envstr) > 0;

    envstr = getenv("CDI_COORDINATES_LONLAT");
    if (envstr) CDI_Coordinates_Lon_Lat = atoi(envstr) > 0;

    envstr = getenv("IGNORE_VALID_RANGE");
    if (envstr) CDI_Ignore_Valid_Range = atoi(envstr) > 0;

    envstr = getenv("CDI_SKIP_RECORDS");
    if (envstr)
    {
      CDI_Skip_Records = atoi(envstr);
      CDI_Skip_Records = CDI_Skip_Records > 0 ? CDI_Skip_Records : 0;
    }

    envstr = getenv("CDI_CONVENTION");
    if (envstr)
    {
      if (str_is_equal(envstr, "CF") || str_is_equal(envstr, "cf"))
      {
        CDI_Convention = CDI_CONVENTION_CF;
        if (CDI_Debug) Message("CDI convention was set to CF!");
      }
    }

    envstr = getenv("CDI_INVENTORY_MODE");
    if (envstr)
    {
      if (strncmp(envstr, "time", 4) == 0)
      {
        CDI_Inventory_Mode = 2;
        if (CDI_Debug) Message("Inventory mode was set to timestep!");
      }
    }

    envstr = getenv("CDI_QUERY_ABORT");
    if (envstr)
    {
      int ival = atoi(envstr);
      if (ival == 0 || ival == 1)
      {
        CDI_Query_Abort = ival;
        if (CDI_Debug) Message("CDI_Query_Abort = %s", envstr);
      }
    }

    envstr = getenv("CDI_VERSION_INFO");
    if (envstr)
    {
      int ival = atoi(envstr);
      if (ival == 0 || ival == 1)
      {
        CDI_Version_Info = ival;
        if (CDI_Debug) Message("CDI_Version_Info = %s", envstr);
      }
    }

    envstr = getenv("CDI_CONVERT_CUBESPHERE");
    if (envstr)
    {
      int ival = atoi(envstr);
      if (ival == 0 || ival == 1)
      {
        CDI_Convert_Cubesphere = ival;
        if (CDI_Debug) Message("CDI_Convert_Cubesphere = %s", envstr);
      }
    }

    envstr = getenv("CDI_CALENDAR");
    if (envstr)
    {
      // clang-format off
	    if      (strncmp(envstr, "standard", 8)  == 0) CDI_Default_Calendar = CALENDAR_STANDARD;
	    else if (strncmp(envstr, "gregorian", 9) == 0) CDI_Default_Calendar = CALENDAR_GREGORIAN;
	    else if (strncmp(envstr, "proleptic", 9) == 0) CDI_Default_Calendar = CALENDAR_PROLEPTIC;
	    else if (strncmp(envstr, "360days", 7)   == 0) CDI_Default_Calendar = CALENDAR_360DAYS;
	    else if (strncmp(envstr, "365days", 7)   == 0) CDI_Default_Calendar = CALENDAR_365DAYS;
	    else if (strncmp(envstr, "366days", 7)   == 0) CDI_Default_Calendar = CALENDAR_366DAYS;
	    else if (strncmp(envstr, "none", 4)      == 0) CDI_Default_Calendar = CALENDAR_NONE;
      // clang-format on
      if (CDI_Debug) Message("Default calendar set to %s!", envstr);
    }
#ifdef HAVE_LIBCGRIBEX
    gribSetCalendar(CDI_Default_Calendar);
#endif

    envstr = getenv("PARTAB_INTERN");
    if (envstr) cdiPartabIntern = atoi(envstr);

    envstr = getenv("PARTAB_PATH");
    if (envstr) cdiPartabPath = strdup(envstr);
  }
}

const char *
strfiletype(int filetype)
{
  enum
  {
    numFiletypes = (int) (sizeof(Filetypes) / sizeof(Filetypes[0]))
  };
  return (filetype > 0 && filetype < numFiletypes) ? Filetypes[filetype] : Filetypes[0];
}

void
cdiDefGlobal(const char *string, int value)
{
  // clang-format off
  if      (str_is_equal(string, "REGULARGRID"))           cdiDataUnreduced = value;
  else if (str_is_equal(string, "LOCKIO"))                CDI_Lock_IO = (bool) value;
  else if (str_is_equal(string, "THREADSAFE"))            CDI_Threadsafe = (bool) value;
  else if (str_is_equal(string, "ECCODES_DEBUG"))         CDI_gribapi_debug = (bool) value;
  else if (str_is_equal(string, "ECCODES_GRIB1"))         cdiSetEccodesGrib1((bool) value);
  else if (str_is_equal(string, "SORTNAME"))              cdiSortName = value;
  else if (str_is_equal(string, "HAVE_MISSVAL"))          cdiHaveMissval = value;
  else if (str_is_equal(string, "NC_CHUNKSIZEHINT"))      CDI_Netcdf_Chunksizehint = value;
  else if (str_is_equal(string, "READ_CELL_CENTER"))      CDI_Read_Cell_Center = value;
  else if (str_is_equal(string, "READ_CELL_CORNERS"))     CDI_Read_Cell_Corners = value;
  else if (str_is_equal(string, "CMOR_MODE"))             CDI_CMOR_Mode = value;
  else if (str_is_equal(string, "REDUCE_DIM"))            CDI_Reduce_Dim = value;
  else if (str_is_equal(string, "NETCDF_HDR_PAD"))        CDI_Netcdf_Hdr_Pad = (size_t) value;
  else if (str_is_equal(string, "NETCDF_LAZY_GRID_LOAD")) CDI_Netcdf_Lazy_Grid_Load = (bool) value;
  else if (str_is_equal(string, "COPY_CHUNKSPEC"))        CDI_Copy_ChunkSpec = (bool) value;
  else if (str_is_equal(string, "REMOVE_CHUNKSPEC"))      CDI_Remove_ChunkSpec = (bool) value;
  else Warning("Unsupported global key: %s", string);
  // clang-format on
}

void
cdiDefMissval(double missval)
{
  cdiInitialize();

  CDI_Default_Missval = missval;
}

double
cdiInqMissval(void)
{
  cdiInitialize();

  return CDI_Default_Missval;
}

bool
cdiFiletypeIsExse(int filetype)
{
  return (filetype == CDI_FILETYPE_SRV || filetype == CDI_FILETYPE_EXT || filetype == CDI_FILETYPE_IEG);
}

int
cdiBaseFiletype(int filetype)
{
  switch (filetype)
  {
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2: return CDI_FILETYPE_GRIB;
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    case CDI_FILETYPE_NCZARR:
    case CDI_FILETYPE_NC5: return CDI_FILETYPE_NETCDF;
  }

  return filetype;
}

void
stream_def_accesstype(stream_t *s, int type)
{
  if (s->accesstype == CDI_UNDEFID) { s->accesstype = type; }
  else if (s->accesstype != type)
    Error("Changing access type from %s not allowed!", s->accesstype == TYPE_REC ? "REC to VAR" : "VAR to REC");
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <string.h>
#include <stdlib.h>

struct CdiQuery
{
  int numEntries;
  // Names
  int numNames;
  bool *namesFound;
  const char **names;
  // Grid cell indices
  int numCells;
  bool cellsFound[2];
  size_t cells[2];
  // Layer indices
  int numLayers;
  bool layersFound[2];
  int layers[2];
  // Time step indices
  int numSteps;
  bool stepsFound[2];
  int steps[2];
};

static int
sum_found(int listSize, const bool *listFound)
{
  int numFound = 0;
  for (int i = 0; i < listSize; ++i) numFound += listFound[i];
  return numFound;
}

static int
sum_not_found(int listSize, const bool *listFound)
{
  return listSize - sum_found(listSize, listFound);
}

void
cdiQueryInit(struct CdiQuery *query)
{
  query->numEntries = 0;

  query->numNames = 0;
  query->names = NULL;
  query->namesFound = NULL;

  query->numCells = 0;
  query->cells[0] = 0;
  query->cellsFound[0] = false;

  query->numLayers = 0;
  query->layers[0] = 0;
  query->layersFound[0] = false;

  query->numSteps = 0;
  query->steps[0] = 0;
  query->stepsFound[0] = false;
}

struct CdiQuery *
cdiQueryCreate(void)
{
  struct CdiQuery *query = (struct CdiQuery *) Malloc(sizeof(struct CdiQuery));
  cdiQueryInit(query);
  return query;
}

void
cdiQueryDelete(struct CdiQuery *query)
{
  if (query)
  {
    if (query->numNames)
    {
      for (int i = 0; i < query->numNames; ++i) Free((void *) query->names[i]);
      Free(query->names);
      Free(query->namesFound);
    }

    cdiQueryInit(query);
    Free(query);
  }
}

int
cdiQueryNumNames(const struct CdiQuery *query)
{
  return query ? query->numNames : 0;
}

int
cdiQueryNumCells(const struct CdiQuery *query)
{
  return query ? query->numCells : 0;
}

int
cdiQueryNumLayers(const struct CdiQuery *query)
{
  return query ? query->numLayers : 0;
}

int
cdiQueryNumSteps(const struct CdiQuery *query)
{
  return query ? query->numSteps : 0;
}

int
cdiQueryNumEntries(const struct CdiQuery *query)
{
  return query ? query->numEntries : 0;
}

void
cdiQuerySetNames(struct CdiQuery *query, int numNames, const char **names)
{
  if (numNames > 0)
  {
    query->numEntries += numNames;
    query->numNames = numNames;
    query->namesFound = (bool *) Calloc((size_t) numNames, sizeof(bool));
    query->names = (const char **) Malloc((size_t) numNames * sizeof(char *));
    for (int i = 0; i < numNames; ++i) query->names[i] = strdup(names[i]);
  }
}

void
cdiQuerySetCells(struct CdiQuery *query, int numCells, const size_t *cells)
{
  if (numCells > 0 && numCells <= 2)
  {
    query->numEntries += numCells;
    query->numCells = numCells;
    for (int i = 0; i < numCells; ++i) query->cells[i] = cells[i];
  }
}

void
cdiQuerySetLayers(struct CdiQuery *query, int numLayers, const int *layers)
{
  if (numLayers > 0 && numLayers <= 2)
  {
    query->numEntries += numLayers;
    query->numLayers = numLayers;
    for (int i = 0; i < numLayers; ++i) query->layers[i] = layers[i];
  }
}

void
cdiQuerySetSteps(struct CdiQuery *query, int numSteps, const int *steps)
{
  if (numSteps > 0 && numSteps <= 2)
  {
    query->numEntries += numSteps;
    query->numSteps = numSteps;
    for (int i = 0; i < numSteps; ++i) query->steps[i] = steps[i];
  }
}

size_t
cdiQueryGetCell(const struct CdiQuery *query, int index)
{
  return (index >= 0 && index < query->numCells) ? query->cells[index] : (size_t) -1;
}

int
cdiQueryGetLayer(const struct CdiQuery *query, int index)
{
  return (index >= 0 && index < query->numLayers) ? query->layers[index] : -1;
}

int
cdiQueryGetStep(const struct CdiQuery *query, int index)
{
  return (index >= 0 && index < query->numSteps) ? query->steps[index] : -1;
}

struct CdiQuery *
cdiQueryClone(const struct CdiQuery *query)
{
  struct CdiQuery *queryOut = cdiQueryCreate();

  if (query)
  {
    cdiQuerySetNames(queryOut, query->numNames, query->names);
    cdiQuerySetCells(queryOut, query->numCells, query->cells);
    cdiQuerySetLayers(queryOut, query->numLayers, query->layers);
    cdiQuerySetSteps(queryOut, query->numSteps, query->steps);
  }

  return queryOut;
}

static void
print_list_compact_int(int n, const int *list)
{
  for (int i = 0; i < n; ++i)
  {
    int value = list[i];
    printf(" %d", value);
    if ((i + 2) < n && (value + 1) == list[i + 1] && (value + 2) == list[i + 2])
    {
      printf("/to/");
      int last = list[++i];
      while ((i + 1) < n && (last + 1) == list[i + 1]) last = list[++i];
      printf("%d", last);
    }
  }
  printf("\n");
}

void
cdiQueryPrint(const struct CdiQuery *query)
{
  if (query)
  {
    if (query->numNames)
    {
      printf("Names:");
      for (int i = 0; i < query->numNames; ++i) printf(" %s", query->names[i]);
      printf("\n");
    }

    if (query->numCells)
    {
      printf("Cells:");
      for (int i = 0; i < query->numCells; ++i) printf(" %zu", query->cells[i]);
      printf("\n");
    }

    if (query->numLayers)
    {
      printf("Layers:");
      print_list_compact_int(query->numLayers, query->layers);
    }

    if (query->numSteps)
    {
      printf("Steps:");
      print_list_compact_int(query->numSteps, query->steps);
    }
  }
}

int
cdiQueryNumEntriesFound(const struct CdiQuery *query)
{
  int numEntriesFound = 0;

  if (query)
  {
    if (query->numNames) numEntriesFound += sum_found(query->numNames, query->namesFound);
    if (query->numCells) numEntriesFound += sum_found(query->numCells, query->cellsFound);
    if (query->numLayers) numEntriesFound += sum_found(query->numLayers, query->layersFound);
    if (query->numSteps) numEntriesFound += sum_found(query->numSteps, query->stepsFound);
  }

  return numEntriesFound;
}

void
cdiQueryPrintEntriesNotFound(const struct CdiQuery *query)
{
  if (query)
  {
    int numEntriesNotFound = cdiQueryNumEntries(query) - cdiQueryNumEntriesFound(query);
    if (numEntriesNotFound > 0)
    {
      if (query->numNames)
      {
        if (sum_not_found(query->numNames, query->namesFound) > 0)
        {
          printf("Name not found:");
          for (int i = 0; i < query->numNames; ++i)
            if (!query->namesFound[i]) printf(" %s", query->names[i]);
          printf("\n");
        }
      }

      if (query->numCells)
      {
        if (sum_not_found(query->numCells, query->cellsFound) > 0)
        {
          printf("Grid cell index not found:");
          for (int i = 0; i < query->numCells; ++i)
            if (!query->cellsFound[i]) printf(" %zu", query->cells[i]);
          printf("\n");
        }
      }

      if (query->numLayers)
      {
        if (sum_not_found(query->numLayers, query->layersFound) > 0)
        {
          printf("Layer not found:");
          for (int i = 0; i < query->numLayers; ++i)
            if (!query->layersFound[i]) printf(" %d", query->layers[i]);
          printf("\n");
        }
      }

      if (query->numSteps)
      {
        if (sum_not_found(query->numSteps, query->stepsFound) > 0)
        {
          printf("Step not found:");
          for (int i = 0; i < query->numSteps; ++i)
            if (!query->stepsFound[i]) printf(" %d", query->steps[i]);
          printf("\n");
        }
      }
    }
  }
}

int
cdiQueryName(struct CdiQuery *query, const char *name)
{
  if (query && query->numNames && name && *name)
  {
    for (int i = 0; i < query->numNames; ++i)
      if (strcmp(name, query->names[i]) == 0)
      {
        query->namesFound[i] = true;
        return 0;
      }
  }

  return -1;
}
/*
int
cdiQueryCell(struct CdiQuery *query, size_t cell)
{
  if (query && query->numCells)
  {
    for (int i = 0; i < query->numCells; ++i)
      if (query->cells[i] == cell)
      {
        query->cellsFound[i] = true;
        return 0;
      }
  }

  return -1;
}

int
cdiQueryLayer(struct CdiQuery *query, int layer)
{
  if (query && query->numLayers)
  {
    for (int i = 0; i < query->numLayers; ++i)
      if (query->layers[i] == layer)
      {
        query->layersFound[i] = true;
        return 0;
      }
  }

  return -1;
}

int
cdiQueryStep(struct CdiQuery *query, int step)
{
  if (query && query->numSteps)
  {
    for (int i = 0; i < query->numSteps; ++i)
      if (query->steps[i] == step)
      {
        query->stepsFound[i] = true;
        return 0;
      }
  }

  return -1;
}
*/
#ifdef HAVE_CONFIG_H
#endif

#ifndef _WIN32
#include <unistd.h>
#endif
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>

void
cdiDecodeParam(int param, int *pnum, int *pcat, int *pdis)
{
  unsigned uparam = (unsigned) param;

  *pdis = (int) (0xffU & uparam);
  *pcat = (int) (0xffU & (uparam >> 8));
  unsigned upnum = 0xffffU & (uparam >> 16);
  if (upnum > 0x7fffU) upnum = 0x8000U - upnum;
  *pnum = (int) upnum;
}

int
cdiEncodeParam(int pnum, int pcat, int pdis)
{
  if (pcat < 0 || pcat > 255) pcat = 255;
  if (pdis < 0 || pdis > 255) pdis = 255;

  unsigned upnum = (unsigned) pnum;
  if (pnum < 0) upnum = (unsigned) (0x8000 - pnum);

  unsigned uparam = (upnum << 16) | (((unsigned) pcat) << 8) | (unsigned) pdis;

  return (int) uparam;
}

void
cdiParamToString(int param, char *paramstr, int maxlen)
{
  int dis, cat, num;
  cdiDecodeParam(param, &num, &cat, &dis);

  size_t umaxlen = maxlen >= 0 ? (unsigned) maxlen : 0U;
  int len;
  if (dis == 255 && (cat == 255 || cat == 0))
    len = snprintf(paramstr, umaxlen, "%d", num);
  else if (dis == 255)
    len = snprintf(paramstr, umaxlen, "%d.%d", num, cat);
  else
    len = snprintf(paramstr, umaxlen, "%d.%d.%d", num, cat, dis);

  if (len >= maxlen || len < 0) fprintf(stderr, "Internal problem (%s): size of input string is too small!\n", __func__);
}

const char *
cdiUnitNamePtr(int cdi_unit)
{
  const char *cdiUnits[] = {
    /*  0 */ "undefined",
    /*  1 */ "Pa",
    /*  2 */ "hPa",
    /*  3 */ "mm",
    /*  4 */ "cm",
    /*  5 */ "dm",
    /*  6 */ "m",
  };
  enum
  {
    numUnits = (int) (sizeof(cdiUnits) / sizeof(cdiUnits[0]))
  };

  return (cdi_unit > 0 && cdi_unit < numUnits) ? cdiUnits[cdi_unit] : NULL;
}

size_t
cdiGetPageSize(bool largePageAlign)
{
  long pagesize = -1L;
#if HAVE_DECL__SC_LARGE_PAGESIZE || HAVE_DECL__SC_PAGE_SIZE || HAVE_DECL__SC_PAGESIZE
  bool nameAssigned = false;
  int name = 0;
#if HAVE_DECL__SC_LARGE_PAGESIZE
  if (largePageAlign)
  {
    name = _SC_LARGE_PAGESIZE;
    nameAssigned = true;
  }
  else
#else
  (void) largePageAlign;
#endif
  {
#if HAVE_DECL__SC_PAGESIZE || HAVE_DECL__SC_PAGE_SIZE
    name =
#if HAVE_DECL__SC_PAGESIZE
        _SC_PAGESIZE;
#else
        _SC_PAGE_SIZE;
#endif
    nameAssigned = true;
#endif
  }
  if (nameAssigned) pagesize = sysconf(name);
#endif
  if (pagesize == -1L)
    pagesize =
#if HAVE_DECL_PAGESIZE
        PAGESIZE;
#elif HAVE_DECL_PAGE_SIZE
        PAGE_SIZE;
#else
        commonPageSize;
#endif
  return (size_t) pagesize;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */

// Automatically generated by m214003 at 2024-10-30, do not edit

// CGRIBEXLIB_VERSION="2.3.1"

#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic warning "-Wstrict-overflow"
#endif

#ifdef _ARCH_PWR6
#pragma options nostrict
#include <ppu_intrinsics.h>
#endif

#ifdef HAVE_CONFIG_H
#endif

#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <sys/types.h>
#include <limits.h>
#include <inttypes.h>


#ifndef CGRIBEX_TEMPLATES_H
#define CGRIBEX_TEMPLATES_H

// clang-format off
#define CAT(X,Y)      X##_##Y
#define TEMPLATE(X,Y) CAT(X,Y)
// clang-format on

#endif
#ifndef GRIB_INT_H
#define GRIB_INT_H

#ifdef HAVE_CONFIG_H
#endif

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <float.h>

// clang-format off

#ifndef  CGRIBEX_H
#endif
#ifndef  ERROR_H
#endif

#ifndef UCHAR
#define  UCHAR  unsigned char
#endif


#if defined (CRAY) || defined (SX) || defined (__uxpch__)
#define VECTORCODE 1
#endif


#ifdef VECTORCODE
#  define  GRIBPACK     uint32_t
#  define  PACK_GRIB    packInt32
#  define  UNPACK_GRIB  unpackInt32
#else
#  define  GRIBPACK     unsigned char
#endif

#ifndef HOST_ENDIANNESS
#ifdef __cplusplus
static const uint32_t HOST_ENDIANNESS_temp[1] = { UINT32_C(0x00030201) };
#define HOST_ENDIANNESS (((const unsigned char *)HOST_ENDIANNESS_temp)[0])
#else
#define HOST_ENDIANNESS (((const unsigned char *)&(const uint32_t[1]){UINT32_C(0x00030201)})[0])
#endif
#endif

#define  IS_BIGENDIAN()  (HOST_ENDIANNESS == 0)

#if defined (__xlC__) /* performance problems on IBM */
#ifndef DBL_IS_NAN
#  define DBL_IS_NAN(x)     ((x) != (x))
#endif
#else
#ifndef DBL_IS_NAN
#if  defined  (HAVE_DECL_ISNAN)
#  define DBL_IS_NAN(x)     (isnan(x))
#elif  defined  (FP_NAN)
#  define DBL_IS_NAN(x)     (fpclassify(x) == FP_NAN)
#else
#  define DBL_IS_NAN(x)     ((x) != (x))
#endif
#endif
#endif

#ifndef IS_EQUAL
#  define IS_NOT_EQUAL(x,y) (x < y || y < x)
#  define IS_EQUAL(x,y)     (!IS_NOT_EQUAL(x,y))
#endif

/* dummy use of unused parameters to silence compiler warnings */
#ifndef UNUSED
#define  UNUSED(x) (void)(x)
#endif

#define  JP24SET    0xFFFFFF  /* 2**24     (---> 16777215) */
#define  JP23SET    0x7FFFFF  /* 2**23 - 1 (--->  8388607) */

#define  POW_2_M24  0.000000059604644775390625  /* pow(2.0, -24.0) */

#ifdef __cplusplus
extern "C" {
#endif

#define intpow2(x) (ldexp(1.0, (x)))

static inline int
gribrec_len(unsigned b1, unsigned b2, unsigned b3)
{
  /*
    If bit 7 of b1 is set, we have to rescale by factor of 120.
    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.
  */
  const int needRescaling = b1 & (1 << 7);

  int gribsize = (int)((((b1&127) << 16)+(b2<<8) + b3));

  if ( needRescaling ) gribsize *= 120;

  return gribsize;

}

unsigned correct_bdslen(unsigned bdslen, long recsize, long gribpos);

/* CDI converter routines */

/* param format:  DDDCCCNNN */

void    cdiDecodeParam(int param, int *pnum, int *pcat, int *pdis);
int     cdiEncodeParam(int pnum, int pcat, int pdis);

/* date format:  YYYYMMDD */
/* time format:  hhmmss   */

void    cdiDecodeDate(int date, int *year, int *month, int *day);
int     cdiEncodeDate(int year, int month, int day);

void    cdiDecodeTime(int time, int *hour, int *minute, int *second);
int     cdiEncodeTime(int hour, int minute, int second);

/* CALENDAR types */

#define  CALENDAR_STANDARD        0  /* don't change this value (used also in cgribexlib)! */
#define  CALENDAR_GREGORIAN       1
#define  CALENDAR_PROLEPTIC       2
#define  CALENDAR_360DAYS         3
#define  CALENDAR_365DAYS         4
#define  CALENDAR_366DAYS         5
#define  CALENDAR_NONE            6

extern FILE *grprsm;

extern int  CGRIBEX_Debug, CGRIBEX_Fix_ZSE, CGRIBEX_Const;
extern int  CGRIBEX_grib_calendar;

void   gprintf(const char *caller, const char *fmt, ...);

void   grsdef(void);

void   prtbin(int kin, int knbit, int *kout, int *kerr);
void   confp3(double pval, int *kexp, int *kmant, int kbits, int kround);
double decfp2(int kexp, int kmant);
void   ref2ibm(double *pref, int kbits);

void   scale_complex_double(double *fpdata, int pcStart, int pcScale, int trunc, int inv);
void   scale_complex_float(float *fpdata, int pcStart, int pcScale, int trunc, int inv);
void   scatter_complex_double(double *fpdata, int pcStart, int trunc, int nsp);
void   scatter_complex_float(float *fpdata, int pcStart, int trunc, int nsp);
void   gather_complex_double(double *fpdata, size_t pcStart, size_t trunc, size_t nsp);
void   gather_complex_float(float *fpdata, size_t pcStart, size_t trunc, size_t nsp);

int    qu2reg2(double *pfield, int *kpoint, int klat, int klon,
	       double *ztemp, double msval, int *kret);
int    qu2reg3_double(double *pfield, int *kpoint, int klat, int klon,
		      double msval, int *kret, int omisng, int operio, int oveggy);
int    qu2reg3_float(float *pfield, int *kpoint, int klat, int klon,
		     float msval, int *kret, int omisng, int operio, int oveggy);

long   packInt32(uint32_t *up, unsigned char *cp, long bc, long tc);
long   packInt64(uint64_t *up, unsigned char *cp, long bc, long tc);
long   unpackInt32(const unsigned char *cp, uint32_t *up, long bc, long tc);
long   unpackInt64(const unsigned char *cp, uint64_t *up, long bc, long tc);

void  grib_encode_double(int *isec0, int *isec1, int *isec2, double *fsec2, int *isec3,
			 double *fsec3, int *isec4, double *fsec4, int klenp, int *kgrib,
			 int kleng, int *kword, int efunc, int *kret);
void  grib_encode_float(int *isec0, int *isec1, int *isec2, float *fsec2, int *isec3,
			float *fsec3, int *isec4, float *fsec4, int klenp, int *kgrib,
			int kleng, int *kword, int efunc, int *kret);

void  grib_decode_double(int *isec0, int *isec1, int *isec2, double *fsec2, int *isec3,
			 double *fsec3, int *isec4, double *fsec4, int klenp, int *kgrib,
			 int kleng, int *kword, int dfunc, int *kret);
void  grib_decode_float(int *isec0, int *isec1, int *isec2, float *fsec2, int *isec3,
			float *fsec3, int *isec4, float *fsec4, int klenp, int *kgrib,
			int kleng, int *kword, int dfunc, int *kret);


int grib1Sections(unsigned char *gribbuffer, long gribbufsize, unsigned char **pdsp,
		  unsigned char **gdsp, unsigned char **bmsp, unsigned char **bdsp, long *gribrecsize);
int grib2Sections(unsigned char *gribbuffer, long gribbufsize, unsigned char **idsp,
		  unsigned char **lusp, unsigned char **gdsp, unsigned char **pdsp,
		  unsigned char **drsp, unsigned char **bmsp, unsigned char **bdsp);

#ifdef  __cplusplus
}
#endif

// clang-format on

#endif /* GRIB_INT_H */
#ifndef GRIBDECODE_H
#define GRIBDECODE_H

// clang-format off

#define  UNDEFINED          9.999e20


#define  GET_INT3(a,b,c)    ((1-(int) ((unsigned) (a & 128) >> 6)) * (int) (((a & 127) << 16)+(b<<8)+c))
#define  GET_INT2(a,b)      ((1-(int) ((unsigned) (a & 128) >> 6)) * (int) (((a & 127) << 8) + b))
#define  GET_INT1(a)        ((1-(int) ((unsigned) (a & 128) >> 6)) * (int) (a&127))

/* this requires a 32-bit default integer machine */
#define  GET_UINT4(a,b,c,d) ((unsigned) ((a << 24) + (b << 16) + (c << 8) + (d)))
#define  GET_UINT3(a,b,c)   ((unsigned) ((a << 16) + (b << 8)  + (c)))
#define  GET_UINT2(a,b)     ((unsigned) ((a << 8)  + (b)))
#define  GET_UINT1(a)       ((unsigned)  (a))

#define  BUDG_START(s)      (s[0]=='B' && s[1]=='U' && s[2]=='D' && s[3]=='G')
#define  TIDE_START(s)      (s[0]=='T' && s[1]=='I' && s[2]=='D' && s[3]=='E')
#define  GRIB_START(s)      (s[0]=='G' && s[1]=='R' && s[2]=='I' && s[3]=='B')
#define  GRIB_FIN(s)        (s[0]=='7' && s[1]=='7' && s[2]=='7' && s[3]=='7')

/* GRIB1 Section 0: Indicator Section (IS) */

#define  GRIB1_SECLEN(s)     GET_UINT3(s[ 4], s[ 5], s[ 6])
#define  GRIB_EDITION(s)     GET_UINT1(s[ 7])

/* GRIB1 Section 1: Product Definition Section (PDS) */

#define  PDS_Len             GET_UINT3(pds[ 0], pds[ 1], pds[ 2])
#define  PDS_CodeTable       GET_UINT1(pds[ 3])
#define  PDS_CenterID        GET_UINT1(pds[ 4])
#define  PDS_ModelID         GET_UINT1(pds[ 5])
#define  PDS_GridDefinition  GET_UINT1(pds[ 6])
#define  PDS_Sec2Or3Flag     GET_UINT1(pds[ 7])
#define  PDS_HAS_GDS         ((pds[7] & 128) != 0)
#define  PDS_HAS_BMS         ((pds[7] &  64) != 0)
#define  PDS_Parameter       GET_UINT1(pds[ 8])
#define  PDS_LevelType       GET_UINT1(pds[ 9])
#define  PDS_Level1          (pds[10])
#define  PDS_Level2	     (pds[11])
#define  PDS_Level	     GET_UINT2(pds[10], pds[11])
#define  PDS_Year            GET_INT1(pds[12])
#define  PDS_Month           GET_UINT1(pds[13])
#define  PDS_Day             GET_UINT1(pds[14])
#define  PDS_Hour            GET_UINT1(pds[15])
#define  PDS_Minute          GET_UINT1(pds[16])
#define  PDS_Date            (PDS_Year*10000+PDS_Month*100+PDS_Day)
#define  PDS_Time            (PDS_Hour*100+PDS_Minute)
#define  PDS_TimeUnit        GET_UINT1(pds[17])
#define  PDS_TimePeriod1     GET_UINT1(pds[18])
#define  PDS_TimePeriod2     GET_UINT1(pds[19])
#define  PDS_TimeRange       GET_UINT1(pds[20])
#define  PDS_AvgNum          GET_UINT2(pds[21], pds[22])
#define  PDS_AvgMiss         GET_UINT1(pds[23])
#define  PDS_Century         GET_UINT1(pds[24])
#define  PDS_Subcenter       GET_UINT1(pds[25])
#define  PDS_DecimalScale    GET_INT2(pds[26],pds[27])


/* GRIB1 Section 2: Grid Description Section (GDS) */

#define  GDS_Len             ((gds) == NULL ? 0 : GET_UINT3(gds[0], gds[1], gds[2]))
#define  GDS_NV              GET_UINT1(gds[ 3])
#define  GDS_PVPL            GET_UINT1(gds[ 4])
#define  GDS_PV	             ((gds[3] ==    0) ? -1 : (int) gds[4] - 1)
#define  GDS_PL	             ((gds[4] == 0xFF) ? -1 : (int) gds[3] * 4 + (int) gds[4] - 1)
#define  GDS_GridType        GET_UINT1(gds[ 5])


/* GRIB1 Triangular grid of DWD */
#define  GDS_GME_NI2         GET_UINT2(gds[ 6], gds[ 7])
#define  GDS_GME_NI3         GET_UINT2(gds[ 8], gds[ 9])
#define  GDS_GME_ND          GET_UINT3(gds[10], gds[11], gds[12])
#define  GDS_GME_NI          GET_UINT3(gds[13], gds[14], gds[15])
#define  GDS_GME_AFlag       GET_UINT1(gds[16])
#define  GDS_GME_LatPP       GET_INT3(gds[17], gds[18], gds[19])
#define  GDS_GME_LonPP       GET_INT3(gds[20], gds[21], gds[22])
#define  GDS_GME_LonMPL      GET_INT3(gds[23], gds[24], gds[25])
#define  GDS_GME_BFlag       GET_UINT1(gds[27])

/* GRIB1 Spectral */
#define  GDS_PentaJ          GET_UINT2(gds[ 6], gds[ 7])
#define  GDS_PentaK          GET_UINT2(gds[ 8], gds[ 9])
#define  GDS_PentaM          GET_UINT2(gds[10], gds[11])
#define  GDS_RepType         GET_UINT1(gds[12])
#define  GDS_RepMode         GET_UINT1(gds[13])

/* GRIB1 Regular grid */
#define  GDS_NumLon          GET_UINT2(gds[ 6], gds[ 7])
#define  GDS_NumLat          GET_UINT2(gds[ 8], gds[ 9])
#define  GDS_FirstLat        GET_INT3(gds[10], gds[11], gds[12])
#define  GDS_FirstLon        GET_INT3(gds[13], gds[14], gds[15])
#define  GDS_ResFlag         GET_UINT1(gds[16])
#define  GDS_LastLat         GET_INT3(gds[17], gds[18], gds[19])
#define  GDS_LastLon         GET_INT3(gds[20], gds[21], gds[22])
#define  GDS_LonIncr         GET_UINT2(gds[23], gds[24])
#define  GDS_LatIncr         GET_UINT2(gds[25], gds[26])
#define  GDS_NumPar          GET_UINT2(gds[25], gds[26])
#define  GDS_ScanFlag        GET_UINT1(gds[27])
#define  GDS_LatSP           GET_INT3(gds[32], gds[33], gds[34])
#define  GDS_LonSP           GET_INT3(gds[35], gds[36], gds[37])
#define  GDS_RotAngle        (GET_Real(&(gds[38])))

/* GRIB1 Lambert */
#define  GDS_Lambert_Lov     GET_INT3(gds[17], gds[18], gds[19])
#define  GDS_Lambert_dx	     GET_INT3(gds[20], gds[21], gds[22])
#define  GDS_Lambert_dy	     GET_INT3(gds[23], gds[24], gds[25])
#define  GDS_Lambert_ProjFlag GET_UINT1(gds[26])
#define  GDS_Lambert_LatS1   GET_INT3(gds[28], gds[29], gds[30])
#define  GDS_Lambert_LatS2   GET_INT3(gds[31], gds[32], gds[33])
#define  GDS_Lambert_LatSP   GET_INT3(gds[34], gds[35], gds[36])
#define  GDS_Lambert_LonSP   GET_INT3(gds[37], gds[37], gds[37])

/* GRIB1 Section 3: Bit Map Section (BMS) */

#define  BMS_Len	     ((bms) == NULL ? 0 : GET_UINT3(bms[0], bms[1], bms[2]))
#define  BMS_UnusedBits      (bms[3])
#define  BMS_Bitmap	     ((bms) == NULL ? NULL : (bms)+6)
#define  BMS_BitmapSize      (((((bms[0]<<16)+(bms[1]<<8)+bms[2]) - 6)<<3) - bms[3])

/* GRIB1 Section 4: Binary Data Section (BDS) */

#define  BDS_Len	    GET_UINT3(bds[0], bds[1], bds[2])
#define  BDS_Flag	    (bds[3])
#define  BDS_BinScale       GET_INT2(bds[ 4], bds[ 5])
#define  BDS_RefValue       (decfp2((int)bds[ 6], (int)(GET_UINT3(bds[7], bds[8], bds[9]))))
#define  BDS_NumBits        ((int) bds[10])
#define  BDS_RealCoef       (decfp2((int)bds[zoff+11], (int)(GET_UINT3(bds[zoff+12], bds[zoff+13], bds[zoff+14]))))
#define  BDS_PackData       ((int) ((bds[zoff+11]<<8) + bds[zoff+12]))
#define  BDS_Power          GET_INT2(bds[zoff+13], bds[zoff+14])
#define  BDS_Z              (bds[13])

/* GRIB1 Section 5: End Section (ES) */

/* GRIB2 */

#define  GRIB2_SECLEN(section)   (GET_UINT4(section[0], section[1], section[2], section[3]))
#define  GRIB2_SECNUM(section)   (GET_UINT1(section[4]))

// clang-format on

#endif /* GRIBDECODE_H */
#ifndef CGRIBEX_GRIB_ENCODE_H
#define CGRIBEX_GRIB_ENCODE_H

#include <limits.h>

// clang-format off

#define PutnZero(n) \
{ \
  for ( size_t i___ = z >= 0 ? (size_t)z : 0; i___ < (size_t)(z+n); i___++ ) lGrib[i___] = 0; \
  z += n; \
}

#define Put1Byte(Value)  (lGrib[z++] = (GRIBPACK)(Value))
#define Put2Byte(Value) ((lGrib[z++] = (GRIBPACK)((Value) >>  8)),      \
                         (lGrib[z++] = (GRIBPACK)(Value)))
#define Put3Byte(Value) ((lGrib[z++] = (GRIBPACK)((Value) >> 16)),      \
                         (lGrib[z++] = (GRIBPACK)((Value) >>  8)),      \
                         (lGrib[z++] = (GRIBPACK)(Value)))
#define Put4Byte(Value) ((lGrib[z++] = (GRIBPACK)((Value) >> 24)),      \
                         (lGrib[z++] = (GRIBPACK)((Value) >> 16)),      \
                         (lGrib[z++] = (GRIBPACK)((Value) >>  8)),      \
                         (lGrib[z++] = (GRIBPACK)(Value)))

#define Put1Int(Value)  {ival = Value; if ( ival < 0 ) ival =     0x80 - ival; Put1Byte(ival);}
#define Put2Int(Value)  {ival = Value; if ( ival < 0 ) ival =   0x8000 - ival; Put2Byte(ival);}
#define Put3Int(Value)  {ival = Value; if ( ival < 0 ) ival = 0x800000 - ival; Put3Byte(ival);}

enum {
  BitsPerInt = (int) (sizeof(int) * CHAR_BIT),
};


#define Put1Real(Value)          \
{                                \
  confp3(Value, &exponent, &mantissa, BitsPerInt, 1); \
  Put1Byte(exponent);            \
  Put3Byte(mantissa);            \
}

// clang-format on

#endif /* CGRIBEX_GRIB_ENCODE_H */
#ifndef CODEC_COMMON_H
#define CODEC_COMMON_H
#define gribSwapByteOrder_uint16(ui16) ((uint16_t) ((ui16 << 8) | (ui16 >> 8)))
#endif /* CODEC_COMMON_H */
/*
icc -g -Wall -O3 -march=native -std=c99 -qopt-report=5 -DTEST_MINMAXVAL -qopenmp -DOMP_SIMD minmax_val.c
 result on hama2 (icc 16.0.0):
     float:
minmax_val: fmin: -500000  fmax: 499999  time:   1.22s
simd      : fmin: -500000  fmax: 499999  time:   1.20s
    double:
minmax_val: fmin: -500000  fmax: 499999  time:   2.86s
orig      : fmin: -500000  fmax: 499999  time:   2.74s
simd      : fmin: -500000  fmax: 499999  time:   2.70s
avx       : fmin: -500000  fmax: 499999  time:   2.99s

gcc -g -Wall -O3 -march=native -std=c99 -DTEST_MINMAXVAL -fopenmp -DOMP_SIMD -Wa,-q minmax_val.c
 result on thunder5 (gcc 6.1.0):
float:
minmax_val: fmin: -500000  fmax: 499999  time:   8.25s
  simd    : fmin: -500000  fmax: 499999  time:   1.24s
double:
minmax_val: fmin: -500000  fmax: 499999  time:   2.73s
  orig    : fmin: -500000  fmax: 499999  time:   9.24s
  simd    : fmin: -500000  fmax: 499999  time:   2.78s
  avx     : fmin: -500000  fmax: 499999  time:   2.90s

gcc -g -Wall -O3 -march=native -std=c99 -DTEST_MINMAXVAL minmax_val.c
 result on bailung (gcc 4.8.2):
  orig    : fmin: -500000  fmax: 499999  time:   4.82s
  sse2    : fmin: -500000  fmax: 499999  time:   4.83s

gcc -g -Wall -O3 -march=native -std=c99 -DTEST_MINMAXVAL -fopenmp -DOMP_SIMD -Wa,-q minmax_val.c
 result on thunder5 (gcc 4.8.2):
  orig    : fmin: -500000  fmax: 499999  time:   3.10s
  simd    : fmin: -500000  fmax: 499999  time:   3.10s # omp simd in gcc 4.9
  avx     : fmin: -500000  fmax: 499999  time:   2.84s

icc -g -Wall -O3 -march=native -std=c99 -qopt-report=5 -DTEST_MINMAXVAL -openmp -DOMP_SIMD minmax_val.c
 result on thunder5 (icc 14.0.2):
  orig    : fmin: -500000  fmax: 499999  time:   2.83s
  simd    : fmin: -500000  fmax: 499999  time:   2.83s
  avx     : fmin: -500000  fmax: 499999  time:   2.92s

xlc_r -g -O3 -qhot -q64 -qarch=auto -qtune=auto -qreport -DTEST_MINMAXVAL minmax_val.c
 result on blizzard (xlc 12):
  orig    : fmin: -500000  fmax: 499999  time:   7.26s
  pwr6u6  : fmin: -500000  fmax: 499999  time:   5.92s
*/
#ifdef _ARCH_PWR6
#pragma options nostrict
#endif

#ifdef OMP_SIMD
#include <omp.h>
#endif

#include <stdlib.h>

// #undef _GET_X86_COUNTER
// #undef _GET_IBM_COUNTER
// #undef _GET_MACH_COUNTER
// #undef _ARCH_PWR6

#if defined(_GET_IBM_COUNTER)
#include <libhpc.h>
#elif defined(_GET_X86_COUNTER)
#include <x86intrin.h>
#elif defined(_GET_MACH_COUNTER)
#include <mach/mach_time.h>
#endif

#if defined(__GNUC__) && !defined(__ICC) && !defined(__clang__)
#if (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 4)
#define GNUC_PUSH_POP
#endif
#endif

#ifndef DISABLE_SIMD
#if defined(__GNUC__) && (__GNUC__ >= 4)
#elif defined(__ICC) && (__ICC >= 1100)
#elif defined(__clang__)
#else
#define DISABLE_SIMD
#endif
#endif

#ifdef DISABLE_SIMD
#define DISABLE_SIMD_MINMAXVAL
#endif

#ifndef TEST_MINMAXVAL
#define DISABLE_SIMD_MINMAXVAL
#endif

#ifdef DISABLE_SIMD_MINMAXVAL
#ifdef ENABLE_AVX
#define _ENABLE_AVX
#endif
#ifdef ENABLE_SSE2
#define _ENABLE_SSE2
#endif
#endif

#ifndef DISABLE_SIMD_MINMAXVAL
#ifdef __AVX__
#define _ENABLE_AVX
#endif
#ifdef __SSE2__
#define _ENABLE_SSE2
#endif
#endif

#include <float.h>
#include <stdint.h>
#include <inttypes.h>

#if defined(_ENABLE_AVX)
#include <immintrin.h>
#elif defined(_ENABLE_SSE2)
#include <emmintrin.h>
#endif

#if defined(_ENABLE_AVX)

static void
avx_minmax_val_double(const double *restrict buf, size_t nframes, double *min, double *max)
{
  double fmin[4], fmax[4];
  __m256d current_max, current_min, work;

  // load max and min values into all four slots of the YMM registers
  current_min = _mm256_set1_pd(*min);
  current_max = _mm256_set1_pd(*max);

  // Work input until "buf" reaches 32 byte alignment
  while (((unsigned long) buf) % 32 != 0 && nframes > 0)
  {

    // Load the next double into the work buffer
    work = _mm256_set1_pd(*buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf++;
    nframes--;
  }

  while (nframes >= 16)
  {

    (void) _mm_prefetch((const char *) (buf + 8), _MM_HINT_NTA);

    work = _mm256_load_pd(buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf += 4;

    work = _mm256_load_pd(buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf += 4;

    (void) _mm_prefetch((const char *) (buf + 8), _MM_HINT_NTA);

    work = _mm256_load_pd(buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf += 4;

    work = _mm256_load_pd(buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf += 4;
    nframes -= 16;
  }

  // work through aligned buffers
  while (nframes >= 4)
  {
    work = _mm256_load_pd(buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf += 4;
    nframes -= 4;
  }

  // work through the remainung values
  while (nframes > 0)
  {
    work = _mm256_set1_pd(*buf);
    current_min = _mm256_min_pd(current_min, work);
    current_max = _mm256_max_pd(current_max, work);
    buf++;
    nframes--;
  }

  // find min & max value through shuffle tricks

  work = current_min;
  work = _mm256_shuffle_pd(work, work, 5);
  work = _mm256_min_pd(work, current_min);
  current_min = work;
  work = _mm256_permute2f128_pd(work, work, 1);
  work = _mm256_min_pd(work, current_min);
  _mm256_storeu_pd(fmin, work);

  work = current_max;
  work = current_max;
  work = _mm256_shuffle_pd(work, work, 5);
  work = _mm256_max_pd(work, current_max);
  current_max = work;
  work = _mm256_permute2f128_pd(work, work, 1);
  work = _mm256_max_pd(work, current_max);
  _mm256_storeu_pd(fmax, work);

  *min = fmin[0];
  *max = fmax[0];

  return;
}

#elif defined(_ENABLE_SSE2)

static void
sse2_minmax_val_double(const double *restrict buf, size_t nframes, double *min, double *max)
{
  __m128d current_max, current_min, work;

  // load starting max and min values into all slots of the XMM registers
  current_min = _mm_set1_pd(*min);
  current_max = _mm_set1_pd(*max);

  // work on input until buf reaches 16 byte alignment
  while (((unsigned long) buf) % 16 != 0 && nframes > 0)
  {

    // load one double and replicate
    work = _mm_set1_pd(*buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf++;
    nframes--;
  }

  while (nframes >= 8)
  {
    // use 64 byte prefetch for double octetts
    // __builtin_prefetch(buf+64,0,0); // for GCC 4.3.2 +

    work = _mm_load_pd(buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf += 2;
    work = _mm_load_pd(buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf += 2;
    work = _mm_load_pd(buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf += 2;
    work = _mm_load_pd(buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf += 2;
    nframes -= 8;
  }

  // work through smaller chunks of aligned buffers without prefetching
  while (nframes >= 2)
  {
    work = _mm_load_pd(buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf += 2;
    nframes -= 2;
  }

  // work through the remaining value
  while (nframes > 0)
  {
    // load the last double and replicate
    work = _mm_set1_pd(*buf);
    current_min = _mm_min_pd(current_min, work);
    current_max = _mm_max_pd(current_max, work);
    buf++;
    nframes--;
  }

  // find final min and max value through shuffle tricks
  work = current_min;
  work = _mm_shuffle_pd(work, work, _MM_SHUFFLE2(0, 1));
  work = _mm_min_pd(work, current_min);
  _mm_store_sd(min, work);
  work = current_max;
  work = _mm_shuffle_pd(work, work, _MM_SHUFFLE2(0, 1));
  work = _mm_max_pd(work, current_max);
  _mm_store_sd(max, work);

  return;
}

#endif  // SIMD

#if defined(_ARCH_PWR6)
static void
pwr6_minmax_val_double_unrolled6(const double *restrict data, size_t datasize, double *fmin, double *fmax)
{
#define __UNROLL_DEPTH_1 6

  // to allow pipelining we have to unroll

  {
    size_t residual = datasize % __UNROLL_DEPTH_1;
    size_t ofs = datasize - residual;
    double dmin[__UNROLL_DEPTH_1];
    double dmax[__UNROLL_DEPTH_1];

    for (size_t j = 0; j < __UNROLL_DEPTH_1; ++j)
    {
      dmin[j] = data[0];
      dmax[j] = data[0];
    }

    for (size_t i = 0; i < datasize - residual; i += __UNROLL_DEPTH_1)
    {
      for (size_t j = 0; j < __UNROLL_DEPTH_1; ++j)
      {
        dmin[j] = __fsel(dmin[j] - data[i + j], data[i + j], dmin[j]);
        dmax[j] = __fsel(data[i + j] - dmax[j], data[i + j], dmax[j]);
      }
    }

    for (size_t j = 0; j < residual; ++j)
    {
      dmin[j] = __fsel(dmin[j] - data[ofs + j], data[ofs + j], dmin[j]);
      dmax[j] = __fsel(data[ofs + j] - dmax[j], data[ofs + j], dmax[j]);
    }

    for (size_t j = 0; j < __UNROLL_DEPTH_1; ++j)
    {
      *fmin = __fsel(*fmin - dmin[j], dmin[j], *fmin);
      *fmax = __fsel(dmax[j] - *fmax, dmax[j], *fmax);
    }
  }
#undef __UNROLL_DEPTH_1
}
#endif

// clang-format off
#if defined(TEST_MINMAXVAL) && defined(__GNUC__)
static void minmax_val_double_orig(const double *restrict data, size_t datasize, double *fmin, double *fmax) __attribute__((noinline));
static void minmax_val_double_simd(const double *restrict data, size_t datasize, double *fmin, double *fmax) __attribute__((noinline));
static void minmax_val_double_omp(const double *restrict data, size_t datasize, double *fmin, double *fmax) __attribute__((noinline));
static void minmax_val_float(const float *restrict data, long datasize, float *fmin, float *fmax) __attribute__((noinline));
static void minmax_val_float_simd(const float *restrict data, size_t datasize, float *fmin, float *fmax) __attribute__((noinline));
#endif
// clang-format on

#if defined(GNUC_PUSH_POP) && defined __OPTIMIZE__
#pragma GCC push_options
#pragma GCC optimize("O3", "fast-math")
#endif
static void
minmax_val_double_orig(const double *restrict data, size_t datasize, double *fmin, double *fmax)
{
  double dmin = *fmin, dmax = *fmax;

#if defined(CRAY)
#pragma _CRI ivdep
#elif defined(SX)
#pragma vdir nodep
#elif defined(__uxp__)
#pragma loop novrec
#elif defined(__ICC)
#pragma ivdep
#endif
  for (size_t i = 0; i < datasize; ++i)
  {
    dmin = (dmin < data[i]) ? dmin : data[i];
    dmax = (dmax > data[i]) ? dmax : data[i];
  }

  *fmin = dmin;
  *fmax = dmax;
}

static void
minmax_val_float(const float *restrict data, long idatasize, float *fmin, float *fmax)
{
  size_t datasize = (size_t) idatasize;
  float dmin = *fmin, dmax = *fmax;

#if defined(CRAY)
#pragma _CRI ivdep
#elif defined(SX)
#pragma vdir nodep
#elif defined(__uxp__)
#pragma loop novrec
#elif defined(__ICC)
#pragma ivdep
#endif
  for (size_t i = 0; i < datasize; ++i)
  {
    dmin = (dmin < data[i]) ? dmin : data[i];
    dmax = (dmax > data[i]) ? dmax : data[i];
  }

  *fmin = dmin;
  *fmax = dmax;
}
#if defined(GNUC_PUSH_POP) && defined __OPTIMIZE__
#pragma GCC pop_options
#endif

// TEST
#if defined(OMP_SIMD)

#if defined(GNUC_PUSH_POP) && defined __OPTIMIZE__
#pragma GCC push_options
#pragma GCC optimize("O3", "fast-math")
#endif
static void
minmax_val_double_omp(const double *restrict data, size_t datasize, double *fmin, double *fmax)
{
  double dmin = *fmin, dmax = *fmax;

#if defined(_OPENMP)
#pragma omp parallel for simd reduction(min : dmin) reduction(max : dmax)
#endif
  for (size_t i = 0; i < datasize; ++i)
  {
    dmin = (dmin < data[i]) ? dmin : data[i];
    dmax = (dmax > data[i]) ? dmax : data[i];
  }

  *fmin = dmin;
  *fmax = dmax;
}

static void
minmax_val_double_simd(const double *restrict data, size_t datasize, double *fmin, double *fmax)
{
  double dmin = *fmin, dmax = *fmax;

#ifdef _OPENMP
#pragma omp simd reduction(min : dmin) reduction(max : dmax)
#endif
  for (size_t i = 0; i < datasize; ++i)
  {
    dmin = (dmin < data[i]) ? dmin : data[i];
    dmax = (dmax > data[i]) ? dmax : data[i];
  }

  *fmin = dmin;
  *fmax = dmax;
}

static void
minmax_val_float_simd(const float *restrict data, size_t datasize, float *fmin, float *fmax)
{
  float dmin = *fmin, dmax = *fmax;

#if defined(_OPENMP)
#pragma omp simd reduction(min : dmin) reduction(max : dmax)
#endif
  for (size_t i = 0; i < datasize; ++i)
  {
    dmin = (dmin < data[i]) ? dmin : data[i];
    dmax = (dmax > data[i]) ? dmax : data[i];
  }

  *fmin = dmin;
  *fmax = dmax;
}
#if defined(GNUC_PUSH_POP) && defined __OPTIMIZE__
#pragma GCC pop_options
#endif
#endif

static void
minmax_val_double(const double *restrict data, long idatasize, double *fmin, double *fmax)
{
#if defined(_GET_X86_COUNTER) || defined(_GET_MACH_COUNTER)
  uint64_t start_minmax, end_minmax;
#endif
  size_t datasize = (size_t) idatasize;

  if (idatasize >= 1)
    ;
  else
    return;

#if defined(_GET_X86_COUNTER)
  start_minmax = _rdtsc();
#endif
#if defined(_GET_MACH_COUNTER)
  start_minmax = mach_absolute_time();
#endif

#if defined(_ENABLE_AVX)

  avx_minmax_val_double(data, datasize, fmin, fmax);

#elif defined(_ENABLE_SSE2)

  sse2_minmax_val_double(data, datasize, fmin, fmax);

#else

#if defined(_ARCH_PWR6)
#define __UNROLL_DEPTH_1 6

    // to allow pipelining we have to unroll

#if defined(_GET_IBM_COUNTER)
  hpmStart(1, "minmax fsel");
#endif

  pwr6_minmax_val_double_unrolled6(data, datasize, fmin, fmax);

#if defined(_GET_IBM_COUNTER)
  hpmStop(1);
#endif

#undef __UNROLL_DEPTH_1

#else  // original loop

#if defined(_GET_IBM_COUNTER)
  hpmStart(1, "minmax base");
#endif

  minmax_val_double_orig(data, datasize, fmin, fmax);

#if defined(_GET_IBM_COUNTER)
  hpmStop(1);
#endif

#endif  // _ARCH_PWR6 && original loop
#endif  // SIMD

#if defined(_GET_X86_COUNTER) || defined(_GET_MACH_COUNTER)
#if defined(_GET_X86_COUNTER)
  end_minmax = _rdtsc();
#endif
#if defined(_GET_MACH_COUNTER)
  end_minmax = mach_absolute_time();
#endif
#if defined(_ENABLE_AVX)
  printf("AVX minmax cycles:: %" PRIu64 "\n", end_minmax - start_minmax);
  fprintf(stderr, "AVX min: %lf max: %lf\n", *fmin, *fmax);
#elif defined(_ENABLE_SSE2)
  printf("SSE2 minmax cycles:: %" PRIu64 "\n", end_minmax - start_minmax);
  fprintf(stderr, "SSE2 min: %lf max: %lf\n", *fmin, *fmax);
#else
  printf("loop minmax cycles:: %" PRIu64 "\n", end_minmax - start_minmax);
  fprintf(stderr, "loop min: %lf max: %lf\n", *fmin, *fmax);
#endif
#endif

  return;
}

#if defined(TEST_MINMAXVAL)

#include <stdio.h>
#include <sys/time.h>

static double
dtime()
{
  double tseconds = 0.0;
  struct timeval mytime;
  gettimeofday(&mytime, NULL);
  tseconds = (double) (mytime.tv_sec + (double) mytime.tv_usec * 1.0e-6);
  return (tseconds);
}

#define NRUN 10000

int
main(void)
{
  long datasize = 1000000;
  double t_begin, t_end;

  printf("datasize %ld\n", datasize);
#if defined(_OPENMP)
  printf("_OPENMP=%d\n", _OPENMP);
#endif

#if defined(__ICC)
  printf("icc\n");
#elif defined(__clang__)
  printf("clang\n");
#elif defined(__GNUC__)
  printf("gcc\n");
#endif

  {
    float fmin, fmax;
    float *data_sp = (float *) malloc(datasize * sizeof(float));

    for (long i = 0; i < datasize / 2; ++i) data_sp[i] = (float) (i);
    for (long i = datasize / 2; i < datasize; ++i) data_sp[i] = (float) (-datasize + i);

    printf("float:\n");

    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_sp[0];
      minmax_val_float(data_sp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("minmax_val: fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);

#if defined(OMP_SIMD)
    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_sp[0];
      minmax_val_float_simd(data_sp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("simd      : fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);
#endif

    free(data_sp);
  }

  {
    double fmin, fmax;
    double *data_dp = (double *) malloc(datasize * sizeof(double));

    // for (long i = datasize-1; i >= 0; --i) data[i] = (double) (-datasize/2 + i);
    for (long i = 0; i < datasize / 2; ++i) data_dp[i] = (double) (i);
    for (long i = datasize / 2; i < datasize; ++i) data_dp[i] = (double) (-datasize + i);

    printf("double:\n");

    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      minmax_val_double(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("minmax_val: fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);

    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      minmax_val_double_orig(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("orig      : fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);

#if defined(OMP_SIMD)
    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      minmax_val_double_simd(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("simd      : fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);

    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      minmax_val_double_omp(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("openmp %d  : fmin: %ld  fmax: %ld  time: %6.2fs\n", omp_get_max_threads(), (long) fmin, (long) fmax, t_end - t_begin);
#endif

#if defined(_ENABLE_AVX)
    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      avx_minmax_val_double(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("avx       : fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);
#elif defined(_ENABLE_SSE2)
    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      sse2_minmax_val_double(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("sse2      : fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);
#endif
#if defined(_ARCH_PWR6)
    t_begin = dtime();
    for (int i = 0; i < NRUN; ++i)
    {
      fmin = fmax = data_dp[0];
      pwr6_minmax_val_double_unrolled6(data_dp, datasize, &fmin, &fmax);
    }
    t_end = dtime();
    printf("pwr6u6  : fmin: %ld  fmax: %ld  time: %6.2fs\n", (long) fmin, (long) fmax, t_end - t_begin);
#endif
    free(data_dp);
  }

  return 0;
}
#endif  // TEST_MINMAXVAL

#undef DISABLE_SIMD_MINMAXVAL
#undef _ENABLE_AVX
#undef _ENABLE_SSE2
#undef GNUC_PUSH_POP
/*
### new version with gribSwapByteOrder_uint16()
icc -g -Wall -O3 -march=native -std=c99 -qopt-report=5 -DTEST_ENCODE encode_array.c
 result on hama2 (icc 16.0.2):
   float:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 1.8731s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 2.0898s
  double:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 3.68089s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 4.30798s
     avx: val1: 1  val2: 1  val3: 2  valn: 66  time: 4.23864s

gcc -g -Wall -O3 -march=native -Wa,-q -std=c99 -DTEST_ENCODE encode_array.c
 result on hama2 (gcc 6.1.0):
float:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 2.22871s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 2.30281s
double:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 4.2669s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 4.81643s
     avx: val1: 1  val2: 1  val3: 2  valn: 66  time: 3.98415s

###
icc -g -Wall -O3 -march=native -std=c99 -qopt-report=5 -DTEST_ENCODE encode_array.c
 result on hama2 (icc 16.0.0):
   float:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 9.10691s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 8.63584s
  double:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 13.5768s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 9.17742s
     avx: val1: 1  val2: 1  val3: 2  valn: 66  time: 3.9488s

gcc -g -Wall -O3 -std=c99 -DTEST_ENCODE encode_array.c
 result on hama2 (gcc 5.2.0):
   float:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 5.32775s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 7.87125s
  double:
    orig: val1: 1  val2: 1  val3: 2  valn: 66  time: 7.85873s
unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time: 12.9979s

###
gcc -g -Wall -O3 -march=native -std=c99 -DTEST_ENCODE encode_array.c
 result on bailung (gcc 4.7):
  orig    : val1: 1  val2: 1  val3: 2  valn: 66  time: 8.4166s
  sse41   : val1: 1  val2: 1  val3: 2  valn: 66  time: 7.1522s

gcc -g -Wall -O3 -march=native -std=c99 -DTEST_ENCODE encode_array.c
 result on thunder5 (gcc 4.7):
  orig    : val1: 1  val2: 1  val3: 2  valn: 66  time: 6.21976s
  avx     : val1: 1  val2: 1  val3: 2  valn: 66  time: 4.54485s

icc -g -Wall -O3 -march=native -std=c99 -vec-report=1 -DTEST_ENCODE encode_array.c
 result on thunder5 (icc 13.2):
  orig    : val1: 1  val2: 1  val3: 2  valn: 66  time: 14.6279s
  avx     : val1: 1  val2: 1  val3: 2  valn: 66  time:  4.9776s

xlc_r -g -O3 -qhot -q64 -qarch=auto -qtune=auto -qreport -DTEST_ENCODE encode_array.c
 result on blizzard (xlc 12):
  orig    : val1: 1  val2: 1  val3: 2  valn: 66  time: 132.25s
  unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time:  27.202s
  orig    : val1: 1  val2: 1  val3: 2  valn: 66  time: 106.627s  // without -qhot
  unrolled: val1: 1  val2: 1  val3: 2  valn: 66  time:  39.929s  // without -qhot
*/
#ifdef _ARCH_PWR6
#pragma options nostrict
#endif

#ifdef TEST_ENCODE
#include <stdio.h>
#include <stdlib.h>
#define GRIBPACK unsigned char

#ifndef HOST_ENDIANNESS
#ifdef __cplusplus
static const uint32_t HOST_ENDIANNESS_temp[1] = { UINT32_C(0x00030201) };
#define HOST_ENDIANNESS (((const unsigned char *) HOST_ENDIANNESS_temp)[0])
#else
#define HOST_ENDIANNESS (((const unsigned char *) &(const uint32_t[1]){ UINT32_C(0x00030201) })[0])
#endif
#endif

#define IS_BIGENDIAN() (HOST_ENDIANNESS == 0)
#define Error(x, y)
#endif

// #undef _GET_X86_COUNTER
// #undef _GET_MACH_COUNTER
// #undef _GET_IBM_COUNTER
// #undef _ARCH_PWR6

#if defined _GET_IBM_COUNTER
#include <libhpc.h>
#elif defined _GET_X86_COUNTER
#include <x86intrin.h>
#elif defined _GET_MACH_COUNTER
#include <mach/mach_time.h>
#endif

#include <stdint.h>
#include <math.h>

#ifndef DISABLE_SIMD
#if defined(__GNUC__) && (__GNUC__ >= 4)
#elif defined(__ICC) && (__ICC >= 1100)
#elif defined(__clang__)
#else
#define DISABLE_SIMD
#endif
#endif

#ifdef DISABLE_SIMD
#define DISABLE_SIMD_ENCODE
#endif

// #define DISABLE_SIMD_ENCODE

#ifdef DISABLE_SIMD_ENCODE
#ifdef ENABLE_AVX
#define _ENABLE_AVX
#endif
#ifdef ENABLE_SSE4_1
#define _ENABLE_SSE4_1
#endif
#endif

#ifndef DISABLE_SIMD_ENCODE
#ifdef __AVX__
#define _ENABLE_AVX
#endif
#ifdef __SSE4_1__
#define _ENABLE_SSE4_1
#endif
#endif

#if defined _ENABLE_AVX
#include <immintrin.h>
#elif defined _ENABLE_SSE4_1
#include <smmintrin.h>
#endif

#if defined _ENABLE_AVX

static void avx_encode_array_2byte_double(size_t datasize, unsigned char *restrict lGrib, const double *restrict data, double zref,
                                          double factor, size_t *gz) __attribute__((optimize(2)));
static void
avx_encode_array_2byte_double(size_t datasize, unsigned char *restrict lGrib, const double *restrict data, double zref,
                              double factor, size_t *gz)
{
  const double *dval = data;
  __m128i *sgrib = (__m128i *) (lGrib + (*gz));

  const __m128i swap = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);

  const __m256d c0 = _mm256_set1_pd(zref);
  const __m256d c1 = _mm256_set1_pd(factor);
  const __m256d c2 = _mm256_set1_pd(0.5);

  __m256d d0, d3, d2, d1;
  __m128i i0, i1, i2, i3;
  __m128i s0, s1;

  size_t residual = datasize % 16;

  size_t i;
  for (i = 0; i < (datasize - residual); i += 16)
  {
    (void) _mm_prefetch((const char *) (dval + 8), _MM_HINT_NTA);
    //_____________________________________________________________________________

    d0 = _mm256_loadu_pd(dval);
    d0 = _mm256_sub_pd(d0, c0);
    d0 = _mm256_mul_pd(d0, c1);
    d0 = _mm256_add_pd(d0, c2);

    i0 = _mm256_cvttpd_epi32(d0);

    //_____________________________________________________________________________

    d1 = _mm256_loadu_pd(dval + 4);
    d1 = _mm256_sub_pd(d1, c0);
    d1 = _mm256_mul_pd(d1, c1);
    d1 = _mm256_add_pd(d1, c2);

    i1 = _mm256_cvttpd_epi32(d1);

    //_____________________________________________________________________________

    s0 = _mm_packus_epi32(i0, i1);
    s0 = _mm_shuffle_epi8(s0, swap);
    (void) _mm_storeu_si128(sgrib, s0);

    //_____________________________________________________________________________

    (void) _mm_prefetch((const char *) (dval + 16), _MM_HINT_NTA);

    //_____________________________________________________________________________

    d2 = _mm256_loadu_pd(dval + 8);
    d2 = _mm256_sub_pd(d2, c0);
    d2 = _mm256_mul_pd(d2, c1);
    d2 = _mm256_add_pd(d2, c2);

    i2 = _mm256_cvttpd_epi32(d2);

    //_____________________________________________________________________________

    d3 = _mm256_loadu_pd(dval + 12);
    d3 = _mm256_sub_pd(d3, c0);
    d3 = _mm256_mul_pd(d3, c1);
    d3 = _mm256_add_pd(d3, c2);

    i3 = _mm256_cvttpd_epi32(d3);

    //_____________________________________________________________________________

    s1 = _mm_packus_epi32(i2, i3);
    s1 = _mm_shuffle_epi8(s1, swap);
    (void) _mm_storeu_si128(sgrib + 1, s1);

    //_____________________________________________________________________________

    dval += 16;
    sgrib += 2;
  }

  if (i != datasize)
  {
    uint16_t ui16;
    for (size_t j = i; j < datasize; ++j)
    {
      ui16 = (uint16_t) ((data[j] - zref) * factor + 0.5);
      lGrib[*gz + 2 * j] = ui16 >> 8;
      lGrib[*gz + 2 * j + 1] = ui16;
    }
  }

  *gz += 2 * datasize;

  return;
}

#define grib_encode_array_2byte_double avx_encode_array_2byte_double

#elif defined _ENABLE_SSE4_1

static void
sse41_encode_array_2byte_double(size_t datasize, unsigned char *restrict lGrib, const double *restrict data, double zref,
                                double factor, size_t *gz)
{
  const double *dval = data;
  __m128i *sgrib = (__m128i *) (lGrib + (*gz));

  const __m128i swap = _mm_set_epi8(14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);

  const __m128d c0 = _mm_set1_pd(zref);
  const __m128d c1 = _mm_set1_pd(factor);
  const __m128d c2 = _mm_set1_pd(0.5);

  __m128d d0, d4, d3, d2, d1;
  __m128i i0, i1, i2, i3, i4;
  __m128i s0, s1;

  size_t residual = datasize % 16;

  size_t i;
  for (i = 0; i < (datasize - residual); i += 16)
  {
    (void) _mm_prefetch((const char *) (dval + 8), _MM_HINT_NTA);
    //_____________________________________________________________________________

    d0 = _mm_loadu_pd(dval);
    d0 = _mm_sub_pd(d0, c0);
    d0 = _mm_mul_pd(d0, c1);
    d0 = _mm_add_pd(d0, c2);

    d4 = _mm_loadu_pd(dval + 2);
    d4 = _mm_sub_pd(d4, c0);
    d4 = _mm_mul_pd(d4, c1);
    d4 = _mm_add_pd(d4, c2);

    i0 = _mm_cvttpd_epi32(d0);
    i4 = _mm_cvttpd_epi32(d4);
    i0 = _mm_unpacklo_epi64(i0, i4);

    //_____________________________________________________________________________

    d1 = _mm_loadu_pd(dval + 4);
    d1 = _mm_sub_pd(d1, c0);
    d1 = _mm_mul_pd(d1, c1);
    d1 = _mm_add_pd(d1, c2);

    d4 = _mm_loadu_pd(dval + 6);
    d4 = _mm_sub_pd(d4, c0);
    d4 = _mm_mul_pd(d4, c1);
    d4 = _mm_add_pd(d4, c2);

    i1 = _mm_cvttpd_epi32(d1);
    i4 = _mm_cvttpd_epi32(d4);
    i1 = _mm_unpacklo_epi64(i1, i4);

    //_____________________________________________________________________________

    s0 = _mm_packus_epi32(i0, i1);
    s0 = _mm_shuffle_epi8(s0, swap);
    (void) _mm_storeu_si128(sgrib, s0);

    //_____________________________________________________________________________

    (void) _mm_prefetch((const char *) (dval + 16), _MM_HINT_NTA);

    //_____________________________________________________________________________

    d2 = _mm_loadu_pd(dval + 8);
    d2 = _mm_sub_pd(d2, c0);
    d2 = _mm_mul_pd(d2, c1);
    d2 = _mm_add_pd(d2, c2);

    d4 = _mm_loadu_pd(dval + 10);
    d4 = _mm_sub_pd(d4, c0);
    d4 = _mm_mul_pd(d4, c1);
    d4 = _mm_add_pd(d4, c2);

    i2 = _mm_cvttpd_epi32(d2);
    i4 = _mm_cvttpd_epi32(d4);
    i2 = _mm_unpacklo_epi64(i2, i4);

    //_____________________________________________________________________________

    d3 = _mm_loadu_pd(dval + 12);
    d3 = _mm_sub_pd(d3, c0);
    d3 = _mm_mul_pd(d3, c1);
    d3 = _mm_add_pd(d3, c2);

    d4 = _mm_loadu_pd(dval + 14);
    d4 = _mm_sub_pd(d4, c0);
    d4 = _mm_mul_pd(d4, c1);
    d4 = _mm_add_pd(d4, c2);

    i3 = _mm_cvttpd_epi32(d3);
    i4 = _mm_cvttpd_epi32(d4);
    i3 = _mm_unpacklo_epi64(i3, i4);

    //_____________________________________________________________________________

    s1 = _mm_packus_epi32(i2, i3);
    s1 = _mm_shuffle_epi8(s1, swap);
    (void) _mm_storeu_si128(sgrib + 1, s1);

    //_____________________________________________________________________________

    dval += 16;
    sgrib += 2;
  }

  if (i != datasize)
  {
    uint16_t ui16;
    for (size_t j = i; j < datasize; ++j)
    {
      ui16 = (uint16_t) ((data[j] - zref) * factor + 0.5);
      lGrib[*gz + 2 * j] = ui16 >> 8;
      lGrib[*gz + 2 * j + 1] = ui16;
    }
  }

  *gz += 2 * datasize;

  return;
}

#define grib_encode_array_2byte_double sse41_encode_array_2byte_double

#else

#define grib_encode_array_2byte_double encode_array_2byte_double

#endif  // SIMD variants

#ifdef TEST_ENCODE

// clang-format off
#define CAT(X,Y)      X##_##Y
#define TEMPLATE(X,Y) CAT(X,Y)

#ifdef T
#undef T
#endif
#define T double

#ifdef T
#undef T
#endif
#define T float
// clang-format on

#include <sys/time.h>

static double
dtime()
{
  double tseconds = 0.0;
  struct timeval mytime;
  gettimeofday(&mytime, NULL);
  tseconds = (double) (mytime.tv_sec + (double) mytime.tv_usec * 1.0e-6);
  return (tseconds);
}

static void
pout(char *name, int s, unsigned char *lgrib, long datasize, double tt)
{
  printf("%8s: val1: %d  val2: %d  val3: %d  valn: %d  time: %gs\n", name, (int) lgrib[s * 1 + 1], (int) lgrib[s * 2 + 1],
         (int) lgrib[s * 3 + 1], (int) lgrib[2 * datasize - 1], tt);
}

int
main(void)
{
  enum
  {
    datasize = 1000000,
    NRUN = 10000,
  };

  double t_begin, t_end;

  float *dataf = (float *) malloc(datasize * sizeof(float));
  double *data = (double *) malloc(datasize * sizeof(double));
  unsigned char *lgrib = (unsigned char *) malloc(2 * datasize * sizeof(unsigned char));

  for (long i = 0; i < datasize; ++i) dataf[i] = (float) (-datasize / 2 + i);
  for (long i = 0; i < datasize; ++i) data[i] = (double) (-datasize / 2 + i);

  int PackStart = 0;
  int nbpv = 16;
  double zref = data[0];
  size_t z;
  double factor = 0.00390625;
  int s = 256;

  if (0)
  {
    encode_array_float(0, 0, 0, NULL, NULL, 0, 0, NULL);
    encode_array_double(0, 0, 0, NULL, NULL, 0, 0, NULL);
  }

#if defined(__ICC)
  printf("icc\n");
#elif defined(__clang__)
  printf("clang\n");
#elif defined(__GNUC__)
  printf("gcc\n");
#endif

  printf("float:\n");

  t_begin = dtime();
  for (int i = 0; i < NRUN; ++i)
  {
    z = 0;
    encode_array_2byte_float(datasize, lgrib, dataf, (float) zref, (float) factor, &z);
  }
  t_end = dtime();
  pout("orig", s, lgrib, datasize, t_end - t_begin);

  t_begin = dtime();
  for (int i = 0; i < NRUN; ++i)
  {
    z = 0;
    encode_array_unrolled_float(nbpv, PackStart, datasize, lgrib, dataf, (float) zref, (float) factor, &z);
  }
  t_end = dtime();
  pout("unrolled", s, lgrib, datasize, t_end - t_begin);

  printf("double:\n");

  t_begin = dtime();
  for (int i = 0; i < NRUN; ++i)
  {
    z = 0;
    encode_array_2byte_double(datasize, lgrib, data, zref, factor, &z);
  }
  t_end = dtime();
  pout("orig", s, lgrib, datasize, t_end - t_begin);

  t_begin = dtime();
  for (int i = 0; i < NRUN; ++i)
  {
    z = 0;
    encode_array_unrolled_double(nbpv, PackStart, datasize, lgrib, data, zref, factor, &z);
  }
  t_end = dtime();
  pout("unrolled", s, lgrib, datasize, t_end - t_begin);

#if defined _ENABLE_AVX
  t_begin = dtime();
  for (int i = 0; i < NRUN; ++i)
  {
    z = 0;
    avx_encode_array_2byte_double(datasize, lgrib, data, zref, factor, &z);
  }
  t_end = dtime();
  pout("avx", s, lgrib, datasize, t_end - t_begin);
#elif defined _ENABLE_SSE4_1
  t_begin = dtime();
  for (int i = 0; i < NRUN; ++i)
  {
    z = 0;
    sse41_encode_array_2byte_double(datasize, lgrib, data, zref, factor, &z);
  }
  t_end = dtime();
  pout("sse41", s, lgrib, datasize, t_end - t_begin);
#endif

  return 0;
}
#endif  // TEST_ENCODE

#undef DISABLE_SIMD_ENCODE
#undef _ENABLE_AVX
#undef _ENABLE_SSE4_1

void
confp3(double pval, int *kexp, int *kmant, int kbits, int kround)
{
  /*

    Purpose:
    --------

    Convert floating point number from machine
    representation to GRIB representation.

    Input Parameters:
    -----------------

       pval    - Floating point number to be converted.
       kbits   - Number of bits in computer word.
       kround  - Conversion type.
                 0 , Closest number in GRIB format less than
                     original number.
                 1 , Closest number in GRIB format to the
                     original number (equal to, greater than or
                     less than original number).

    Output Parameters:
    ------------------

       kexp    - 8 Bit signed exponent.
       kmant   - 24 Bit mantissa.

    Method:
    -------

    Floating point number represented as 8 bit signed
    exponent and 24 bit mantissa in integer values.

    Externals.
    ----------

    decfp2    - Decode from IBM floating point format.

    Reference:
    ----------

    WMO Manual on Codes re GRIB representation.

    Comments:
    ---------

    Routine aborts if an invalid conversion type parameter
    is used or if a 24 bit mantissa is not produced.

    Author:
    -------

    John Hennessy   ECMWF   18.06.91

    Modifications:
    --------------

    Uwe Schulzweida   MPIfM   01/04/2001

    Convert to C from EMOS library version 130

    Uwe Schulzweida   MPIfM   02/08/2002

     - speed up by factor 1.6 on NEC SX6
        - replace 1.0 / pow(16.0, (double)(iexp - 70)) by rpow16m70tab[iexp]
  */

  // extern int CGRIBEX_Debug;

  /* ----------------------------------------------------------------- */
  /*   Section 1 . Initialise                                          */
  /* ----------------------------------------------------------------- */

  // Check conversion type parameter.

  int iround = kround;
  if (iround != 0 && iround != 1)
  {
    Error("Invalid conversion type = %d", iround);

    // If not aborting, arbitrarily set rounding to 'up'.
    iround = 1;
  }

  /* ----------------------------------------------------------------- */
  /*   Section 2 . Convert value of zero.                              */
  /* ----------------------------------------------------------------- */

  if (fabs(pval) <= 0)
  {
    *kexp = 0;
    *kmant = 0;
    goto LABEL900;
  }

  /* ----------------------------------------------------------------- */
  /*   Section 3 . Convert other values.                               */
  /* ----------------------------------------------------------------- */
  {
    double zeps = (kbits != 32) ? 1.0e-12 : 1.0e-8;
    double zref = pval;

    // Sign of value.
    int isign = (zref >= 0.0) ? 0 : 128;
    zref = fabs(zref);

    // Exponent.
    int iexp = (int) (log(zref) / log(16.0) + 65.0 + zeps);

    // only ANSI C99 has log2
    // iexp = (int) (log2(zref) * 0.25 + 65.0 + zeps);

    if (iexp < 0) iexp = 0;
    if (iexp > 127) iexp = 127;

    // double rpowref = zref / pow(16.0, (double)(iexp - 70));
    double rpowref = ldexp(zref, 4 * -(iexp - 70));

    // Mantissa.
    if (iround == 0)
    {
      /*  Closest number in GRIB format less than original number. */
      /*  Truncate for positive numbers. */
      /*  Round up for negative numbers. */
      *kmant = (isign == 0) ? (int) rpowref : (int) lround(rpowref + 0.5);
    }
    else
    {
      /*  Closest number in GRIB format to the original number   */
      /*  (equal to, greater than or less than original number). */
      *kmant = (int) lround(rpowref);
    }

    /*  Check that mantissa value does not exceed 24 bits. */
    /*  If it does, adjust the exponent upwards and recalculate the mantissa. */
    /*  16777215 = 2**24 - 1 */
    if (*kmant > 16777215)
    {

    LABEL350:

      ++iexp;

      // Check for exponent overflow during adjustment
      if (iexp > 127)
      {
        Message("Exponent overflow");
        Message("Original number = %30.20f", pval);
        Message("Sign = %3d, Exponent = %3d, Mantissa = %12d", isign, iexp, *kmant);

        Error("Exponent overflow");

        // If not aborting, arbitrarily set value to zero
        Message("Value arbitrarily set to zero.");
        *kexp = 0;
        *kmant = 0;
        goto LABEL900;
      }

      rpowref = ldexp(zref, 4 * -(iexp - 70));

      if (iround == 0)
      {
        /*  Closest number in GRIB format less than original number. */
        /*  Truncate for positive numbers. */
        /*  Round up for negative numbers. */
        *kmant = (isign == 0) ? (int) rpowref : (int) lround(rpowref + 0.5);
      }
      else
      {
        /*  Closest number in GRIB format to the original number */
        /*  (equal to, greater or less than original number). */
        *kmant = (int) lround(rpowref);
      }

      // Repeat calculation (with modified exponent) if still have mantissa overflow.
      if (*kmant > 16777215) goto LABEL350;
    }

    // Add sign bit to exponent.
    *kexp = iexp + isign;
  }

  /* ----------------------------------------------------------------- */
  /*   Section 9. Return                                               */
  /* ----------------------------------------------------------------- */

LABEL900:
  /*
  if ( CGRIBEX_Debug )
    {
      double zval;

      Message("Conversion type parameter = %4d", kround);
      Message("Original number = %30.20f", pval);

      zval = decfp2(*kexp, *kmant);

      Message("Converted to      %30.20f", zval);
      Message("Sign = %3d, Exponent = %3d, Mantissa = %12d", isign, iexp, *kmant);
    }
  */
  return;
} /* confp3 */
#include <math.h>

double
decfp2(int kexp, int kmant)
{
  /*

    Purpose:
    --------

    Convert GRIB representation of a floating point
    number to machine representation.

    Input Parameters:
    -----------------

    kexp    - 8 Bit signed exponent.
    kmant   - 24 Bit mantissa.

    Output Parameters:
    ------------------

    Return value   - Floating point number represented
                     by kexp and kmant.

    Method:
    -------

    Floating point number represented as 8 bit exponent
    and 24 bit mantissa in integer values converted to
    machine floating point format.

    Externals:
    ----------

    None.

    Reference:
    ----------

    WMO Manual on Codes re GRIB representation.

    Comments:
    ---------

    Rewritten from DECFP, to conform to programming standards.
    Sign bit on 0 value now ignored, if present.
    If using 32 bit reals, check power of 16 is not so small as to
    cause overflows (underflows!); this causes warning to be given
    on Fujitsus.

    Author:
    -------

    John Hennessy   ECMWF   18.06.91

    Modifications:
    --------------

    Uwe Schulzweida   MPIfM   01/04/2001

     - Convert to C from EMOS library version 130

    Uwe Schulzweida   MPIfM   02/08/2002

     - speed up by factor 2 on NEC SX6
        - replace pow(2.0, -24.0) by constant POW_2_M24
        - replace pow(16.0, (double)(iexp - 64)) by pow16m64tab[iexp]
  */

  /* ----------------------------------------------------------------- */
  /*   Section 1 . Convert value of 0.0. Ignore sign bit.              */
  /* ----------------------------------------------------------------- */

  if ((kexp == 128) || (kexp == 0) || (kexp == 255)) return 0.0;

  /* ----------------------------------------------------------------- */
  /*   Section 2 . Convert other values.                               */
  /* ----------------------------------------------------------------- */

  //  Sign of value.

  int iexp = kexp;
  int isign = (iexp < 128) * 2 - 1;

  iexp -= iexp < 128 ? 0 : 128;

  //  Decode value.

  // double pval = isign * pow(2.0, -24.0) * kmant * pow(16.0, (double)(iexp - 64));

  iexp -= 64;

  double pval = ldexp(1.0, 4 * iexp) * isign * POW_2_M24 * kmant;

  /* ----------------------------------------------------------------- */
  /*   Section 9. Return to calling routine.                           */
  /* ----------------------------------------------------------------- */

  return pval;
}
#include <stdarg.h>
#include <stdint.h>

static void
gribDecodeRefDate(const int *isec1, int *year, int *month, int *day)
{
  int ryear = ISEC1_Year;

  int century = ISEC1_Century;
  if (century < 0) century = -century;
  century -= 1;

  if (century == -255 && ryear == 127) { ryear = 0; }
  else
  {
    // if ( century != 0 )
    {
      if (ryear == 100)
      {
        ryear = 0;
        century += 1;
      }

      if (ryear != 255)
      {
        ryear = century * 100 + ryear;
        if (ISEC1_Century < 0) ryear = -ryear;
      }
      else { ryear = 1; }
    }
  }

  *year = ryear;
  *month = ISEC1_Month;
  *day = ISEC1_Day;
}

int
gribRefDate(const int *isec1)
{
  int ryear, rmonth, rday;
  gribDecodeRefDate(isec1, &ryear, &rmonth, &rday);
  return (int) cdiEncodeDate(ryear, rmonth, rday);
}

static void
gribDecodeRefTime(const int *isec1, int *hour, int *minute, int *second)
{
  *hour = ISEC1_Hour;
  *minute = ISEC1_Minute;
  *second = 0;
}

int
gribRefTime(const int *isec1)
{
  int rhour, rminute, rsecond;
  gribDecodeRefTime(isec1, &rhour, &rminute, &rsecond);
  return cdiEncodeTime(rhour, rminute, rsecond);
}

bool
gribTimeIsFC(const int *isec1)
{
  bool isFC = false;

  const int time_period = (ISEC1_TimeRange == 10) ? (ISEC1_TimePeriod1 << 8) + ISEC1_TimePeriod2 : ISEC1_TimePeriod1;

  if (time_period > 0 && ISEC1_Day > 0) { isFC = (ISEC1_TimeRange == 0 || ISEC1_TimeRange == 10); }

  return isFC;
}

static int
getTimeUnitFactor(int timeUnit)
{
  static bool lprint = true;
  // clang-format off
  switch (timeUnit)
    {
    case ISEC1_TABLE4_MINUTE:    return    60; break;
    case ISEC1_TABLE4_QUARTER:   return   900; break;
    case ISEC1_TABLE4_30MINUTES: return  1800; break;
    case ISEC1_TABLE4_HOUR:      return  3600; break;
    case ISEC1_TABLE4_3HOURS:    return 10800; break;
    case ISEC1_TABLE4_6HOURS:    return 21600; break;
    case ISEC1_TABLE4_12HOURS:   return 43200; break;
    case ISEC1_TABLE4_DAY:       return 86400; break;
    default:
      if (lprint)
        {
          gprintf(__func__, "Time unit %d unsupported", timeUnit);
          lprint = false;
        }
      break;
    }
  // clang-format on

  return 0;
}

void
gribDateTimeX(int *isec1, int *date, int *time, int *startDate, int *startTime)
{
  *startDate = 0;
  *startTime = 0;

  CdiDateTime rDateTime = cdiDateTime_set(gribRefDate(isec1), gribRefTime(isec1));

  int64_t time_period = 0, time_period_x = 0;
  if (ISEC1_TimeRange == 10)
    time_period = (ISEC1_TimePeriod1 << 8) + ISEC1_TimePeriod2;
  else if (ISEC1_TimeRange >= 2 && ISEC1_TimeRange <= 5)
  {
    time_period_x = ISEC1_TimePeriod1;
    time_period = ISEC1_TimePeriod2;
  }
  else if (ISEC1_TimeRange == 0)
    time_period = ISEC1_TimePeriod1;

  if (time_period > 0 && rDateTime.date.day > 0)
  {
    JulianDate julianDate = julianDate_encode(CGRIBEX_grib_calendar, rDateTime);

    const int timeUnitFactor = getTimeUnitFactor(ISEC1_TimeUnit);

    if (time_period_x > 0)
    {
      JulianDate julianDate2 = julianDate_add_seconds(julianDate, timeUnitFactor * time_period_x);
      CdiDateTime sDateTime = julianDate_decode(CGRIBEX_grib_calendar, julianDate2);
      sDateTime.time.second = 0;
      *startDate = (int) cdiDate_get(sDateTime.date);
      *startTime = cdiTime_get(sDateTime.time);
    }

    julianDate = julianDate_add_seconds(julianDate, timeUnitFactor * time_period);
    rDateTime = julianDate_decode(CGRIBEX_grib_calendar, julianDate);
  }

  *date = (int) cdiDate_get(rDateTime.date);
  *time = cdiTime_get(rDateTime.time);
}

void
gribDateTime(int *isec1, int *date, int *time)
{
  int sdate, stime;
  gribDateTimeX(isec1, date, time, &sdate, &stime);
}

void
gprintf(const char *caller, const char *fmt, ...)
{
  va_list args;

  if (grprsm == NULL) Error("GRIBEX initialization missing!");

  va_start(args, fmt);

  fprintf(grprsm, "%-18s : ", caller);
  vfprintf(grprsm, fmt, args);
  fputs("\n", grprsm);

  va_end(args);
}

// clang-format off
void
gribExDP(int *isec0, int *isec1, int *isec2, double *fsec2, int *isec3,
	 double *fsec3, int *isec4, double *fsec4, int klenp, int *kgrib,
	 int kleng, int *kword, const char *hoper, int *kret)
{
  int yfunc = *hoper;

  if ( yfunc == 'C' )
    {
      grib_encode_double(isec0, isec1, isec2, fsec2, isec3,
			 fsec3, isec4, fsec4, klenp, kgrib,
			 kleng, kword, yfunc, kret);
    }
  else if ( yfunc == 'D' || yfunc == 'J' || yfunc == 'R' )
    {
      grib_decode_double(isec0, isec1, isec2, fsec2, isec3,
			 fsec3, isec4, fsec4, klenp, kgrib,
			 kleng, kword, yfunc, kret);
    }
  else if ( yfunc == 'V' )
    {
      fprintf(stderr, "  cgribex: Version is %s\n", cgribexLibraryVersion());
    }
  else
    {
      Error("oper %c unsupported!", yfunc);
      *kret=-9;
    }
}


void
gribExSP(int *isec0, int *isec1, int *isec2, float *fsec2, int *isec3,
	 float *fsec3, int *isec4, float *fsec4, int klenp, int *kgrib,
	 int kleng, int *kword, const char *hoper, int *kret)
{
  int yfunc = *hoper;

  if ( yfunc == 'C' )
    {
      grib_encode_float(isec0, isec1, isec2, fsec2, isec3,
			fsec3, isec4, fsec4, klenp, kgrib,
			kleng, kword, yfunc, kret);
    }
  else if ( yfunc == 'D' || yfunc == 'J' || yfunc == 'R' )
    {
      grib_decode_float(isec0, isec1, isec2, fsec2, isec3,
			fsec3, isec4, fsec4, klenp, kgrib,
			kleng, kword, yfunc, kret);
    }
  else if ( yfunc == 'V' )
    {
      fprintf(stderr, " cgribex: Version is %s\n", cgribexLibraryVersion());
    }
  else
    {
      Error("oper %c unsupported!", yfunc);
      *kret=-9;
    }
}
// clang-format on

int CGRIBEX_Fix_ZSE = 0; /* 1: Fix ZeroShiftError of simple packed spherical harmonics */
int CGRIBEX_Const = 0;   /* 1: Don't pack constant fields on regular grids */
int CGRIBEX_Debug = 0;   /* 1: Debugging */

void
gribSetDebug(int debug)
{
  CGRIBEX_Debug = debug;

  if (CGRIBEX_Debug) Message("debug level %d", debug);
}

void
gribFixZSE(int flag)
{
  CGRIBEX_Fix_ZSE = flag;

  if (CGRIBEX_Debug) Message("Fix ZeroShiftError set to %d", flag);
}

void
gribSetConst(int flag)
{
  CGRIBEX_Const = flag;

  if (CGRIBEX_Debug) Message("Const set to %d", flag);
}

void
gribSetRound(int round)
{
  UNUSED(round);
}

void
gribSetRefDP(double refval)
{
  UNUSED(refval);
}

void
gribSetRefSP(float refval)
{
  gribSetRefDP((double) refval);
}

void
gribSetValueCheck(int vcheck)
{
  UNUSED(vcheck);
}
#include <string.h>
#include <math.h>

void
gribPrintSec0(int *isec0)
{
  /*

    Print the information in the Indicator
    Section (Section 0) of decoded GRIB data.

    Input Parameters:

       isec0 - Array of decoded integers from Section 0


    Converted from EMOS routine GRPRS0.

       Uwe Schulzweida   MPIfM   01/04/2001

  */

  grsdef();

  fprintf(grprsm, " \n");
  fprintf(grprsm, " Section 0 - Indicator Section.       \n");
  fprintf(grprsm, " -------------------------------------\n");
  fprintf(grprsm, " Length of GRIB message (octets).     %9d\n", ISEC0_GRIB_Len);
  fprintf(grprsm, " GRIB Edition Number.                 %9d\n", ISEC0_GRIB_Version);
}

void
gribPrintSec1(int *isec0, int *isec1)
{
  /*

    Print the information in the Product Definition
    Section (Section 1) of decoded GRIB data.

    Input Parameters
       isec0 - Array of decoded integers from Section 0

       isec1 - Array of decoded integers from Section 1

    Comments:

       When decoding data from Experimental Edition or Edition 0,
       routine GRIBEX adds the additional fields available in
       Edition 1.


    Converted from EMOS routine GRPRS1.

       Uwe Schulzweida   MPIfM   01/04/2001

  */

  int iprev, icurr, ioffset;
  int ibit, ierr, iout, iyear;
  int jiloop;
  float value;
  char hversion[9];

  grsdef();

  /*
    -----------------------------------------------------------------
    Section 0 . Print required information.
    -----------------------------------------------------------------
  */

  fprintf(grprsm, " \n");
  fprintf(grprsm, " Section 1 - Product Definition Section.\n");
  fprintf(grprsm, " ---------------------------------------\n");

  fprintf(grprsm, " Code Table 2 Version Number.         %9d\n", isec1[0]);
  fprintf(grprsm, " Originating centre identifier.       %9d\n", isec1[1]);
  fprintf(grprsm, " Model identification.                %9d\n", isec1[2]);
  fprintf(grprsm, " Grid definition.                     %9d\n", isec1[3]);

  ibit = 8;
  prtbin(isec1[4], ibit, &iout, &ierr);
  fprintf(grprsm, " Flag (Code Table 1)                   %8.8d\n", iout);
  fprintf(grprsm, " Parameter identifier (Code Table 2). %9d\n", isec1[5]);

  if (isec1[5] != 127)
  {
    fprintf(grprsm, " Type of level (Code Table 3).        %9d\n", isec1[6]);
    fprintf(grprsm, " Value 1 of level (Code Table 3).     %9d\n", isec1[7]);
    fprintf(grprsm, " Value 2 of level (Code Table 3).     %9d\n", isec1[8]);
  }
  else
  {
    fprintf(grprsm, " Satellite identifier.                %9d\n", isec1[6]);
    fprintf(grprsm, " Spectral band.                       %9d\n", isec1[7]);
  }

  iyear = isec1[9];
  if (iyear != 255)
  {
    int date, time;
    gribDateTime(isec1, &date, &time);
    iyear = date / 10000;
    fprintf(grprsm, " Year of reference time of data.      %9d  (%4d)\n", isec1[9], iyear);
  }
  else { fprintf(grprsm, " Year of reference time of data MISSING  (=255)\n"); }

  fprintf(grprsm, " Month of reference time of data.     %9d\n", isec1[10]);
  fprintf(grprsm, " Day of reference time of data.       %9d\n", isec1[11]);
  fprintf(grprsm, " Hour of reference time of data.      %9d\n", isec1[12]);
  fprintf(grprsm, " Minute of reference time of data.    %9d\n", isec1[13]);
  fprintf(grprsm, " Time unit (Code Table 4).            %9d\n", isec1[14]);
  fprintf(grprsm, " Time range one.                      %9d\n", isec1[15]);
  fprintf(grprsm, " Time range two.                      %9d\n", isec1[16]);
  fprintf(grprsm, " Time range indicator (Code Table 5)  %9d\n", isec1[17]);
  fprintf(grprsm, " Number averaged.                     %9d\n", isec1[18]);
  fprintf(grprsm, " Number missing from average.         %9d\n", isec1[19]);
  /*
     All ECMWF data in GRIB Editions before Edition 1 is decoded
     as 20th century data. Other centres are decoded as missing.
  */
  if (isec0[1] < 1 && isec1[1] != 98)
    fprintf(grprsm, " Century of reference time of data.   Not given\n");
  else
    fprintf(grprsm, " Century of reference time of data.   %9d\n", isec1[20]);

  //   Print sub-centre
  fprintf(grprsm, " Sub-centre identifier.               %9d\n", ISEC1_SubCenterID);

  //   Decimal scale factor
  fprintf(grprsm, " Units decimal scaling factor.        %9d\n", isec1[22]);

  /*
    -----------------------------------------------------------------
    Section 1 . Print local DWD information.
    -----------------------------------------------------------------
  */
  if ((ISEC1_CenterID == 78 || ISEC1_CenterID == 215 || ISEC1_CenterID == 250) && (isec1[36] == 253 || isec1[36] == 254))
  {
    fprintf(grprsm, " DWD local usage identifier.          %9d\n", isec1[36]);
    if (isec1[36] == 253) fprintf(grprsm, " (Database labelling and ensemble forecast)\n");
    if (isec1[36] == 254) fprintf(grprsm, " (Database labelling)\n");

    fprintf(grprsm, " Year of database entry                     %3d  (%4d)\n", isec1[43], 1900 + isec1[43]);
    fprintf(grprsm, " Month of database entry                    %3d\n", isec1[44]);
    fprintf(grprsm, " Day of database entry                      %3d\n", isec1[45]);
    fprintf(grprsm, " Hour of database entry                     %3d\n", isec1[46]);
    fprintf(grprsm, " Minute of database entry                   %3d\n", isec1[47]);
    fprintf(grprsm, " DWD experiment number                %9d\n", isec1[48]);
    fprintf(grprsm, " DWD run type                         %9d\n", isec1[49]);
    if (isec1[36] == 253)
    {
      fprintf(grprsm, " User id                              %9d\n", isec1[50]);
      fprintf(grprsm, " Experiment identifier                %9d\n", isec1[51]);
      fprintf(grprsm, " Ensemble identification type         %9d\n", isec1[52]);
      fprintf(grprsm, " Number of ensemble members           %9d\n", isec1[53]);
      fprintf(grprsm, " Actual number of ensemble member     %9d\n", isec1[54]);
      fprintf(grprsm, " Model version                            %2d.%2.2d\n", isec1[55], isec1[56]);
    }
  }

  /*
    -----------------------------------------------------------------
    Section 2 . Print local ECMWF information.
    -----------------------------------------------------------------
  */
  /*
    Regular MARS labelling, or reformatted Washington EPS products.
  */
  if ((ISEC1_CenterID == 98 && ISEC1_LocalFLag == 1) || (ISEC1_SubCenterID == 98 && ISEC1_LocalFLag == 1)
      || (ISEC1_CenterID == 7 && ISEC1_SubCenterID == 98))
  {
    /*   Parameters common to all definitions.  */

    fprintf(grprsm, " ECMWF local usage identifier.        %9d\n", isec1[36]);
    if (isec1[36] == 1) fprintf(grprsm, " (Mars labelling or ensemble forecast)\n");
    if (isec1[36] == 2) fprintf(grprsm, " (Cluster means and standard deviations)\n");
    if (isec1[36] == 3) fprintf(grprsm, " (Satellite image data)\n");
    if (isec1[36] == 4) fprintf(grprsm, " (Ocean model data)\n");
    if (isec1[36] == 5) fprintf(grprsm, " (Forecast probability data)\n");
    if (isec1[36] == 6) fprintf(grprsm, " (Surface temperature data)\n");
    if (isec1[36] == 7) fprintf(grprsm, " (Sensitivity data)\n");
    if (isec1[36] == 8) fprintf(grprsm, " (ECMWF re-analysis data)\n");
    if (isec1[36] == 9) fprintf(grprsm, " (Singular vectors and ensemble perturbations)\n");
    if (isec1[36] == 10) fprintf(grprsm, " (EPS tubes)\n");
    if (isec1[36] == 11) fprintf(grprsm, " (Supplementary data used by analysis)\n");
    if (isec1[36] == 13) fprintf(grprsm, " (Wave 2D spectra direction and frequency)\n");

    fprintf(grprsm, " Class.                               %9d\n", isec1[37]);
    fprintf(grprsm, " Type.                                %9d\n", isec1[38]);
    fprintf(grprsm, " Stream.                              %9d\n", isec1[39]);
    snprintf(hversion, sizeof(hversion), "%4s", (char *) &isec1[40]);
    hversion[4] = 0;
    fprintf(grprsm, " Version number or Experiment identifier.  %4s\n", hversion);
    /*
      ECMWF Local definition 1.
      (MARS labelling or ensemble forecast data)
    */
    if (isec1[36] == 1)
    {
      fprintf(grprsm, " Forecast number.                     %9d\n", isec1[41]);
      if (isec1[39] != 1090) fprintf(grprsm, " Total number of forecasts.           %9d\n", isec1[42]);

      return;
    }
    /*
      ECMWF Local definition 2.
      (Cluster means and standard deviations)
    */
    if (isec1[36] == 2)
    {
      fprintf(grprsm, " Cluster number.                      %9d\n", isec1[41]);
      fprintf(grprsm, " Total number of clusters.            %9d\n", isec1[42]);
      fprintf(grprsm, " Clustering method.                   %9d\n", isec1[43]);
      fprintf(grprsm, " Start time step when clustering.     %9d\n", isec1[44]);
      fprintf(grprsm, " End time step when clustering.       %9d\n", isec1[45]);
      fprintf(grprsm, " Northern latitude of domain.         %9d\n", isec1[46]);
      fprintf(grprsm, " Western longitude of domain.         %9d\n", isec1[47]);
      fprintf(grprsm, " Southern latitude of domain.         %9d\n", isec1[48]);
      fprintf(grprsm, " Eastern longitude of domain.         %9d\n", isec1[49]);
      fprintf(grprsm, " Operational forecast in cluster      %9d\n", isec1[50]);
      fprintf(grprsm, " Control forecast in cluster          %9d\n", isec1[51]);
      fprintf(grprsm, " Number of forecasts in cluster.      %9d\n", isec1[52]);

      for (int jloop = 0; jloop < isec1[52]; jloop++)
        fprintf(grprsm, " Forecast number                      %9d\n", isec1[jloop + 53]);

      return;
    }
    /*
      ECMWF Local definition 3.
      (Satellite image data)
    */
    if (isec1[36] == 3)
    {
      fprintf(grprsm, " Satellite spectral band.             %9d\n", isec1[41]);
      fprintf(grprsm, " Function code.                       %9d\n", isec1[42]);
      return;
    }
    /*
      ECMWF Local definition 4.
      (Ocean model data)
    */
    if (isec1[36] == 4)
    {
      fprintf(grprsm, " Satellite spectral band.             %9d\n", isec1[41]);
      if (isec1[39] != 1090) fprintf(grprsm, " Function code.                       %9d\n", isec1[42]);
      fprintf(grprsm, " Coordinate structure definition.\n");
      fprintf(grprsm, " Fundamental spatial reference system.%9d\n", isec1[43]);
      fprintf(grprsm, " Fundamental time reference.          %9d\n", isec1[44]);
      fprintf(grprsm, " Space unit flag.                     %9d\n", isec1[45]);
      fprintf(grprsm, " Vertical coordinate definition.      %9d\n", isec1[46]);
      fprintf(grprsm, " Horizontal coordinate definition.    %9d\n", isec1[47]);
      fprintf(grprsm, " Time unit flag.                      %9d\n", isec1[48]);
      fprintf(grprsm, " Time coordinate definition.          %9d\n", isec1[49]);
      fprintf(grprsm, " Position definition.     \n");
      fprintf(grprsm, " Mixed coordinate field flag.         %9d\n", isec1[50]);
      fprintf(grprsm, " Coordinate 1 flag.                   %9d\n", isec1[51]);
      fprintf(grprsm, " Averaging flag.                      %9d\n", isec1[52]);
      fprintf(grprsm, " Position of level 1.                 %9d\n", isec1[53]);
      fprintf(grprsm, " Position of level 2.                 %9d\n", isec1[54]);
      fprintf(grprsm, " Coordinate 2 flag.                   %9d\n", isec1[55]);
      fprintf(grprsm, " Averaging flag.                      %9d\n", isec1[56]);
      fprintf(grprsm, " Position of level 1.                 %9d\n", isec1[57]);
      fprintf(grprsm, " Position of level 2.                 %9d\n", isec1[58]);
      fprintf(grprsm, " Grid Definition.\n");
      fprintf(grprsm, " Coordinate 3 flag (x-axis)           %9d\n", isec1[59]);
      fprintf(grprsm, " Coordinate 4 flag (y-axis)           %9d\n", isec1[60]);
      fprintf(grprsm, " Coordinate 4 of first grid point.    %9d\n", isec1[61]);
      fprintf(grprsm, " Coordinate 3 of first grid point.    %9d\n", isec1[62]);
      fprintf(grprsm, " Coordinate 4 of last grid point.     %9d\n", isec1[63]);
      fprintf(grprsm, " Coordinate 3 of last grid point.     %9d\n", isec1[64]);
      fprintf(grprsm, " i - increment.                       %9d\n", isec1[65]);
      fprintf(grprsm, " j - increment.                       %9d\n", isec1[66]);
      fprintf(grprsm, " Flag for irregular grid coordinates. %9d\n", isec1[67]);
      fprintf(grprsm, " Flag for normal or staggered grids.  %9d\n", isec1[68]);
      fprintf(grprsm, " Further information.\n");
      fprintf(grprsm, " Further information flag.            %9d\n", isec1[69]);
      fprintf(grprsm, " Auxiliary information.\n");
      fprintf(grprsm, " No. entries in horizontal coordinate %9d\n", isec1[70]);
      fprintf(grprsm, " No. entries in mixed coordinate defn.%9d\n", isec1[71]);
      fprintf(grprsm, " No. entries in grid coordinate list. %9d\n", isec1[72]);
      fprintf(grprsm, " No. entries in auxiliary array.      %9d\n", isec1[73]);
      /*
        Horizontal coordinate supplement.
      */
      fprintf(grprsm, " Horizontal coordinate supplement.\n");
      if (isec1[70] == 0) { fprintf(grprsm, "(None).\n"); }
      else
      {
        fprintf(grprsm, "Number of items = %d\n", isec1[70]);
        for (int jloop = 0; jloop < isec1[70]; jloop++) fprintf(grprsm, "         %12d\n", isec1[74 + jloop]);
      }
      /*
        Mixed coordinate definition.
      */
      fprintf(grprsm, " Mixed coordinate definition.\n");
      if (isec1[71] == 0) { fprintf(grprsm, "(None).\n"); }
      else
      {
        fprintf(grprsm, "Number of items = %d\n", isec1[71]);
        ioffset = 74 + isec1[70];
        for (int jloop = 0; jloop < isec1[71]; jloop++) fprintf(grprsm, "         %12d\n", isec1[ioffset + jloop]);
      }
      /*
        Grid coordinate list.
      */
      fprintf(grprsm, " Grid coordinate list. \n");
      if (isec1[72] == 0) { fprintf(grprsm, "(None).\n"); }
      else
      {
        fprintf(grprsm, "Number of items = %d\n", isec1[72]);
        ioffset = 74 + isec1[70] + isec1[71];
        for (int jloop = 0; jloop < isec1[72]; jloop++) fprintf(grprsm, "         %12d\n", isec1[ioffset + jloop]);
      }
      /*
        Auxiliary array.
      */
      fprintf(grprsm, " Auxiliary array.      \n");
      if (isec1[73] == 0) { fprintf(grprsm, "(None).\n"); }
      else
      {
        fprintf(grprsm, "Number of items = %d\n", isec1[73]);
        ioffset = 74 + isec1[70] + isec1[71] + isec1[72];
        for (int jloop = 0; jloop < isec1[73]; jloop++) fprintf(grprsm, "         %12d\n", isec1[ioffset + jloop]);
      }
      /*
        Post-auxiliary array.
      */
      fprintf(grprsm, " Post-auxiliary array. \n");
      ioffset = 74 + isec1[70] + isec1[71] + isec1[72] + isec1[73];
      if (isec1[ioffset] == 0) { fprintf(grprsm, "(None).\n"); }
      else
      {
        fprintf(grprsm, "Number of items = %d\n", isec1[ioffset]);
        for (int jloop = 1; jloop < isec1[ioffset]; jloop++) fprintf(grprsm, "         %12d\n", isec1[ioffset + jloop]);
      }

      return;
    }
    /*
      ECMWF Local definition 5.
      (Forecast probability data)
    */
    if (isec1[36] == 5)
    {
      fprintf(grprsm, " Forecast probability number          %9d\n", isec1[41]);
      fprintf(grprsm, " Total number of forecast probabilities %7d\n", isec1[42]);
      fprintf(grprsm, " Threshold units decimal scale factor %9d\n", isec1[43]);
      fprintf(grprsm, " Threshold indicator(1=lower,2=upper,3=both) %2d\n", isec1[44]);
      if (isec1[44] != 2) fprintf(grprsm, " Lower threshold value                %9d\n", isec1[45]);
      if (isec1[44] != 1) fprintf(grprsm, " Upper threshold value                %9d\n", isec1[46]);
      return;
    }
    /*
      ECMWF Local definition 6.
      (Surface temperature data)
    */
    if (isec1[36] == 6)
    {
      iyear = isec1[43];
      if (iyear > 100)
      {
        if (iyear < 19000000) iyear = iyear + 19000000;
        fprintf(grprsm, " Date of SST field used               %9d\n", iyear);
      }
      else
        fprintf(grprsm, "Date of SST field used               Not given\n");
    }
    if (isec1[44] == 0) fprintf(grprsm, " Type of SST field (= climatology)    %9d\n", isec1[44]);
    if (isec1[44] == 1) fprintf(grprsm, " Type of SST field (= 1/1 degree)     %9d\n", isec1[44]);
    if (isec1[44] == 2) fprintf(grprsm, " Type of SST field (= 2/2 degree)     %9d\n", isec1[44]);

    fprintf(grprsm, " Number of ICE fields used:           %9d\n", isec1[45]);

    for (int jloop = 1; jloop <= isec1[45]; jloop++)
    {
      iyear = isec1[44 + (jloop * 2)];
      if (iyear > 100)
      {
        if (iyear < 19000000) iyear = iyear + 19000000;
        fprintf(grprsm, " Date of ICE field%3d                 %9d\n", jloop, iyear);
        fprintf(grprsm, " Satellite number (ICE field%3d)      %9d\n", jloop, isec1[45 + (jloop * 2)]);
      }
      else
        fprintf(grprsm, "Date of SST field used               Not given\n");
    }
    /*
      ECMWF Local definition 7.
      (Sensitivity data)
    */
    if (isec1[36] == 7)
    {
      if (isec1[38] == 51) fprintf(grprsm, " Forecast number                      %9d\n", isec1[41]);
      if (isec1[38] != 51) fprintf(grprsm, " Iteration number                     %9d\n", isec1[41]);
      if (isec1[38] != 52) fprintf(grprsm, " Total number of diagnostics          %9d\n", isec1[42]);
      if (isec1[38] == 52) fprintf(grprsm, " No.interations in diag. minimisation %9d\n", isec1[42]);
      fprintf(grprsm, " Domain(0=Global,1=Europe,2=N.Hem.,3=S.Hem.) %2d\n", isec1[43]);
      fprintf(grprsm, " Diagnostic number                    %9d\n", isec1[44]);
    }
    /*
      ECMWF Local definition 8.
      (ECMWF re-analysis data)
    */
    if (isec1[36] == 8)
    {
      if ((isec1[39] == 1043) || (isec1[39] == 1070) || (isec1[39] == 1071))
      {
        fprintf(grprsm, " Interval between reference times     %9d\n", isec1[41]);
        for (int jloop = 43; jloop <= 54; jloop++)
        {
          jiloop = jloop + 8;
          fprintf(grprsm, " ERA section 1 octet %2d.              %9d\n", jiloop, isec1[jloop - 1]);
        }
      }
      else
      {
        for (int jloop = 42; jloop <= 54; jloop++)
        {
          jiloop = jloop + 8;
          fprintf(grprsm, " ERA section 1 octet %2d.              %9d\n", jiloop, isec1[jloop - 1]);
        }
      }
      return;
    }

    if (isec1[38] > 4 && isec1[38] < 9)
    {
      fprintf(grprsm, " Simulation number.                   %9d\n", isec1[41]);
      fprintf(grprsm, " Total number of simulations.         %9d\n", isec1[42]);
    }
    /*
      ECMWF Local definition 9.
      (Singular vectors and ensemble perturbations)
    */
    if (isec1[36] == 9)
    {
      if (isec1[38] == 60) fprintf(grprsm, " Perturbed ensemble forecast number   %9d\n", isec1[41]);
      if (isec1[38] == 61) fprintf(grprsm, " Initial state perturbation number    %9d\n", isec1[41]);
      if (isec1[38] == 62) fprintf(grprsm, " Singular vector number               %9d\n", isec1[41]);
      if (isec1[38] == 62)
      {
        fprintf(grprsm, " Number of iterations                 %9d\n", isec1[42]);
        fprintf(grprsm, " Number of singular vectors computed  %9d\n", isec1[43]);
        fprintf(grprsm, " Norm used at initial time            %9d\n", isec1[44]);
        fprintf(grprsm, " Norm used at final time              %9d\n", isec1[45]);
        fprintf(grprsm, " Multiplication factor                %9d\n", isec1[46]);
        fprintf(grprsm, " Latitude of north-west corner        %9d\n", isec1[47]);
        fprintf(grprsm, " Longitude of north-west corner       %9d\n", isec1[48]);
        fprintf(grprsm, " Latitude of south-east corner        %9d\n", isec1[49]);
        fprintf(grprsm, " Longitude of south-east corner       %9d\n", isec1[50]);
        fprintf(grprsm, " Accuracy                             %9d\n", isec1[51]);
        fprintf(grprsm, " Number of singular vectors evolved   %9d\n", isec1[52]);
        fprintf(grprsm, " Ritz number one                      %9d\n", isec1[53]);
        fprintf(grprsm, " Ritz number two                      %9d\n", isec1[54]);
      }
    }
    /*
      ECMWF Local definition 10.
      (EPS tubes)
    */
    if (isec1[36] == 10)
    {
      fprintf(grprsm, " Tube number                          %9d\n", isec1[41]);
      fprintf(grprsm, " Total number of tubes                %9d\n", isec1[42]);
      fprintf(grprsm, " Central cluster definition           %9d\n", isec1[43]);
      fprintf(grprsm, " Parameter                            %9d\n", isec1[44]);
      fprintf(grprsm, " Type of level                        %9d\n", isec1[45]);
      fprintf(grprsm, " Northern latitude of domain of tubing%9d\n", isec1[46]);
      fprintf(grprsm, " Western longitude of domain of tubing%9d\n", isec1[47]);
      fprintf(grprsm, " Southern latitude of domain of tubing%9d\n", isec1[48]);
      fprintf(grprsm, " Eastern longitude of domain of tubing%9d\n", isec1[49]);
      fprintf(grprsm, " Tube number of operational forecast  %9d\n", isec1[50]);
      fprintf(grprsm, " Tube number of control forecast      %9d\n", isec1[51]);
      fprintf(grprsm, " Height/pressure of level             %9d\n", isec1[52]);
      fprintf(grprsm, " Reference step                       %9d\n", isec1[53]);
      fprintf(grprsm, " Radius of central cluster            %9d\n", isec1[54]);
      fprintf(grprsm, " Ensemble standard deviation          %9d\n", isec1[55]);
      fprintf(grprsm, " Dist.of tube extreme to ensemble mean%9d\n", isec1[56]);
      fprintf(grprsm, " Number of forecasts in the tube      %9d\n", isec1[57]);

      fprintf(grprsm, " List of ensemble forecast numbers:\n");
      for (int jloop = 1; jloop <= isec1[57]; jloop++) fprintf(grprsm, "    %9d\n", isec1[57 + jloop]);
    }
    /*
      ECMWF Local definition 11.
      (Supplementary data used by the analysis)
    */
    if (isec1[36] == 11)
    {
      fprintf(grprsm, " Details of analysis which used the supplementary data:\n");
      fprintf(grprsm, "   Class                              %9d\n", isec1[41]);
      fprintf(grprsm, "   Type                               %9d\n", isec1[42]);
      fprintf(grprsm, "   Stream                             %9d\n", isec1[43]);
      /*
      snprintf(hversion, sizeof(hversion), "%8d", isec1[44]);
      fprintf(grprsm, "   Version number/experiment identifier:   %4s\n", &hversion[4]);
      */
      iyear = isec1[45];
      iyear = iyear + ((iyear > 50) ? 1900 : 2000);

      fprintf(grprsm, "   Year                               %9d\n", iyear);
      fprintf(grprsm, "   Month                              %9d\n", isec1[46]);
      fprintf(grprsm, "   Day                                %9d\n", isec1[47]);
      fprintf(grprsm, "   Hour                               %9d\n", isec1[48]);
      fprintf(grprsm, "   Minute                             %9d\n", isec1[49]);
      fprintf(grprsm, "   Century                            %9d\n", isec1[50]);
      fprintf(grprsm, "   Originating centre                 %9d\n", isec1[51]);
      fprintf(grprsm, "   Sub-centre                         %9d\n", isec1[52]);
    }
    /*
      ECMWF Local definition 12.
    */
    if (isec1[36] == 12)
    {
      fprintf(grprsm, " (Mean, average, etc)\n");
      fprintf(grprsm, " Start date of the period              %8d\n", isec1[41]);
      fprintf(grprsm, " Start time of the period                  %4.4d\n", isec1[42]);
      fprintf(grprsm, " Finish date of the period             %8d\n", isec1[43]);
      fprintf(grprsm, " Finish time of the period                 %4.4d\n", isec1[44]);
      fprintf(grprsm, " Verifying date of the period          %8d\n", isec1[45]);
      fprintf(grprsm, " Verifying time of the period              %4.4d\n", isec1[46]);
      fprintf(grprsm, " Code showing method                   %8d\n", isec1[47]);
      fprintf(grprsm, " Number of different time intervals used  %5d\n", isec1[48]);
      fprintf(grprsm, " List of different time intervals used:\n");
      iprev = isec1[49];
      unsigned icount = 0;
      for (int jloop = 1; jloop <= isec1[48]; jloop++)
      {
        icurr = isec1[48 + jloop];
        if (icurr != iprev)
        {
          if (icount == 1) fprintf(grprsm, "  - interval %5.4d used       once\n", iprev);
          if (icount == 2) fprintf(grprsm, "  - interval %5.4d used       twice\n", iprev);
          if (icount > 2) fprintf(grprsm, "  - interval %5.4d used %5u times\n", iprev, icount);
          iprev = icurr;
          icount = 1;
        }
        else
          icount = icount + 1;
      }
      if (icount == 1) fprintf(grprsm, "  - interval %5.4d used       once\n", iprev);
      if (icount == 2) fprintf(grprsm, "  - interval %5.4d used       twice\n", iprev);
      if (icount > 2) fprintf(grprsm, "  - interval %5.4d used %5u times\n", iprev, icount);
    }
    /*
      ECMWF Local definition 13.
      (Wave 2D spectra direction and frequency)
    */
    if (isec1[36] == 13)
    {
      fprintf(grprsm, " Direction number                     %9d\n", isec1[43]);
      fprintf(grprsm, " Frequency number                     %9d\n", isec1[44]);
      fprintf(grprsm, " Total number of directions           %9d\n", isec1[45]);
      fprintf(grprsm, " Total number of frequencies          %9d\n", isec1[46]);
      fprintf(grprsm, " Scale factor applied to directions   %9d\n", isec1[47]);
      fprintf(grprsm, " Scale factor applied to frequencies  %9d\n", isec1[48]);
      fprintf(grprsm, " List of directions:\n");
      for (int jloop = 1; jloop <= isec1[45]; jloop++)
      {
        value = (float) (isec1[48 + jloop]) / (float) (isec1[47]);
        if (isec1[43] == jloop)
          fprintf(grprsm, " %2.2d:%15.7f   <-- this field value\n", jloop, value);
        else
          fprintf(grprsm, "%2.2d:%15.7f\n", jloop, value);
      }
      fprintf(grprsm, " List of frequencies:\n");
      for (int jloop = 1; jloop <= isec1[46]; jloop++)
      {
        value = (float) (isec1[48 + isec1[45] + jloop]) / (float) (isec1[48]);
        if (isec1[44] == jloop)
          fprintf(grprsm, " %2.2d:%15.7f   <-- this field value\n", jloop, value);
        else
          fprintf(grprsm, "%2.2d:%15.7f\n", jloop, value);

        if (isec1[49 + isec1[45] + isec1[46]] != 0)
        {
          fprintf(grprsm, " System number (65535 = missing)      %9d\n", isec1[49 + isec1[45] + isec1[46]]);
          fprintf(grprsm, " Method number (65535 = missing)      %9d\n", isec1[50 + isec1[45] + isec1[46]]);
        }
      }
      /*
        ECMWF Local definition 14.
        (Brightness temperature)
      */
      if (isec1[36] == 14)
      {
        fprintf(grprsm, " Channel number                       %9d\n", isec1[43]);
        fprintf(grprsm, " Scale factor applied to frequencies  %9d\n", isec1[44]);
        fprintf(grprsm, " Total number of frequencies          %9d\n", isec1[45]);
        fprintf(grprsm, " List of frequencies:\n");
        for (int jloop = 1; jloop <= isec1[45]; jloop++)
        {
          value = (float) (isec1[45 + jloop]) / (float) (isec1[44]);
          if (isec1[43] == jloop)
            fprintf(grprsm, " %3d:%15.9f   <-- this channel\n", jloop, value);
          else
            fprintf(grprsm, " %3d:%15.9f\n", jloop, value);
        }
      }
      /*
        ECMWF Local definition 15.
        (Ocean ensemble seasonal forecast)
      */
      if (isec1[36] == 15)
      {
        fprintf(grprsm, " Ensemble member number               %9d\n", isec1[41]);
        fprintf(grprsm, " System number                        %9d\n", isec1[42]);
        fprintf(grprsm, " Method number                        %9d\n", isec1[43]);
      }
      /*
        ECMWF Local definition 16.
        (Seasonal forecast monthly mean atmosphere data)
      */
      if (isec1[36] == 16)
      {
        fprintf(grprsm, " Ensemble member number               %9d\n", isec1[41]);
        fprintf(grprsm, " System number                        %9d\n", isec1[43]);
        fprintf(grprsm, " Method number                        %9d\n", isec1[44]);
        fprintf(grprsm, " Verifying month                      %9d\n", isec1[45]);
        fprintf(grprsm, " Averaging period                     %9d\n", isec1[46]);
      }
      /*
        ECMWF Local definition 17.
        (Sst or sea-ice used by analysis)
      */
      if (isec1[36] == 17)
      {
        iyear = isec1[43];
        if (iyear > 100)
        {
          if (iyear < 19000000) iyear = iyear + 19000000;
          fprintf(grprsm, " Date of sst/ice field used           %9d\n", iyear);
        }
        else
          fprintf(grprsm, " Date of sst/ice field used           Not given\n");

        if (isec1[44] == 0) fprintf(grprsm, " Type of sst/ice field (= climatology)%9d\n", isec1[44]);
        if (isec1[44] == 1) fprintf(grprsm, " Type of sst/ice field (= 1/1 degree) %9d\n", isec1[44]);
        if (isec1[44] == 2) fprintf(grprsm, " Type of sst/ice field (= 2/2 degree) %9d\n", isec1[44]);

        fprintf(grprsm, " Number of ICE fields used:           %9d\n", isec1[45]);

        for (int jloop = 1; jloop < isec1[45]; jloop++)
        {
          iyear = isec1[44 + (jloop * 2)];
          if (iyear > 100)
          {
            if (iyear < 19000000) iyear = iyear + 19000000;
            fprintf(grprsm, " Date of ICE field%3d                 %9d\n", jloop, iyear);
            fprintf(grprsm, " Satellite number (ICE field%3d)      %9d\n", jloop, isec1[45 + (jloop * 2)]);
          }
          else
            fprintf(grprsm, "Date of sst/ice field used           Not given\n");
        }
      }
    }
  }
  /*
    -----------------------------------------------------------------
    Section 3 . Print Washington ensemble product information.
    -----------------------------------------------------------------
  */
  /*
    Washington EPS products (but not reformatted Washington EPS
    products.
  */
  if ((isec1[1] == 7 && isec1[23] == 1) && (!(ISEC1_SubCenterID == 98)))
  { /*   CALL KWPRS1 (iSEC0,iSEC1)*/
  }
  /*
    -----------------------------------------------------------------
    Section 4 . Print local MPIM information.
    -----------------------------------------------------------------
  */
  if (isec1[1] == 252 && isec1[36] == 1)
  {
    fprintf(grprsm, " MPIM local usage identifier.         %9d\n", isec1[36]);
    fprintf(grprsm, " Type of ensemble forecast            %9d\n", isec1[37]);
    fprintf(grprsm, " Individual ensemble member           %9d\n", isec1[38]);
    fprintf(grprsm, " Number of forecasts in ensemble      %9d\n", isec1[39]);
  }
}

static void
printQuasi(int *isec2)
{
  /*

    Print the qusai-regular information in the Grid Description
    Section (Section 2) of decoded GRIB data.

    Input Parameters:

       isec2 - Array of decoded integers from Section 2.

    Comments:

       Only data representation types catered for are Gaussian
       grid, latitude/longitude grid, Spherical Harmonics,
       Polar stereographic and Space view perspective.

    Converted from EMOS routine PTQUASI.

       Uwe Schulzweida   MPIfM   01/04/2001

  */

  char yout[64];

  /*
    -----------------------------------------------------------------
    Section 1. Print quasi-grid data.
    -----------------------------------------------------------------
  */
  // See if scanning is north->south or south->north
  fprintf(grprsm, "  Number of points along a parallel varies.\n");

  int ntos = (fmod((double) isec2[10], 128.) < 64);
  fprintf(grprsm, "  Number of points.   Parallel. %s\n", ntos ? "(North to South)" : "(South to North)");

  // Display number of points for each latitude
  int latcnt = isec2[2];
  int nextlat = 0;
  memset(yout, ' ', (size_t) 11);

  for (int j = 0; j < latcnt; ++j)
  {
    nextlat = nextlat + 1;
    snprintf(yout, sizeof(yout), "%4d", nextlat);

    // Finished?
    if (nextlat > latcnt) break;
    if (nextlat == latcnt)
    {
      fprintf(grprsm, " %5d                %-12s\n", isec2[nextlat + 21], yout);
      break;
    }
    // Look for neighbouring latitudes with same number of points
    unsigned nrepeat = 0;

  LABEL110:
    // If neighbouring latitudes have same number of points increase the repeat count.
    if (isec2[nextlat + 21 + 1] == isec2[nextlat + 21])
    {
      nrepeat = nrepeat + 1;
      nextlat = nextlat + 1;
      if (nextlat < latcnt) goto LABEL110;
    }
    // Display neighbouring latitudes with same number of points as 'nn to mm'.
    if (nrepeat >= 1) snprintf(yout + 4, sizeof(yout) - 4, "to %5d", nextlat);
    fprintf(grprsm, " %5d                %-12s\n", isec2[nextlat + 21], yout);
    memset(yout, ' ', (size_t) 11);
  }
}

void
gribPrintSec2DP(int *isec0, int *isec2, double *fsec2)
{
  /*

    Print the information in the Grid Description
    Section (Section 2) of decoded GRIB data.

    Input Parameters:

       isec0  - Array of decoded integers from Section 0

       isec2  - Array of decoded integers from Section 2

       fsec2  - Array of decoded floats from Section 2

    Comments:

       Only data representation types catered for are Gaussian
       grid, latitude/longitude grid, Spherical Harmonics,
       Polar stereographic and Space view perspective.


    Converted from EMOS routine GRPRS2.

       Uwe Schulzweida   MPIfM   01/04/2001

  */

  int ibit, iedit, ierr, iout, iresol;

  grsdef();
  /*
    -----------------------------------------------------------------
    Section 1 . Print GRIB Edition number.
    -----------------------------------------------------------------
  */
  iedit = isec0[1];
  fprintf(grprsm, " \n");
  fprintf(grprsm, " Section 2 - Grid Description Section.\n");
  fprintf(grprsm, " -------------------------------------\n");
  /*
    -----------------------------------------------------------------
    Section 2 . Print spherical harmonic data.
    -----------------------------------------------------------------
  */
  if (isec2[0] == 50 || isec2[0] == 60 || isec2[0] == 70 || isec2[0] == 80)
  {
    fprintf(grprsm, " Data represent type = spectral     (Table 6) %9d\n", isec2[0]);
    fprintf(grprsm, " J - Pentagonal resolution parameter.         %9d\n", isec2[1]);
    fprintf(grprsm, " K - Pentagonal resolution parameter.         %9d\n", isec2[2]);
    fprintf(grprsm, " M - Pentagonal resolution parameter.         %9d\n", isec2[3]);
    fprintf(grprsm, " Representation type (Table 9)                %9d\n", isec2[4]);
    fprintf(grprsm, " Representation mode (Table 10).              %9d\n", isec2[5]);
    for (int i = 7; i <= 11; ++i) fprintf(grprsm, " Not used.                                    %9d\n", isec2[i - 1]);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Section 3 . Print Gaussian grid data.
    -----------------------------------------------------------------
  */
  if (isec2[0] == 4 || isec2[0] == 14 || isec2[0] == 24 || isec2[0] == 34)
  {
    fprintf(grprsm, " (Southern latitudes and Western longitudes are negative.)\n");
    fprintf(grprsm, " Data represent type = gaussian     (Table 6) %9d\n", isec2[0]);
    /*
      Quasi-regular grids introduced in Edition 1.
    */
    if (isec2[16] == 0 || iedit < 1)
      fprintf(grprsm, " Number of points along a parallel.           %9d\n", isec2[1]);
    else
      printQuasi(isec2);

    fprintf(grprsm, " Number of points along a meridian.           %9d\n", isec2[2]);
    fprintf(grprsm, " Latitude of first grid point.                %9d\n", isec2[3]);
    fprintf(grprsm, " Longitude of first grid point.               %9d\n", isec2[4]);

    ibit = 8;
    iresol = isec2[5] + isec2[17] + isec2[18];
    prtbin(iresol, ibit, &iout, &ierr);

    fprintf(grprsm, " Resolution and components flag.               %8.8d\n", iout);
    fprintf(grprsm, " Latitude of last grid point.                 %9d\n", isec2[6]);
    fprintf(grprsm, " Longitude of last grid point.                %9d\n", isec2[7]);
    /*
      Print increment if given.
    */
    if (isec2[5] == 128)
      fprintf(grprsm, " i direction (East-West) increment.           %9d\n", isec2[8]);
    else
      fprintf(grprsm, " i direction (East-West) increment            Not given\n");

    fprintf(grprsm, " Number of parallels between pole and equator.%9d\n", isec2[9]);

    ibit = 8;
    prtbin(isec2[10], ibit, &iout, &ierr);

    fprintf(grprsm, " Scanning mode flags (Code Table 8)            %8.8d\n", iout);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Section 4 . Print Latitude / longitude grid data.
    -----------------------------------------------------------------
  */
  if (isec2[0] == 0 || isec2[0] == 10 || isec2[0] == 20 || isec2[0] == 30)
  {
    fprintf(grprsm, " (Southern latitudes and Western longitudes are negative.)\n");
    fprintf(grprsm, " Data represent type = lat/long     (Table 6) %9d\n", isec2[0]);
    /*
      Quasi-regular lat/long grids also possible.
    */
    if (isec2[16] == 0)
      fprintf(grprsm, " Number of points along a parallel.           %9d\n", isec2[1]);
    else
      printQuasi(isec2);

    fprintf(grprsm, " Number of points along a meridian.           %9d\n", isec2[2]);
    fprintf(grprsm, " Latitude of first grid point.                %9d\n", isec2[3]);
    fprintf(grprsm, " Longitude of first grid point.               %9d\n", isec2[4]);

    ibit = 8;
    iresol = isec2[5] + isec2[17] + isec2[18];
    prtbin(iresol, ibit, &iout, &ierr);

    fprintf(grprsm, " Resolution and components flag.               %8.8d\n", iout);
    fprintf(grprsm, " Latitude of last grid point.                 %9d\n", isec2[6]);
    fprintf(grprsm, " Longitude of last grid point.                %9d\n", isec2[7]);
    /*
      Print increment if given.
    */
    if (isec2[8] < 0)
      fprintf(grprsm, " i direction (East-West) increment            Not given\n");
    else
      fprintf(grprsm, " i direction (East-West) increment.           %9d\n", isec2[8]);

    if (isec2[9] < 0)
      fprintf(grprsm, " j direction (North-South) increment          Not given\n");
    else
      fprintf(grprsm, " j direction (North-South) increment.         %9d\n", isec2[9]);

    ibit = 8;
    prtbin(isec2[10], ibit, &iout, &ierr);

    fprintf(grprsm, " Scanning mode flags (Code Table 8)            %8.8d\n", iout);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Section 5 . Print polar stereographic data.
    -----------------------------------------------------------------
  */
  if (isec2[0] == 5)
  {
    fprintf(grprsm, " (Southern latitudes and Western longitudes are negative.)\n");
    fprintf(grprsm, " Data represent type = polar stereo (Table 6) %9d\n", isec2[0]);
    fprintf(grprsm, " Number of points along X axis.               %9d\n", isec2[1]);
    fprintf(grprsm, " Number of points along Y axis.               %9d\n", isec2[2]);
    fprintf(grprsm, " Latitude of first grid point.                %9d\n", isec2[3]);
    fprintf(grprsm, " Longitude of first grid point.               %9d\n", isec2[4]);
    ibit = 8;
    iresol = isec2[17] + isec2[18];
    prtbin(iresol, ibit, &iout, &ierr);
    fprintf(grprsm, " Resolution and components flag.               %8.8d\n", iout);
    fprintf(grprsm, " Orientation of the grid.                     %9d\n", isec2[6]);
    fprintf(grprsm, " X direction increment.                       %9d\n", isec2[8]);
    fprintf(grprsm, " Y direction increment.                       %9d\n", isec2[9]);
    ibit = 8;
    prtbin(isec2[10], ibit, &iout, &ierr);
    fprintf(grprsm, " Scanning mode flags (Code Table 8)            %8.8d\n", iout);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    fprintf(grprsm, " Projection centre flag.                      %9d\n", isec2[12]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Section 6 . Print Lambert conformal data.
    -----------------------------------------------------------------
  */
  if (isec2[0] == 3)
  {
    fprintf(grprsm, " (Southern latitudes and Western longitudes are negative.)\n");
    fprintf(grprsm, " Data represent type = Lambert      (Table 6) %9d\n", isec2[0]);
    fprintf(grprsm, " Number of points along X axis.               %9d\n", isec2[1]);
    fprintf(grprsm, " Number of points along Y axis.               %9d\n", isec2[2]);
    fprintf(grprsm, " Latitude of first grid point.                %9d\n", isec2[3]);
    fprintf(grprsm, " Longitude of first grid point.               %9d\n", isec2[4]);
    ibit = 8;
    iresol = isec2[17] + isec2[18] + isec2[5];
    prtbin(iresol, ibit, &iout, &ierr);
    fprintf(grprsm, " Resolution and components flag.               %8.8d\n", iout);
    fprintf(grprsm, " Orientation of the grid.                     %9d\n", isec2[6]);
    fprintf(grprsm, " X direction increment.                       %9d\n", isec2[8]);
    fprintf(grprsm, " Y direction increment.                       %9d\n", isec2[9]);
    ibit = 8;
    prtbin(isec2[10], ibit, &iout, &ierr);
    fprintf(grprsm, " Scanning mode flags (Code Table 8)            %8.8d\n", iout);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    fprintf(grprsm, " Projection centre flag.                      %9d\n", isec2[12]);
    fprintf(grprsm, " Latitude intersection 1 - Latin 1 -.         %9d\n", isec2[13]);
    fprintf(grprsm, " Latitude intersection 2 - Latin 2 -.         %9d\n", isec2[14]);
    fprintf(grprsm, " Latitude of Southern Pole.                   %9d\n", isec2[19]);
    fprintf(grprsm, " Longitude of Southern Pole.                  %9d\n", isec2[20]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Section 7 . Print space view perspective or orthographic data.
    -----------------------------------------------------------------
  */
  if (isec2[0] == 90)
  {
    fprintf(grprsm, " (Southern latitudes and Western longitudes are negative.)\n");
    fprintf(grprsm, " Data represent type = space/ortho  (Table 6) %9d\n", isec2[0]);
    fprintf(grprsm, " Number of points along X axis.               %9d\n", isec2[1]);
    fprintf(grprsm, " Number of points along Y axis.               %9d\n", isec2[2]);
    fprintf(grprsm, " Latitude of sub-satellite point.             %9d\n", isec2[3]);
    fprintf(grprsm, " Longitude of sub-satellite point.            %9d\n", isec2[4]);
    // iresol = isec2[17] + isec2[18];
    fprintf(grprsm, " Diameter of the earth in x direction.        %9d\n", isec2[6]);
    fprintf(grprsm, " Y coordinate of sub-satellite point.         %9d\n", isec2[9]);
    ibit = 8;
    prtbin(isec2[10], ibit, &iout, &ierr);
    fprintf(grprsm, " Scanning mode flags (Code Table 8)            %8.8d\n", iout);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    fprintf(grprsm, " Orientation of the grid.                     %9d\n", isec2[6]);
    fprintf(grprsm, " Altitude of the camera.                      %9d\n", isec2[13]);
    fprintf(grprsm, " Y coordinate of origin of sector image.      %9d\n", isec2[14]);
    fprintf(grprsm, " X coordinate of origin of sector image.      %9d\n", isec2[15]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Section 7.5 . Print ocean data
    -----------------------------------------------------------------
  */
  /*
  if ( isec2[0] == 192 && ISEC1_CenterID == 98 )
    {
      fprintf(grprsm, " Data represent type = ECMWF ocean  (Table 6) %9d\n", isec2[0]);
      if ( isec2[1] ==  32767 )
        fprintf(grprsm, " Number of points along the first axis.       Not used\n");
      else
        fprintf(grprsm, " Number of points along the first axis.       %9d\n", isec2[1]);

      if ( isec2[2] ==  32767 )
        fprintf(grprsm, " Number of points along the second axis.      Not used\n");
      else
        fprintf(grprsm, " Number of points along the second axis.      %9d\n", isec2[2]);

      ibit = 8;
      prtbin(isec2[10], ibit, &iout, &ierr);
      fprintf(grprsm, " Scanning mode flags (Code Table 8)            %8.8d\n", iout);
      goto LABEL800;
    }
    */
  /*
    -----------------------------------------------------------------
    Section 7.6 . Print triangular data
    -----------------------------------------------------------------
  */
  if (isec2[0] == 192 /* && ISEC1_CenterID == 78 */)
  {
    fprintf(grprsm, " Data represent type = triangular   (Table 6) %9d\n", isec2[0]);
    fprintf(grprsm, " Number of factor 2 in factorisation of Ni.   %9d\n", isec2[1]);
    fprintf(grprsm, " Number of factor 3 in factorisation of Ni.   %9d\n", isec2[2]);
    fprintf(grprsm, " Number of diamonds (Nd).                     %9d\n", isec2[3]);
    fprintf(grprsm, " Number of triangular subdivisions of the\n");
    fprintf(grprsm, "           icosahedron (Ni).                  %9d\n", isec2[4]);
    fprintf(grprsm, " Flag for orientation of diamonds (Table A).  %9d\n", isec2[5]);
    fprintf(grprsm, " Latitude of pole point.                      %9d\n", isec2[6]);
    fprintf(grprsm, " Longitude of pole point.                     %9d\n", isec2[7]);
    fprintf(grprsm, " Longitude of the first diamond.              %9d\n", isec2[8]);
    fprintf(grprsm, " Flag for storage sequence (Table B).         %9d\n", isec2[9]);
    fprintf(grprsm, " Number of vertical coordinate parameters.    %9d\n", isec2[11]);
    goto LABEL800;
  }
  /*
    -----------------------------------------------------------------
    Drop through to here => representation type not catered for.
    -----------------------------------------------------------------
  */
  fprintf(grprsm, "GRPRS2 :Data representation type not catered for -%d\n", isec2[0]);

  goto LABEL900;
  /*
    -----------------------------------------------------------------
    Section 8 . Print vertical coordinate parameters,
                rotated grid information,
                stretched grid information, if any.
    -----------------------------------------------------------------
  */
LABEL800:;
  /*
    Vertical coordinate parameters ...
  */
  if (isec2[11] != 0)
  {
    fprintf(grprsm, " \n");
    fprintf(grprsm, " Vertical Coordinate Parameters.\n");
    fprintf(grprsm, " -------------------------------\n");
    for (int i = 10; i < isec2[11] + 10; ++i) fprintf(grprsm, "    %20.12f\n", fsec2[i]);
  }
  /*
    Rotated and stretched grids introduced in Edition 1.
  */
  if (iedit < 1) goto LABEL900;
  /*
    Rotated grid information ...
  */
  if (isec2[0] == 10 || isec2[0] == 30 || isec2[0] == 14 || isec2[0] == 34 || isec2[0] == 60 || isec2[0] == 80 || isec2[0] == 30)
  {
    fprintf(grprsm, " \n");
    fprintf(grprsm, " Latitude of southern pole of rotation.       %9d\n", isec2[12]);
    fprintf(grprsm, " Longitude of southern pole of rotation.      %9d\n", isec2[13]);
    fprintf(grprsm, " Angle of rotation.                     %20.10f\n", fsec2[0]);
  }
  /*
    Stretched grid information ...
  */
  if (isec2[0] == 20 || isec2[0] == 30 || isec2[0] == 24 || isec2[0] == 34 || isec2[0] == 70 || isec2[0] == 80)
  {
    fprintf(grprsm, " \n");
    fprintf(grprsm, " Latitude of pole of stretching.              %9d\n", isec2[14]);
    fprintf(grprsm, " Longitude of pole of stretching.             %9d\n", isec2[15]);
    fprintf(grprsm, " Stretching factor.                     %20.10f\n", fsec2[1]);
  }

LABEL900:;

  return;
}

void
gribPrintSec2SP(int *isec0, int *isec2, float *fsec2sp)
{
  int inum = 10 + isec2[11];

  double *fsec2 = (double *) Malloc((size_t) inum * sizeof(double));
  if (fsec2 == NULL) SysError("No Memory!");

  for (int j = 0; j < inum; ++j) fsec2[j] = fsec2sp[j];

  gribPrintSec2DP(isec0, isec2, fsec2);

  Free(fsec2);
}

void
gribPrintSec3DP(int *isec0, int *isec3, double *fsec3)
{
  /*

    Print the information in the Bit-Map Section
    (Section 3) of decoded GRIB data.

    Input Parameters:

       isec0  - Array of decoded integers from Section 0

       isec3  - Array of decoded integers from Section 3

       fsec3  - Array of decoded floats from Section 3


    Converted from EMOS routine GRPRS3.

       Uwe Schulzweida   MPIfM   01/04/2001

  */

  UNUSED(isec0);

  grsdef();

  fprintf(grprsm, " \n");
  fprintf(grprsm, " Section 3 - Bit-map Section.\n");
  fprintf(grprsm, " -------------------------------------\n");

  if (isec3[0] != 0)
    fprintf(grprsm, " Predetermined bit-map number.                %9d\n", isec3[0]);
  else
    fprintf(grprsm, " No predetermined bit-map.\n");

  fprintf(grprsm, " Missing data value for integer data.    %14d\n", isec3[1]);

  fprintf(grprsm, " Missing data value for real data. %20.6g\n", fsec3[1]);
}

void
gribPrintSec3SP(int *isec0, int *isec3, float *fsec3sp)
{
  double fsec3[2];

  fsec3[0] = fsec3sp[0];
  fsec3[1] = fsec3sp[1];

  gribPrintSec3DP(isec0, isec3, fsec3);
}

void
gribPrintSec4DP(int *isec0, int *isec4, double *fsec4)
{
  /*

    Print the information in the Binary Data Section
    (Section 4) of decoded GRIB data.

    Input Parameters:

       isec0  - Array of decoded integers from Section 0

       isec4  - Array of decoded integers from Section 4

       fsec4  - Array of decoded floats from Section 4


    Converted from EMOS routine GRPRS4.

       Uwe Schulzweida   MPIfM   01/04/2001

  */
  int inum;

  UNUSED(isec0);

  grsdef();

  /*
    -----------------------------------------------------------------
    Section 1 . Print integer information from isec4.
    -----------------------------------------------------------------
  */
  fprintf(grprsm, " \n");
  fprintf(grprsm, " Section 4 - Binary Data  Section.\n");
  fprintf(grprsm, " -------------------------------------\n");

  fprintf(grprsm, " Number of data values coded/decoded.         %9d\n", isec4[0]);
  fprintf(grprsm, " Number of bits per data value.               %9d\n", isec4[1]);
  fprintf(grprsm, " Type of data       (0=grid pt, 128=spectral).%9d\n", isec4[2]);
  fprintf(grprsm, " Type of packing    (0=simple, 64=complex).   %9d\n", isec4[3]);
  fprintf(grprsm, " Type of data       (0=float, 32=integer).    %9d\n", isec4[4]);
  fprintf(grprsm, " Additional flags   (0=none, 16=present).     %9d\n", isec4[5]);
  fprintf(grprsm, " Reserved.                                    %9d\n", isec4[6]);
  fprintf(grprsm, " Number of values   (0=single, 64=matrix).    %9d\n", isec4[7]);
  fprintf(grprsm, " Secondary bit-maps (0=none, 32=present).     %9d\n", isec4[8]);
  fprintf(grprsm, " Values width       (0=constant, 16=variable).%9d\n", isec4[9]);
  /*
    If complex packing ..
  */
  if (isec4[3] == 64)
  {
    if (isec4[2] == 128)
    {
      fprintf(grprsm, " Byte offset of start of packed data (N).     %9d\n", isec4[15]);
      fprintf(grprsm, " Power (P * 1000).                            %9d\n", isec4[16]);
      fprintf(grprsm, " Pentagonal resolution parameter J for subset.%9d\n", isec4[17]);
      fprintf(grprsm, " Pentagonal resolution parameter K for subset.%9d\n", isec4[18]);
      fprintf(grprsm, " Pentagonal resolution parameter M for subset.%9d\n", isec4[19]);
    }
    else
    {
      fprintf(grprsm, " Bits number of 2nd order values    (none=>0).%9d\n", isec4[10]);
      fprintf(grprsm, " General extend. 2-order packing (0=no,8=yes).%9d\n", isec4[11]);
      fprintf(grprsm, " Boustrophedonic ordering        (0=no,4=yes).%9d\n", isec4[12]);
      fprintf(grprsm, " Spatial differencing order          (0=none).%9d\n", isec4[13] + isec4[14]);
    }
  }
  /*
    Number of non-missing values
  */
  if (isec4[20] != 0) fprintf(grprsm, " Number of non-missing values                 %9d\n", isec4[20]);
  /*
    Information on matrix of values , if present.
  */
  if (isec4[7] == 64)
  {
    fprintf(grprsm, " First dimension (rows) of each matrix.       %9d\n", isec4[49]);
    fprintf(grprsm, " Second dimension (columns) of each matrix.   %9d\n", isec4[50]);
    fprintf(grprsm, " First dimension coordinate values definition.%9d\n", isec4[51]);
    fprintf(grprsm, " (Code Table 12)\n");
    fprintf(grprsm, " NC1 - Number of coefficients for 1st dimension.%7d\n", isec4[52]);
    fprintf(grprsm, " Second dimension coordinate values definition.%8d\n", isec4[53]);
    fprintf(grprsm, " (Code Table 12)\n");
    fprintf(grprsm, " NC2 - Number of coefficients for 2nd dimension.%7d\n", isec4[54]);
    fprintf(grprsm, " 1st dimension physical signifance (Table 13). %8d\n", isec4[55]);
    fprintf(grprsm, " 2nd dimension physical signifance (Table 13).%8d\n", isec4[56]);
  }
  /*
    -----------------------------------------------------------------
    Section 2. Print values from fsec4.
    -----------------------------------------------------------------
  */

  inum = isec4[0];
  if (inum < 0) inum = -inum;
  if (inum > 20) inum = 20;
  /*
    Print first inum values.
  */
  fprintf(grprsm, " \n");
  fprintf(grprsm, " First %4d data values.\n", inum);

  if (isec4[4] == 0)
  {
    /*
      Print real values ...
    */
    for (int j = 0; j < inum; ++j)
    {
      if (fabs(fsec4[j]) > 0)
      {
        if (fabs(fsec4[j]) >= 0.1 && fabs(fsec4[j]) <= 1.e8)
          fprintf(grprsm, " %#16.8G    \n", fsec4[j]);
        else
          fprintf(grprsm, " %#20.8E\n", fsec4[j]);
      }
      else
        fprintf(grprsm, " %#16.0f    \n", fabs(fsec4[j]));
    }
  }
  else
  {
    /*
      Print integer values ...
    */
    fprintf(grprsm, " Print of integer values not supported\n");
    /*
      CALL SETPAR(IBIT,IDUM,IDUM)
      DO 212 J=1,INUM
         INSPT = 0
         CALL INXBIT(IVALUE,1,INSPT,FSEC4(J),1,IBIT,IBIT,'C',IRET)
         WRITE (*,9033) IVALUE
9033 FORMAT(' ',I15)
212   CONTINUE
    ENDIF
    */
  }
}

void
gribPrintSec4SP(int *isec0, int *isec4, float *fsec4sp)
{
  double fsec4[20];

  int inum = isec4[0];
  if (inum < 0) inum = -inum;
  if (inum > 20) inum = 20;

  for (int j = 0; j < inum; ++j) fsec4[j] = fsec4sp[j];

  gribPrintSec4DP(isec0, isec4, fsec4);
}

void
gribPrintSec4Wave(int *isec4)
{
  /*

    Print the wave coordinate information in the Binary Data
    Section (Section 4) of decoded GRIB data.

    Input Parameters:

       isec4 - Array of decoded integers from Section 4

    Comments:

       Wave coordinate information held in isec4 are 32-bit floats,
       hence the PTEMP and NTEMP used for printing are 4-byte variables.


    Converted from EMOS routine GRPRS4W.

       Uwe Schulzweida   MPIfM   01/04/2001

  */
  int ntemp[100];
  float *ptemp;

  grsdef();

  /*
    -----------------------------------------------------------------
    Section 1 . Print integer information from isec4.
    -----------------------------------------------------------------
  */
  fprintf(grprsm, " Coefficients defining first dimension coordinates:\n");
  for (int jloop = 0; jloop < isec4[52]; jloop++)
  {
    ntemp[jloop] = isec4[59 + jloop];
    ptemp = (float *) &ntemp[jloop];
    fprintf(grprsm, "%20.10f\n", *ptemp);
  }
  fprintf(grprsm, " Coefficients defining second dimension coordinates:\n");
  for (int jloop = 0; jloop < isec4[54]; jloop++)
  {
    ntemp[jloop] = isec4[59 + isec4[52] + jloop];
    ptemp = (float *) &ntemp[jloop];
    fprintf(grprsm, "%20.10f\n", *ptemp);
  }
}
#ifdef HAVE_CONFIG_H
#endif

#include <string.h>
#include <ctype.h>

int
gribOpen(const char *filename, const char *mode)
{
  int fileID = fileOpen(filename, mode);

#if defined(__sun)
  if (fileID != FILE_UNDEFID && tolower(*mode) == 'r') { fileSetBufferType(fileID, FILE_BUFTYPE_MMAP); }
#endif

  return fileID;
}

void
gribClose(int fileID)
{
  fileClose(fileID);
}

off_t
gribGetPos(int fileID)
{
  return fileGetPos(fileID);
}

int
gribCheckSeek(int fileID, long *offset, int *version)
{
  int ierr = gribFileSeek(fileID, offset);

  *version = -1;
  if (!ierr)
  {
    char buffer[4];
    if (fileRead(fileID, buffer, 4) == 4) *version = buffer[3];
  }

  return ierr;
}

int
gribFileSeek(int fileID, long *offset)
{
  // position file pointer after GRIB
  const long GRIB = 0x47524942;
  long code = 0;
  int retry = 4096 * 4096;

  *offset = 0;

  void *fileptr = filePtr(fileID);

  while (retry--)
  {
    int ch = filePtrGetc(fileptr);
    if (ch == EOF) return -1;

    code = ((code << 8) + ch) & 0xFFFFFFFF;
    if (code == GRIB)
    {
      if (CGRIBEX_Debug) Message("record offset = %ld", *offset);
      return 0;
    }

    (*offset)++;
  }

  if (CGRIBEX_Debug) Message("record offset = %ld", *offset);

  return 1;
}

static inline unsigned
read3ByteMSBFirst(void *fileptr)
{
  unsigned b1 = (unsigned) (filePtrGetc(fileptr));
  unsigned b2 = (unsigned) (filePtrGetc(fileptr));
  unsigned b3 = (unsigned) (filePtrGetc(fileptr));
  return GET_UINT3(b1, b2, b3);
}

size_t
gribReadSize(int fileID)
{
  size_t rgribsize = 0;
  void *fileptr = filePtr(fileID);
  off_t pos = fileGetPos(fileID);

  unsigned gribsize = read3ByteMSBFirst(fileptr);

  int gribversion = filePtrGetc(fileptr);

  if (gribsize == 24 && gribversion != 1 && gribversion != 2) gribversion = 0;

  if (CGRIBEX_Debug) Message("gribversion = %d", gribversion);

  if (gribversion == 0)
  {
    unsigned gdssize = 0, bmssize = 0;
    unsigned issize = 4, essize = 4;

    unsigned pdssize = gribsize;
    fileSetPos(fileID, (off_t) 3, SEEK_CUR);
    if (CGRIBEX_Debug) Message("pdssize     = %u", pdssize);
    int flag = filePtrGetc(fileptr);
    if (CGRIBEX_Debug) Message("flag        = %d", flag);

    fileSetPos(fileID, (off_t) pdssize - 8, SEEK_CUR);

    if (flag & 128)
    {
      gdssize = read3ByteMSBFirst(fileptr);
      fileSetPos(fileID, (off_t) gdssize - 3, SEEK_CUR);
      if (CGRIBEX_Debug) Message("gdssize     = %u", gdssize);
    }

    if (flag & 64)
    {
      bmssize = read3ByteMSBFirst(fileptr);
      fileSetPos(fileID, (off_t) bmssize - 3, SEEK_CUR);
      if (CGRIBEX_Debug) Message("bmssize     = %u", bmssize);
    }

    unsigned bdssize = read3ByteMSBFirst(fileptr);
    if (CGRIBEX_Debug) Message("bdssize     = %u", bdssize);

    gribsize = issize + pdssize + gdssize + bmssize + bdssize + essize;
    rgribsize = (size_t) gribsize;
  }
  else if (gribversion == 1)
  {
    if (gribsize > JP23SET)  // Large GRIB record
    {
      unsigned pdssize = read3ByteMSBFirst(fileptr);
      if (CGRIBEX_Debug) Message("pdssize     = %u", pdssize);

      int flag = 0;
      for (int i = 0; i < 5; ++i) flag = filePtrGetc(fileptr);
      if (CGRIBEX_Debug) Message("flag        = %d", flag);

      fileSetPos(fileID, (off_t) pdssize - 8, SEEK_CUR);

      unsigned gdssize = 0;
      if (flag & 128)
      {
        gdssize = read3ByteMSBFirst(fileptr);
        fileSetPos(fileID, (off_t) gdssize - 3, SEEK_CUR);
        if (CGRIBEX_Debug) Message("gdssize     = %u", gdssize);
      }

      unsigned bmssize = 0;
      if (flag & 64)
      {
        bmssize = read3ByteMSBFirst(fileptr);
        fileSetPos(fileID, (off_t) bmssize - 3, SEEK_CUR);
        if (CGRIBEX_Debug) Message("bmssize     = %u", bmssize);
      }

      unsigned bdssize = read3ByteMSBFirst(fileptr);
      if (CGRIBEX_Debug) Message("bdssize     = %u", bdssize);
      if (bdssize <= 120)
      {
        enum
        {
          issize = 4
        };
        gribsize &= JP23SET;
        gribsize *= 120;
        bdssize = correct_bdslen(bdssize, gribsize, issize + pdssize + gdssize + bmssize);
        if (CGRIBEX_Debug) Message("bdssize     = %u", bdssize);

        gribsize = issize + pdssize + gdssize + bmssize + bdssize + 4;
      }
    }
    rgribsize = (size_t) gribsize;
  }
  else if (gribversion == 2)
  {
    /* we set gribsize the following way because it doesn't matter then
       whether int is 4 or 8 bytes long - we don't have to care if the size
       really fits: if it does not, the record can not be read at all */
    rgribsize = 0;
    enum
    {
      g2size_bytes = 8
    };
    unsigned char g2size[g2size_bytes];
    filePtrRead(fileptr, g2size, g2size_bytes);
    for (int i = 0; i < g2size_bytes; ++i) rgribsize = (rgribsize << 8) | g2size[i];
  }
  else
  {
    rgribsize = 0;
    Warning("GRIB version %d unsupported!", gribversion);
  }

  if (filePtrEOF(fileptr)) rgribsize = 0;

  if (CGRIBEX_Debug) Message("gribsize = %zu", rgribsize);

  fileSetPos(fileID, pos, SEEK_SET);

  return rgribsize;
}

size_t
gribGetSize(int fileID)
{
  long offset;
  int ierr = gribFileSeek(fileID, &offset);  // position file pointer after GRIB
  if (ierr > 0)
  {
    Warning("GRIB record not found!");
    return 0;
  }

  if (ierr == -1) return 0;

  size_t recSize = gribReadSize(fileID);

  if (CGRIBEX_Debug) Message("recsize = %zu", recSize);

  fileSetPos(fileID, (off_t) -4, SEEK_CUR);

  return recSize;
}

int
gribRead(int fileID, void *buffer, size_t *buffersize)
{
  long offset;
  int ierr = gribFileSeek(fileID, &offset);  // position file pointer after GRIB
  if (ierr > 0)
  {
    Warning("GRIB record not found!");
    *buffersize = 0;
    return -2;
  }

  if (ierr == -1)
  {
    *buffersize = 0;
    return -1;
  }

  size_t recSize = gribReadSize(fileID);
  size_t readSize = recSize;

  if (readSize > *buffersize)
  {
    readSize = *buffersize;
    ierr = -3;  // Tell the caller that the buffer was insufficient.
  }

  *buffersize = recSize;  // Inform the caller about the record size.

  // Write the stuff to the buffer that has already been read in gribFileSeek().
  memcpy(buffer, "GRIB", 4);

  readSize -= 4;
  // Read the rest of the record into the buffer.
  size_t nread = fileRead(fileID, (char *) buffer + 4, readSize);

  if (nread != readSize) ierr = 1;

  return ierr;
}

int
gribWrite(int fileID, void *buffer, size_t buffersize)
{
  int nwrite = (int) (fileWrite(fileID, buffer, buffersize));
  if (nwrite != (int) buffersize)
  {
    perror(__func__);
    nwrite = -1;
  }

  return nwrite;
}
#include <string.h>
#include <ctype.h>

FILE *grprsm = NULL;
int CGRIBEX_grib_calendar = -1;

void
gribSetCalendar(int calendar)
{
  CGRIBEX_grib_calendar = calendar;
}

void
grsdef(void)
{
  /*
C---->
C**** GRSDEF - Initial (default) setting of common area variables
C              for GRIBEX package.
C
C     Purpose.
C     --------
C
C     Sets initial values for common area variables for all
C     routines of GRIBEX package, if not already done.
C
C**   Interface.
C     ----------
C
C     CALL GRSDEF
C
C     Input Parameters.
C     -----------------
C
C     None.
C
C     Output Parameters.
C     ------------------
C
C     None.
C
C     Method.
C     -------
C
C     Self-explanatory.
C
C     Externals.
C     ----------
C
C     None.
C
C     Reference.
C     ----------
C
C     See subroutine GRIBEX.
C
C     Comments.
C     ---------
C
C     None
C
C     Author.
C     -------
C
C     J. Clochard, Meteo France, for ECMWF - March 1998.
C
C     Modifications.
C     --------------
C
C     J. Clochard, Meteo France, for ECMWF - June 1999.
C     Add variable NSUBCE.
C     Use a static variable to determine if initialisation has already
C     been done. NUSER removed .
C     Reverse defaults for NEXT2O and NLOC2O, for consistency with
C     version 13.023 of software .
C
  */
  /*
C     ----------------------------------------------------------------
C*    Section 0 . Definition of variables.
C     ----------------------------------------------------------------
  */
  char *envString;
  char *env_stream;
  static bool lfirst = true;
  extern int CGRIBEX_Const;

  if (!lfirst) return;

  /*
    ----------------------------------------------------------------
    Section 1 . Set values, conditionally.
    ----------------------------------------------------------------
  */
  /*
    Common area variables have not been set. Set them.
  */
  // Set GRIB calendar.
  if (CGRIBEX_grib_calendar == -1)
  {
    CGRIBEX_grib_calendar = CALENDAR_PROLEPTIC;

    envString = getenv("GRIB_CALENDAR");
    if (envString)
    {
      if (strncmp(envString, "standard", 8) == 0)
        CGRIBEX_grib_calendar = CALENDAR_STANDARD;
      else if (strncmp(envString, "proleptic", 9) == 0)
        CGRIBEX_grib_calendar = CALENDAR_PROLEPTIC;
      else if (strncmp(envString, "360days", 7) == 0)
        CGRIBEX_grib_calendar = CALENDAR_360DAYS;
      else if (strncmp(envString, "365days", 7) == 0)
        CGRIBEX_grib_calendar = CALENDAR_365DAYS;
      else if (strncmp(envString, "366days", 7) == 0)
        CGRIBEX_grib_calendar = CALENDAR_366DAYS;
      else if (strncmp(envString, "none", 4) == 0)
        CGRIBEX_grib_calendar = CALENDAR_NONE;
    }
  }
  // Set GRIBEX compatibility mode.
  envString = getenv("GRIB_GRIBEX_MODE_ON");
  if (envString != NULL)
  {
    if (atoi(envString) == 1) CGRIBEX_Const = 0;
  }

  // See if output stream needs changing
  grprsm = stdout;
  env_stream = getenv("GRPRS_STREAM");
  if (env_stream)
  {
    if (isdigit((int) env_stream[0]))
    {
      int unit;
      unit = atoi(env_stream);
      if (unit < 1 || unit > 99)
        Warning("Invalid number for GRPRS_STREAM: %d", unit);
      else if (unit == 2)
        grprsm = stderr;
      else if (unit == 6)
        grprsm = stdout;
      else
      {
        char filename[] = "unit.00";
        snprintf(filename, sizeof(filename), "%2.2d", unit);
        grprsm = fopen(filename, "w");
        if (!grprsm) SysError("GRPRS_STREAM = %d", unit);
      }
    }
    else
    {
      if (env_stream[0])
      {
        grprsm = fopen(env_stream, "w");
        if (!grprsm) SysError("GRPRS_STREAM = %s", env_stream);
      }
    }
  }
  // Mark common area values set by user.
  lfirst = false;
}

// clang-format off

/* pack 8-bit bytes from 64-bit words to a packed buffer */
/* same as : for (int i = 0; i < bc; ++i) cp[i] = (unsigned char) up[i]; */

long packInt64(uint64_t *up, unsigned char *cp, long bc, long tc)
{
#if defined (CRAY)
  (void) _pack(up, cp, bc, tc);
#else
  unsigned char *cp0;
  uint64_t upi, *up0, *ip0, *ip1, *ip2, *ip3, *ip4, *ip5, *ip6, *ip7;
  long ipack = sizeof(int64_t);
  
  // Bytes until first word boundary in destination buffer

  long head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;

  long inner = bc - head;

  // Trailing bytes which do not make a full word

  long trail = inner & (ipack-1);

  // Number of bytes/words to be processed in fast loop

  inner -= trail;
  inner /= ipack;

  ip0 = up + head;
  ip1 = ip0 + 1;
  ip2 = ip0 + 2;
  ip3 = ip0 + 3;
  ip4 = ip0 + 4;
  ip5 = ip0 + 5;
  ip6 = ip0 + 6;
  ip7 = ip0 + 7;

  up0 = (uint64_t *)(void *)(cp + head);

  /* Here we should process any bytes until the first word boundary 
   * of our destination buffer 
   * That code is missing so far  because our output buffer is 
   * word aligned by FORTRAN 
   */

  long j = 0;

  if ( IS_BIGENDIAN() )
    {
#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
      for (long i = 0; i < inner; ++i)
	{
	  upi =             (   ip0[j]          << 56 ) 
	                 |  ( ( ip1[j] & 0xFF ) << 48 )
	                 |  ( ( ip2[j] & 0xFF ) << 40 )
	                 |  ( ( ip3[j] & 0xFF ) << 32 )
	                 |  ( ( ip4[j] & 0xFF ) << 24 ) ;
	  up0[i] = upi   |  ( ( ip5[j] & 0xFF ) << 16 )
	                 |  ( ( ip6[j] & 0xFF ) <<  8 )
	                 |    ( ip7[j] & 0xFF ) ;
	  j += ipack;
	}
    }
  else
    {
      for (long i = 0; i < inner; ++i)
	{
	  upi =             (   ip7[j]          << 56 ) 
	                 |  ( ( ip6[j] & 0xFF ) << 48 )
                         |  ( ( ip5[j] & 0xFF ) << 40 )
                         |  ( ( ip4[j] & 0xFF ) << 32 )
                         |  ( ( ip3[j] & 0xFF ) << 24 ) ;
	  up0[i] = upi   |  ( ( ip2[j] & 0xFF ) << 16 )
                         |  ( ( ip1[j] & 0xFF ) <<  8 )
                         |    ( ip0[j] & 0xFF ) ;
	  j += ipack;
	}
    }

  cp0 = (unsigned char *) ( up0 + inner );
  if ( trail > 0 )
    {
      up0[inner] = 0;
      for (long i = 0; i < trail ; ++i)
	{
	  *cp0 = (unsigned char) ip0[ipack*inner+i];
	  cp0++;
	}
    }

  if ( tc != -1 )
    {
      bc++;
      *cp0 = (unsigned char) tc;
    }
#endif
  return (bc);
}

/* unpack 8-bit bytes from a packed buffer with 64-bit words */
/* same as : for (int i = 0; i < bc; ++i) up[i] = (int64_t) cp[i]; */

long unpackInt64(const unsigned char *cp, uint64_t *up, long bc, long tc)
{
  const unsigned char *cp0;
  uint64_t *ip0, *ip1, *ip2, *ip3, *ip4, *ip5, *ip6, *ip7;
  long offset;
  long ipack = sizeof(int64_t);

  UNUSED(tc);

  // Bytes until first word boundary in source buffer

  long head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;
  if ( head > bc ) head = bc;

  long inner = bc - head;

  // Trailing bytes which do not make a full word
 
  long trail = inner & (ipack-1);
 
  // Number of bytes/words to be processed in fast loop

  inner -= trail;
  inner /= ipack;

  ip0 = up + head;
  ip1 = ip0 + 1;
  ip2 = ip0 + 2;
  ip3 = ip0 + 3;
  ip4 = ip0 + 4;
  ip5 = ip0 + 5;
  ip6 = ip0 + 6;
  ip7 = ip0 + 7;

  const uint64_t *up0 = (const uint64_t *)(const void *)(cp + head);

  /* Process any bytes until the first word boundary 
   * of our source buffer 
   */
  for (long i = 0; i < head; ++i) up[i] = (uint64_t) cp[i];

  long j = 0;

  if ( IS_BIGENDIAN() )
    {
#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
      for (long i = 0; i < inner; ++i)
	{
	  ip0[j] = (up0[i] >> 56) & 0xFF;
	  ip1[j] = (up0[i] >> 48) & 0xFF;
	  ip2[j] = (up0[i] >> 40) & 0xFF;
	  ip3[j] = (up0[i] >> 32) & 0xFF;
	  ip4[j] = (up0[i] >> 24) & 0xFF;
	  ip5[j] = (up0[i] >> 16) & 0xFF;
	  ip6[j] = (up0[i] >>  8) & 0xFF;
	  ip7[j] = (up0[i])       & 0xFF;

	  j += ipack;
	}
    }
  else
    {
      for (long i = 0; i < inner; ++i)
	{
	  ip7[j] = (up0[i] >> 56) & 0xFF;
	  ip6[j] = (up0[i] >> 48) & 0xFF;
	  ip5[j] = (up0[i] >> 40) & 0xFF;
	  ip4[j] = (up0[i] >> 32) & 0xFF;
	  ip3[j] = (up0[i] >> 24) & 0xFF;
	  ip2[j] = (up0[i] >> 16) & 0xFF;
	  ip1[j] = (up0[i] >>  8) & 0xFF;
	  ip0[j] = (up0[i])       & 0xFF;

	  j += ipack;
	}
    }

  if ( trail > 0 )
    {
      offset = head + ipack*inner;
      cp0 = cp + offset;
      for (long i = 0; i < trail; ++i) up[i+offset] = (uint64_t) cp0[i];
    }
  /*
  if ( tc != -1 ) {
    bc++;
    *cp0 = (unsigned char) tc;
  }
  */
  return bc;
}

/* pack 8-bit bytes from 32-bit words to a packed buffer */
/* same as : for (int i = 0; i < bc; ++i) cp[i] = (char) up[i]; */

long packInt32(uint32_t  *up, unsigned char *cp, long bc, long tc)
{
  unsigned char *cp0;
  uint32_t *up0, *ip0, *ip1, *ip2, *ip3;
  long ipack = sizeof(int32_t);
  
  // Bytes until first word boundary in destination buffer

  long head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;

  long inner = bc - head;

  // Trailing bytes which do not make a full word

  long trail = inner & (ipack-1);

  // Number of bytes/words to be processed in fast loop

  inner -= trail;
  inner /= ipack;

  ip0 = up + head;
  ip1 = ip0 + 1;
  ip2 = ip0 + 2;
  ip3 = ip0 + 3;

  up0 = (uint32_t *)(void *)(cp + head);

  /* Here we should process any bytes until the first word boundary 
   * of our destination buffer 
   * That code is missing so far  because our output buffer is 
   * word aligned by FORTRAN 
   */

  long j = 0;

  if ( IS_BIGENDIAN() )
    {
#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
      for (long i = 0; i < inner; ++i)
	{
	  up0[i] =          (   ip0[j]          << 24 ) 
	                 |  ( ( ip1[j] & 0xFF ) << 16 )
	                 |  ( ( ip2[j] & 0xFF ) <<  8 )
	                 |    ( ip3[j] & 0xFF ) ;
	  j += ipack;
	}
    }
  else
    {
      for (long i = 0; i < inner; ++i)
	{
	  up0[i] =          (   ip3[j]          << 24 ) 
	                 |  ( ( ip2[j] & 0xFF ) << 16 )
                         |  ( ( ip1[j] & 0xFF ) <<  8 )
                         |    ( ip0[j] & 0xFF ) ;
	  j += ipack;
	}
    }

  cp0 = (unsigned char *) ( up0 + inner );
  if ( trail > 0 )
    {
      up0[inner] = 0;
      for (long i = 0; i < trail; ++i)
	{
	  *cp0 = (unsigned char) ip0[ipack*inner+i];
	  cp0++;
	}
    }

  if ( tc != -1 )
    {
      bc++;
      *cp0 = (unsigned char) tc;
    }

  return (bc);
}

/* unpack 8-bit bytes from a packed buffer with 32-bit words */
/* same as : for (int i = 0; i < bc; ++i) up[i] = (int32_t) cp[i]; */

long unpackInt32(const unsigned char *cp, uint32_t *up, long bc, long tc)
{
  const unsigned char *cp0;
  uint32_t *ip0, *ip1, *ip2, *ip3;
  long offset;
  long ipack = sizeof(int32_t);

  UNUSED(tc);

  // Bytes until first word boundary in source buffer

  long head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;
  if ( head > bc ) head = bc;

  long inner = bc - head;

  // Trailing bytes which do not make a full word
 
  long trail = inner & (ipack-1);
 
  // Number of bytes/words to be processed in fast loop

  inner -= trail;
  inner /= ipack;

  ip0 = up + head;
  ip1 = ip0 + 1;
  ip2 = ip0 + 2;
  ip3 = ip0 + 3;

  const uint32_t *up0 = (const uint32_t *)(const void *)(cp + head);

  /* Process any bytes until the first word boundary 
   * of our source buffer 
   */
  for (long i = 0; i < head; ++i) up[i] = (uint32_t) cp[i];

  long j = 0;

  if ( IS_BIGENDIAN() )
    {
#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
      for (long i = 0; i < inner; ++i)
	{
	  ip0[j] = (up0[i] >> 24) & 0xFF;
	  ip1[j] = (up0[i] >> 16) & 0xFF;
	  ip2[j] = (up0[i] >>  8) & 0xFF;
	  ip3[j] = (up0[i])       & 0xFF;

	  j += ipack;
	}
    }
  else
    {
      for (long i = 0; i < inner; ++i)
	{
	  ip3[j] = (up0[i] >> 24) & 0xFF;
	  ip2[j] = (up0[i] >> 16) & 0xFF;
	  ip1[j] = (up0[i] >>  8) & 0xFF;
	  ip0[j] = (up0[i])       & 0xFF;

	  j += ipack;
	}
    }

  if ( trail > 0 )
    {
      offset = head + ipack*inner;
      cp0 = cp + offset;
      for (long i = 0; i < trail; ++i) up[i+offset] = (uint32_t) cp0[i];
    }
  /*
  if ( tc != -1 ) {
    bc++;
    *cp0 = (unsigned char) tc;
  }
  */

  return (bc);
}

// clang-format on
#include <stdio.h>

void
prtbin(int kin, int knbit, int *kout, int *kerr)
{
  /*

    Produces a decimal number with ones and zeroes
    corresponding to the ones and zeroes of the input
    binary number.
    eg input number 1011 binary, output number 1011 decimal.


    Input Parameters:

       kin   - Integer variable containing binary number.

       knbit - Number of bits in binary number.

    Output Parameters:

       kout  - Integer variable containing decimal value
               with ones and zeroes corresponding to those of
               the input binary number.

       kerr  - 0, If no error.
               1, Number of bits in binary number exceeds
                  maximum allowed or is less than 1.


    Converted from EMOS routine PRTBIN.

       Uwe Schulzweida   MPIfM   01/04/2001

  */
  int idec;
  int ik;
  int itemp;

  /*
    Check length of binary number to ensure decimal number
    generated will fit in the computer word - in this case will
    it fit in a Cray 48 bit integer?
  */
  if (knbit < 1 || knbit > 14)
  {
    *kerr = 1;
    printf(" prtbin : Error in binary number length - %3d bits.\n", knbit);
    return;
  }
  else
    *kerr = 0;
  /*
    -----------------------------------------------------------------
    Section 1. Generate required number.
    -----------------------------------------------------------------
  */
  *kout = 0;
  ik = kin;
  idec = 1;

  for (int j = 0; j < knbit; ++j)
  {
    itemp = ik - ((ik / 2) * 2);
    *kout = (*kout) + itemp * idec;
    ik = ik / 2;
    idec = idec * 10;
  }

  return;
}

void
ref2ibm(double *pref, int kbits)
{
  /*

    Purpose:
    --------

    Code and check reference value in IBM format

    Input Parameters:
    -----------------

    pref       - Reference value
    kbits      - Number of bits per computer word.

    Output Parameters:
    ------------------

    pref       - Reference value

    Method:
    -------

    Codes in IBM format, then decides to ensure that reference
    value used for packing is not different from that stored
    because of packing differences.

    Externals.
    ----------

    confp3    - Encode into IBM floating point format.
    decfp2    - Decode from IBM floating point format.

    Reference:
    ----------

    None.

    Comments:
    --------

    None.

    Author:
    -------

    J.D.Chambers     ECMWF      17:05:94

    Modifications:
    --------------

    Uwe Schulzweida   MPIfM   01/04/2001

    Convert to C from EMOS library version 130

  */

  int itrnd;
  int kexp, kmant;
  double ztemp, zdumm;
  extern int CGRIBEX_Debug;

  /* ----------------------------------------------------------------- */
  /*   Section 1. Convert to and from IBM format.                      */
  /* ----------------------------------------------------------------- */

  /*  Convert floating point reference value to IBM representation. */

  itrnd = 1;
  zdumm = ztemp = *pref;
  confp3(zdumm, &kexp, &kmant, kbits, itrnd);

  if (kexp == 0 && kmant == 0) return;

  /*  Set reference value to that actually stored in the GRIB code. */

  *pref = decfp2(kexp, kmant);

  /*  If the nearest number which can be represented in */
  /*  GRIB format is greater than the reference value,  */
  /*  find the nearest number in GRIB format lower      */
  /*  than the reference value.                         */

  if (ztemp < *pref)
  {
    /*  Convert floating point to GRIB representation */
    /*  using truncation to ensure that the converted */
    /*  number is smaller than the original one.      */

    itrnd = 0;
    zdumm = ztemp;
    confp3(zdumm, &kexp, &kmant, kbits, itrnd);

    /*  Set reference value to that stored in the GRIB code. */

    *pref = decfp2(kexp, kmant);

    if (ztemp < *pref)
    {
      if (CGRIBEX_Debug)
      {
        Message("Reference value error.");
        Message("Notify Met.Applications Section.");
        Message("ZTEMP = ", ztemp);
        Message("PREF = ", pref);
      }
      *pref = ztemp;
    }
  }

  return;
} /* ref2ibm */
#include <math.h>
#include <string.h>

unsigned
correct_bdslen(unsigned bdslen, long recsize, long gribpos)
{
  /*
    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.
  */
  if (recsize > JP23SET && bdslen <= 120) bdslen = (unsigned) (recsize - gribpos - bdslen);
  return bdslen;
}

int
grib1Sections(unsigned char *gribbuffer, long gribbufsize, unsigned char **pdsp, unsigned char **gdsp, unsigned char **bmsp,
              unsigned char **bdsp, long *gribrecsize)
{
  *gribrecsize = 0;
  *pdsp = NULL;
  *gdsp = NULL;
  *bmsp = NULL;
  *bdsp = NULL;

  unsigned char *section = gribbuffer;
  unsigned char *is = gribbuffer;
  if (!GRIB_START(section))
  {
    fprintf(stderr, "Wrong GRIB indicator section: found >%c%c%c%c<\n", section[0], section[1], section[2], section[3]);
    return -1;
  }

  unsigned recsize = GET_UINT3(section[4], section[5], section[6]);

  int gribversion = GRIB_EDITION(section);
  if (gribversion != 0 && gribversion != 1)
  {
    fprintf(stderr, "Error while decoding GRIB1 sections: GRIB edition %d records not supported!\n", gribversion);
    return -1;
  }

  unsigned grib1offset = (gribversion == 1) ? 4 : 0;

  unsigned char *pds = is + 4 + grib1offset;
  unsigned char *bufpointer = pds + PDS_Len;
  unsigned gribsize = 4 + grib1offset + PDS_Len;

  unsigned char *gds = NULL;
  if (PDS_HAS_GDS)
  {
    gds = bufpointer;
    bufpointer += GDS_Len;
    gribsize += GDS_Len;
  }

  unsigned char *bms = NULL;
  if (PDS_HAS_BMS)
  {
    bms = bufpointer;
    bufpointer += BMS_Len;
    gribsize += BMS_Len;
  }

  unsigned char *bds = bufpointer;
  unsigned bdslen = BDS_Len;
  if (recsize > JP23SET && bdslen <= 120)
  {
    recsize &= JP23SET;
    recsize *= 120;
    bdslen = correct_bdslen(bdslen, recsize, gribsize);
  }
  bufpointer += bdslen;
  gribsize += bdslen;
  gribsize += 4;

  *pdsp = pds;
  *gdsp = gds;
  *bmsp = bms;
  *bdsp = bds;

  *gribrecsize = gribsize;
  if (gribbufsize < gribsize)
  {
    fprintf(stderr, "Inconsistent length of GRIB message (grib_buffer_size=%ld < grib_record_size=%u)!\n", gribbufsize, gribsize);
    return 1;
  }

  if (!GRIB_FIN(bufpointer))  // end section - "7777" in ASCII
  {
    fprintf(stderr, "Missing GRIB end section: found >%c%c%c%c<\n", bufpointer[0], bufpointer[1], bufpointer[2], bufpointer[3]);
    return -2;
  }

  return 0;
}

int
grib2Sections(unsigned char *gribbuffer, long gribbufsize, unsigned char **idsp, unsigned char **lusp, unsigned char **gdsp,
              unsigned char **pdsp, unsigned char **drsp, unsigned char **bmsp, unsigned char **bdsp)
{
  UNUSED(gribbufsize);

  *idsp = NULL;
  *lusp = NULL;
  *gdsp = NULL;
  *pdsp = NULL;
  *drsp = NULL;
  *bmsp = NULL;
  *bdsp = NULL;

  unsigned char *section = gribbuffer;
  unsigned sec_len = 16;

  if (!GRIB_START(section))
  {
    fprintf(stderr, "wrong indicator section >%c%c%c%c<\n", section[0], section[1], section[2], section[3]);
    return -1;
  }

  int gribversion = GRIB_EDITION(section);
  if (gribversion != 2)
  {
    fprintf(stderr, "wrong GRIB version %d\n", gribversion);
    return -1;
  }

  unsigned gribsize = 0;
  for (int i = 0; i < 8; ++i) gribsize = (gribsize << 8) | section[8 + i];

  unsigned grib_len = sec_len;
  section += sec_len;

  /* section 1 */
  sec_len = GRIB2_SECLEN(section);
  int sec_num = GRIB2_SECNUM(section);
  // fprintf(stderr, "ids %d %ld\n", sec_num, sec_len);

  if (sec_num != 1)
  {
    fprintf(stderr, "Unexpected section1 number %d\n", sec_num);
    return -1;
  }

  *idsp = section;

  grib_len += sec_len;
  section += sec_len;

  /* section 2 and 3 */
  sec_len = GRIB2_SECLEN(section);
  sec_num = GRIB2_SECNUM(section);
  // fprintf(stderr, "lus %d %ld\n", sec_num, sec_len);

  if (sec_num == 2)
  {
    *lusp = section;

    grib_len += sec_len;
    section += sec_len;

    /* section 3 */
    sec_len = GRIB2_SECLEN(section);
    // sec_num = GRIB2_SECNUM(section);
    // fprintf(stderr, "gds %d %ld\n", sec_num, sec_len);

    *gdsp = section;
  }
  else if (sec_num == 3) { *gdsp = section; }
  else
  {
    fprintf(stderr, "Unexpected section3 number %d\n", sec_num);
    return -1;
  }

  grib_len += sec_len;
  section += sec_len;

  /* section 4 */
  sec_len = GRIB2_SECLEN(section);
  sec_num = GRIB2_SECNUM(section);
  // fprintf(stderr, "pds %d %ld\n", sec_num, sec_len);

  if (sec_num != 4)
  {
    fprintf(stderr, "Unexpected section4 number %d\n", sec_num);
    return -1;
  }

  *pdsp = section;

  grib_len += sec_len;
  section += sec_len;

  /* section 5 */
  sec_len = GRIB2_SECLEN(section);
  sec_num = GRIB2_SECNUM(section);
  // fprintf(stderr, "drs %d %ld\n", sec_num, sec_len);

  if (sec_num != 5)
  {
    fprintf(stderr, "Unexpected section5 number %d\n", sec_num);
    return -1;
  }

  *drsp = section;

  grib_len += sec_len;
  section += sec_len;

  /* section 6 */
  sec_len = GRIB2_SECLEN(section);
  sec_num = GRIB2_SECNUM(section);
  // fprintf(stderr, "bms %d %ld\n", sec_num, sec_len);

  if (sec_num != 6)
  {
    fprintf(stderr, "Unexpected section6 number %d\n", sec_num);
    return -1;
  }

  *bmsp = section;

  grib_len += sec_len;
  section += sec_len;

  /* section 7 */
  sec_len = GRIB2_SECLEN(section);
  sec_num = GRIB2_SECNUM(section);
  // fprintf(stderr, "bds %d %ld\n", sec_num, sec_len);

  if (sec_num != 7)
  {
    fprintf(stderr, "Unexpected section7 number %d\n", sec_num);
    return -1;
  }

  *bdsp = section;

  grib_len += sec_len;
  section += sec_len;

  /* skip multi GRIB sections */
  int msec = 1;
  while (!GRIB_FIN(section))
  {
    sec_len = GRIB2_SECLEN(section);
    sec_num = GRIB2_SECNUM(section);

    if (sec_num < 1 || sec_num > 7) break;

    if (sec_num == 7) fprintf(stderr, "Skipped unsupported multi GRIB section %d!\n", ++msec);

    if ((grib_len + sec_len) > gribsize) break;

    grib_len += sec_len;
    section += sec_len;
  }

  /* end section - "7777" in ASCII */
  if (!GRIB_FIN(section))
  {
    fprintf(stderr, "Missing end section >%2x %2x %2x %2x<\n", section[0], section[1], section[2], section[3]);
    return -2;
  }

  return 0;
}

int
grib_info_for_grads(off_t recpos, long recsize, unsigned char *gribbuffer, int *intnum, float *fltnum, off_t *bignum)
{
  long gribsize = 0;
  off_t bpos = 0;

  unsigned char *section = gribbuffer;
  unsigned char *is = gribbuffer;
  if (!GRIB_START(section))
  {
    fprintf(stderr, "wrong indicator section >%c%c%c%c<\n", section[0], section[1], section[2], section[3]);
    return -1;
  }

  int gribversion = GRIB_EDITION(section);
  if (recsize == 24 && gribversion == 0) gribversion = 0;

  unsigned grib1offset = (gribversion == 1) ? 4 : 0;

  unsigned char *pds = is + 4 + grib1offset;
  unsigned char *bufpointer = pds + PDS_Len;
  gribsize += 4 + grib1offset + PDS_Len;

  unsigned char *gds = NULL;
  if (PDS_HAS_GDS)
  {
    gds = bufpointer;
    bufpointer += GDS_Len;
    gribsize += GDS_Len;
  }

  unsigned char *bms = NULL;
  if (PDS_HAS_BMS)
  {
    bms = bufpointer;
    bufpointer += BMS_Len;
    bpos = recpos + gribsize + 6;
    gribsize += BMS_Len;
  }

  unsigned char *bds = bufpointer;

  off_t dpos = recpos + gribsize + 11;

  unsigned bdslen = BDS_Len;
  bdslen = correct_bdslen(bdslen, recsize, bds - gribbuffer);
  bufpointer += bdslen;
  gribsize += bdslen;
  gribsize += 4;

  if (gribsize > recsize)
  {
    fprintf(stderr, "GRIB buffer size %ld too small! Min size = %ld\n", recsize, gribsize);
    return 1;
  }

  /* end section - "7777" in ascii */
  if (!GRIB_FIN(bufpointer))
  {
    fprintf(stderr, "Missing end section >%2x %2x %2x %2x<\n", bufpointer[0], bufpointer[1], bufpointer[2], bufpointer[3]);
  }

  int bs = BDS_BinScale;
  if (bs > 32767) bs = 32768 - bs;
  float bsf = ldexpf(1.0f, bs);

  bignum[0] = dpos;
  bignum[1] = bms ? bpos : -999;
  intnum[0] = BDS_NumBits;

  /*  fltnum[0] = 1.0; */
  fltnum[0] = powf(10.0f, (float) PDS_DecimalScale);
  fltnum[1] = bsf;
  fltnum[2] = (float) BDS_RefValue;
  /*
  printf("intnum %d %d %d\n", intnum[0], intnum[1], intnum[2]);
  printf("fltnum %g %g %g\n", fltnum[0], fltnum[1], fltnum[2]);
  */
  return 0;
}

static int
get_level(unsigned char *pds)
{
  int level = 0;

  if (PDS_LevelType == 100)
    level = (int) (PDS_Level) *100;
  else if (PDS_LevelType == 99 || PDS_LevelType == 109)
    level = (int) (PDS_Level);
  else
    level = PDS_Level1;

  return level;
}

static double
get_cr(unsigned char *w1, unsigned char *w2)
{
  unsigned s1 = GET_UINT3(w1[0], w1[1], w1[2]);
  unsigned s2 = GET_UINT3(w2[0], w2[1], w2[2]);
  return ((double) s1) / s2;
}

static void
grib1PrintALL(int nrec, long offset, long recpos, long recsize, unsigned char *gribbuffer)
{
  static bool header = true;
  unsigned char *is = NULL, *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;

  if (header)
  {
    fprintf(stdout, "  Rec : Off Position   Size : V PDS  GDS    BMS    BDS : Code Level :  LType GType: CR LL\n");
    //               ----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+
    header = false;
  }

  is = gribbuffer;

  unsigned gribsize = GET_UINT3(is[4], is[5], is[6]);

  long gribrecsize;
  int nerr = grib1Sections(gribbuffer, recsize, &pds, &gds, &bms, &bds, &gribrecsize);
  if (nerr < 0)
  {
    fprintf(stdout, "%5d :%4ld %8ld %6ld : GRIB message error\n", nrec, offset, recpos, recsize);
    return;
  }

  int GridType = (gds == NULL) ? -1 : (int) GDS_GridType;

  int level = get_level(pds);

  unsigned bdslen = BDS_Len;

  bool llarge = (gribsize > JP23SET && bdslen <= 120);

  bdslen = correct_bdslen(bdslen, recsize, bds - gribbuffer);

  double cr = (((BDS_Flag >> 4) & 1) && (BDS_Z == 128 || BDS_Z == 130)) ? get_cr(&bds[14], &gribbuffer[4]) : 1;

  fprintf(stdout, "%5d :%4ld %8ld %6ld :%2d%4d%5d %6d %6d : %3d %6d : %5d %5d %6.4g  %c", nrec, offset, recpos, recsize,
          GRIB_EDITION(is), PDS_Len, GDS_Len, BMS_Len, bdslen, PDS_Parameter, level, PDS_LevelType, GridType, cr,
          llarge ? 'T' : 'F');

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

static void
grib2PrintALL(int nrec, long offset, long recpos, long recsize, unsigned char *gribbuffer)
{
  static bool header = true;
  unsigned char *is = NULL, *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
  unsigned char *ids = NULL, *lus = NULL, *drs = NULL;
  long ids_len = 0, lus_len = 0, gds_len = 0, pds_len = 0, drs_len = 0, bms_len = 0, bds_len = 0;
  double cr = 1;

  if (header)
  {
    fprintf(stdout, "  Rec : Off Position   Size : V IDS LUS GDS PDS  DRS    BMS    BDS : Parameter   Level :  LType GType: CR\n");
    //       ----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+
    header = false;
  }

  is = gribbuffer;

  int nerr = grib2Sections(gribbuffer, recsize, &ids, &lus, &gds, &pds, &drs, &bms, &bds);
  if (nerr)
  {
    fprintf(stdout, "%5d :%4ld %8ld %6ld : error\n", nrec, offset, recpos, recsize);
    return;
  }

  if (ids) ids_len = GRIB2_SECLEN(ids);
  if (lus) lus_len = GRIB2_SECLEN(lus);
  if (gds) gds_len = GRIB2_SECLEN(gds);
  if (pds) pds_len = GRIB2_SECLEN(pds);
  if (drs) drs_len = GRIB2_SECLEN(drs);
  if (bms) bms_len = GRIB2_SECLEN(bms);
  if (bds) bds_len = GRIB2_SECLEN(bds);

  // double cr = (((BDS_Flag >> 4)&1) && (BDS_Z == 128 || BDS_Z == 130)) ? get_cr(&bds[14], &gribbuffer[4]) : 1;

  int dis = GET_UINT1(is[6]);
  int gridtype = (int) (GET_UINT2(gds[12], gds[13]));
  int paramcat = GET_UINT1(pds[9]);
  int paramnum = GET_UINT1(pds[10]);
  int level1type = GET_UINT1(pds[22]);
  /* level1sf   = GET_UINT1(pds[23]); */
  int level1 = (int) (GET_UINT4(pds[24], pds[25], pds[26], pds[27]));
  /* level2type = GET_UINT1(pds[28]); */
  /* level2sf   = GET_UINT1(pds[29]); */
  /* level2     = GET_UINT4(pds[30],pds[31],pds[32],pds[33]); */
  /*
  printf("level %d %d %d %d %d %d %d\n", level1type, level1sf, level1, level1*level1sf, level2sf, level2, level2*level2sf);
  */
  char paramstr[16];
  snprintf(paramstr, sizeof(paramstr), "%d.%d.%d", paramnum, paramcat, dis);
  fprintf(stdout, "%5d :%4ld %8ld %6ld :%2d %3ld %3ld %3ld %3ld %4ld %6ld %6ld : %-9s %7d : %5d %5d %6.4g\n", nrec, offset, recpos,
          recsize, GRIB_EDITION(is), ids_len, lus_len, gds_len, pds_len, drs_len, bms_len, bds_len, paramstr, level1, level1type,
          gridtype, cr);
}

void
gribPrintALL(int nrec, long offset, long recpos, long recsize, unsigned char *gribbuffer)
{
  int gribversion = gribVersion(gribbuffer, (size_t) recsize);

  if (gribversion == 0 || gribversion == 1)
    grib1PrintALL(nrec, offset, recpos, recsize, gribbuffer);
  else if (gribversion == 2)
    grib2PrintALL(nrec, offset, recpos, recsize, gribbuffer);
  else { fprintf(stdout, "%5d :%4ld%9ld%7ld : GRIB version %d unsupported\n", nrec, offset, recpos, recsize, gribversion); }
}

static void
grib1PrintPDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  static int header = 1;
  unsigned char *is = NULL, *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
  int century, subcenter, decimalscale;
  int fc_num = 0;
  int year = 0, date;

  UNUSED(recpos);

  if (header)
  {
    fprintf(stdout, "  Rec : PDS Tab Cen Sub Ver Grid Code LTyp Level1 Level2    Date  Time P1 P2 TU TR NAVE Scale FCnum CT\n");
    //               ----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+
    header = 0;
  }

  is = gribbuffer;

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

  switch (GRIB_EDITION(is))
  {
    case 0:
      year = GET_UINT1(pds[12]);
      century = 1;
      subcenter = 0;
      decimalscale = 0;
      break;
    case 1:
      year = PDS_Year;
      century = PDS_Century;
      subcenter = PDS_Subcenter;
      decimalscale = PDS_DecimalScale;
      break;
    default: fprintf(stderr, "Grib version %d not supported!", GRIB_EDITION(is)); exit(EXIT_FAILURE);
  }

  if (PDS_Len > 28)
    if (PDS_CenterID == 98 || PDS_Subcenter == 98 || (PDS_CenterID == 7 && PDS_Subcenter == 98))
      if (pds[40] == 1) fc_num = GET_UINT1(pds[49]);

  if (year < 0)
  {
    date = (-year) * 10000 + (int) PDS_Month * 100 + (int) PDS_Day;
    century = -century;
  }
  else { date = year * 10000 + (int) PDS_Month * 100 + (int) PDS_Day; }

  fprintf(stdout, "%5d :%4d%4d%4d%4d%4d %4d %4d%4d%7d%7d %8d%6d%3d%3d%3d%3d%5d%6d%5d%4d", nrec, PDS_Len, PDS_CodeTable,
          PDS_CenterID, subcenter, PDS_ModelID, PDS_GridDefinition, PDS_Parameter, PDS_LevelType, PDS_Level1, PDS_Level2, date,
          PDS_Time, PDS_TimePeriod1, PDS_TimePeriod2, PDS_TimeUnit, PDS_TimeRange, PDS_AvgNum, decimalscale, fc_num, century);

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

void
gribPrintPDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  int gribversion = gribVersion(gribbuffer, (size_t) recsize);

  if (gribversion == 0 || gribversion == 1) grib1PrintPDS(nrec, recpos, recsize, gribbuffer);
  /*
  else if ( gribversion == 2 )
    grib2PrintPDS(nrec, recpos, recsize, gribbuffer);
  */
  else { fprintf(stdout, "%5d :%4ld%9ld%7ld : GRIB version %d unsupported\n", nrec, 0L, recpos, recsize, gribversion); }
}

static void
grib1PrintGDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  static int header = 1;
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;

  UNUSED(recpos);

  if (header)
  {
    fprintf(stdout, "  Rec : GDS  NV PVPL Typ : xsize ysize   Lat1   Lon1   Lat2   Lon2    dx    dy\n");
    //               ----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+
    header = 0;
  }

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

  fprintf(stdout, "%5d :", nrec);

  if (gds)
    fprintf(stdout, "%4d%4d%4d %4d :%6d%6d%7d%7d%7d%7d%6d%6d", GDS_Len, GDS_NV, GDS_PVPL, GDS_GridType, GDS_NumLon, GDS_NumLat,
            GDS_FirstLat, GDS_FirstLon, GDS_LastLat, GDS_LastLon, GDS_LonIncr, GDS_LatIncr);
  else
    fprintf(stdout, " Grid Description Section not defined");

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

void
gribPrintGDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  int gribversion = gribVersion(gribbuffer, (size_t) recsize);

  if (gribversion == 0 || gribversion == 1) grib1PrintGDS(nrec, recpos, recsize, gribbuffer);
  /*
  else if ( gribversion == 2 )
    grib2PrintGDS(nrec, recpos, recsize, gribbuffer);
  */
  else { fprintf(stdout, "%5d :%4ld%9ld%7ld : GRIB version %d unsupported\n", nrec, 0L, recpos, recsize, gribversion); }
}

static void
grib1PrintBMS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  static int header = 1;
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;

  UNUSED(recpos);

  if (header)
  {
    fprintf(stdout, "  Rec : Code Level     BMS    Size\n");
    //               ----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+
    header = 0;
  }

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

  int level = get_level(pds);

  fprintf(stdout, "%5d :", nrec);

  if (bms)
    fprintf(stdout, "%4d%7d %7d %7d", PDS_Parameter, level, BMS_Len, BMS_BitmapSize);
  else
    fprintf(stdout, "%4d%7d Bit Map Section not defined", PDS_Parameter, level);

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

void
gribPrintBMS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  int gribversion = gribVersion(gribbuffer, (size_t) recsize);

  if (gribversion == 0 || gribversion == 1) grib1PrintBMS(nrec, recpos, recsize, gribbuffer);
  /*
  else if ( gribversion == 2 )
    grib2PrintBMS(nrec, recpos, recsize, gribbuffer);
  */
  else { fprintf(stdout, "%5d :%4ld%9ld%7ld : GRIB version %d unsupported\n", nrec, 0L, recpos, recsize, gribversion); }
}

static void
grib1PrintBDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  static int header = 1;
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
  double scale;

  UNUSED(recpos);

  if (header)
  {
    fprintf(stdout, "  Rec : Code Level     BDS Flag     Scale   RefValue Bits  CR\n");
    //               ----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8----+
    header = 0;
  }

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

  int level = get_level(pds);

  double cr = (((BDS_Flag >> 4) & 1) && BDS_Z == 128) ? get_cr(&bds[17], &bds[20]) : 1;

  double refval = BDS_RefValue;

  if (BDS_BinScale < 0)
    scale = 1.0 / pow(2.0, (double) -BDS_BinScale);
  else
    scale = pow(2.0, (double) BDS_BinScale);

  if (PDS_DecimalScale != 0)
  {
    double decscale = pow(10.0, (double) -PDS_DecimalScale);
    refval *= decscale;
    scale *= decscale;
  }

  fprintf(stdout, "%5d :", nrec);

  if (bds)
    fprintf(stdout, "%4d%7d %7d %4d %8.5g %11.5g%4d %6.4g", PDS_Parameter, level, BDS_Len, BDS_Flag, scale, refval, BDS_NumBits,
            cr);
  else
    fprintf(stdout, " Binary Data Section not defined");

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

void
gribPrintBDS(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  int gribversion = gribVersion(gribbuffer, (size_t) recsize);

  if (gribversion == 0 || gribversion == 1) grib1PrintBDS(nrec, recpos, recsize, gribbuffer);
  /*
  else if ( gribversion == 2 )
    grib2PrintBDS(nrec, recpos, recsize, gribbuffer);
  */
  else { fprintf(stdout, "%5d :%4ld%9ld%7ld : GRIB version %d unsupported\n", nrec, 0L, recpos, recsize, gribversion); }
}

void
gribCheck1(int nrec, long recpos, long recsize, unsigned char *gribbuffer)
{
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;

  UNUSED(recpos);

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

  if (nerr > 0)
  {
    fprintf(stdout, "%5d : <-- GRIB data corrupted!\n", nrec);
    return;
  }

  int level = get_level(pds);

  double cr = (((BDS_Flag >> 4) & 1) && BDS_Z == 128) ? get_cr(&bds[17], &bds[20]) : 1;

  if (IS_EQUAL(cr, 1) && BDS_NumBits == 24)
    fprintf(stdout, "GRIB record %5d : code = %4d   level = %7d\n", nrec, PDS_Parameter, level);
}

static void
repair1(unsigned char *gbuf, long gbufsize)
{
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
  /* int recLen; */
  size_t bds_nbits;
  int bds_flag, lspherc, lcomplex /*, lcompress */;
  enum
  {
    bds_head = 11
  };
  size_t bds_ext = 0, bds_ubits;

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

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

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

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

  size_t datstart = bds_head + bds_ext;

  unsigned char *source = bds + datstart;

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

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

void
gribRepair1(int nrec, long recsize, unsigned char *gribbuffer)
{
  unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;

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

  if (nerr > 0)
  {
    fprintf(stdout, "%5d : <-- GRIB data corrupted!\n", nrec);
    return;
  }

  int level = get_level(pds);

  double cr = (((BDS_Flag >> 4) & 1) && BDS_Z == 128) ? get_cr(&bds[17], &bds[20]) : 1;

  if (IS_EQUAL(cr, 1) && BDS_NumBits == 24)
  {
    fprintf(stdout, "Repair GRIB record %5d : code = %4d   level = %7d\n", nrec, PDS_Parameter, level);
    repair1(gribbuffer, recsize);
  }
}
#include <stdio.h>
#include <string.h>

#if defined(HAVE_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;
}
#include <stdio.h>
#include <math.h>

// clang-format off


static void
scm0_double(double *pdl, double *pdr, double *pfl, double *pfr, int klg);


static
int rowina2(double *p, int ko, int ki, double *pw,
	    int kcode, double msval, int *kret)
{
  /* System generated locals */
  int pw_dim1, pw_offset, i_1;

  /* Local variables */
  double zwt1, zrdi, zpos;
  int ip;
  double zdo, zwt;

  /* Parameter adjustments */
  --p;
  pw_dim1 = ko + 3;
  pw_offset = pw_dim1;
  pw -= pw_offset;

  /* **** ROWINA2 - Interpolation of row of values. */
  /*     Input Parameters. */
  /*     ----------------- */
  /*     P      - Row of values to be interpolated. */
  /*              Dimension must be at least KO. */
  /*     KO     - Number of values required. */
  /*     KI     - Number of values in P on input. */
  /*     PW     - Working array. */
  /*              Dimension must be at least (0:KO+2,3). */
  /*     KCODE  - Interpolation required. */
  /*              1 , linear. */
  /*              3 , cubic. */
  /*     PMSVAL - Value used for missing data indicator. */

  /*     Output Parameters. */
  /*     ------------------ */
  /*     P     - Now contains KO values. */
  /*     KRET  - Return code */
  /*             0, OK */
  /*             Non-zero, error */

  /*     Author. */
  /*     ------- */
  /*     J.D.Chambers    ECMWF     22.07.94 */

  /*     ********************************    */
  /*     Section 1.  Linear interpolation .. */
  /*     ********************************    */

  *kret = 0;

  if ( kcode == 1 )
    {
      /*    Move input values to work array */
      for (int jl = 1; jl <= ki; ++jl)
	pw[jl + pw_dim1] = p[jl];

      /*    Arrange wrap-around value in work array */
      pw[ki + 1 + pw_dim1] = p[1];

      /*    Set up constants to be used to figure out weighting for */
      /*    values in interpolation. */
      zrdi = (double) ki;
      zdo = 1.0 / (double) ko;

      /*    Loop through the output points */
      for (int jl = 1; jl <= ko; ++jl)
	{

	  /*    Calculate weight from the start of row */
	  zpos = (jl - 1) * zdo;
	  zwt = zpos * zrdi;

	  /*    Get the current array position(minus 1) from the weight - */
	  /*    note the implicit truncation. */
	  ip = (int) zwt;

	  /*    If the left value is missing, use the right value */
	  if ( IS_EQUAL(pw[ip + 1 + pw_dim1], msval) )
	    {
	      p[jl] = pw[ip + 2 + pw_dim1];
	    }
	  /*    If the right value is missing, use the left value */
	  else if ( IS_EQUAL(pw[ip + 2 + pw_dim1], msval) )
	    {
	      p[jl] = pw[ip + 1 + pw_dim1];
	    }
	  /*    If neither missing, interpolate ... */
	  else
	    {

	      /*       Adjust the weight to range (0.0 to 1.0) */
	      zwt -= ip;

	      /*       Interpolate using the weighted values on either side */
	      /*       of the output point position */
	      p[jl] = (1.0 - zwt) * pw[ip + 1 + pw_dim1] +
		zwt * pw[ip + 2 + pw_dim1];
	    }
	}

      /*     *******************************    */
      /*     Section 2.  Cubic interpolation .. */
      /*     *******************************    */

    }
  else if ( kcode == 3 )
    {
      i_1 = ki;
      for (int jl = 1; jl <= i_1; ++jl)
	{
          if ( IS_EQUAL(p[jl], msval) )
	    {
	      fprintf(stderr," ROWINA2: ");
	      fprintf(stderr," Cubic interpolation not supported");
	      fprintf(stderr," for fields containing missing data.\n");
	      *kret = 1;
	      goto L900;
	    }
          pw[jl + pw_dim1] = p[jl];
	}
      pw[pw_dim1] = p[ki];
      pw[ki + 1 + pw_dim1] = p[1];
      pw[ki + 2 + pw_dim1] = p[2];
      i_1 = ki;
      for (int jl = 1; jl <= i_1; ++jl)
	{
          pw[jl + (pw_dim1 << 1)] =
	        - pw[jl - 1 + pw_dim1] / 3.0 -
	          pw[jl     + pw_dim1] * 0.5 +
	          pw[jl + 1 + pw_dim1] - pw[jl + 2 + pw_dim1] / 6.0;
          pw[jl + 1 + pw_dim1 * 3] =
                  pw[jl - 1 + pw_dim1] / 6.0 -
                  pw[jl     + pw_dim1] +
                  pw[jl + 1 + pw_dim1] * 0.5 +
                  pw[jl + 2 + pw_dim1] / 3.0;
	}

      scm0_double(&pw[(pw_dim1 << 1) + 1], &pw[pw_dim1 * 3 + 2],
		  &pw[pw_dim1 + 1], &pw[pw_dim1 + 2], ki);

      zrdi = (double) ki;
      zdo = 1.0 / (double) ko;
      for (int jl = 1; jl <= ko; ++jl)
	{
          zpos = (jl - 1) * zdo;
          zwt = zpos * zrdi;
          ip = (int) zwt + 1;
          zwt = zwt + 1.0 - ip;
          zwt1 = 1.0 - zwt;
          p[jl] = ((3.0 - zwt1 * 2.0) * pw[ip + pw_dim1] +
                  zwt * pw[ip + (pw_dim1 << 1)]) * zwt1 * zwt1 +
                  ((3.0 - zwt * 2.0) * pw[ip + 1 + pw_dim1] -
                  zwt1 * pw[ip + 1 + pw_dim1 * 3]) * zwt * zwt;
	}

    }
  else
    {
      /*    **************************************    */
      /*    Section 3.  Invalid interpolation code .. */
      /*    **************************************    */
      fprintf(stderr," ROWINA2:");
      fprintf(stderr," Invalid interpolation code = %2d\n",kcode);
      *kret = 2;
    }

L900:
    return 0;
} /* rowina2 */



int qu2reg2(double *pfield, int *kpoint, int klat, int klon,
	    double *ztemp, double msval, int *kret)
{
   /* System generated locals */
   int i_1, i_2;
   int kcode = 1;

   /* Local variables */
   int ilii, ilio, icode;
   double *zline = NULL;
   double *zwork = NULL;
   int iregno, iquano;


   zline = (double*) Malloc(2*(size_t)klon*sizeof(double));
   if ( zline == NULL ) SysError("No Memory!");

   zwork = (double*) Malloc(3*(2*(size_t)klon+3)*sizeof(double));
   if ( zwork == NULL ) SysError("No Memory!");

   /* Parameter adjustments */
   --pfield;
   --kpoint;

/* **** QU2REG - Convert quasi-regular grid data to regular. */
/*     Input Parameters. */
/*     ----------------- */
/*     PFIELD     - Array containing quasi-regular grid */
/*                  data. */
/*     KPOINT     - Array containing list of the number of */
/*                  points on each latitude (or longitude) of */
/*                  the quasi-regular grid. */
/*     KLAT       - Number of latitude lines */
/*     KLON       - Number of longitude lines */
/*     KCODE      - Interpolation required. */
/*                  1 , linear - data quasi-regular on */
/*                               latitude lines. */
/*                  3 , cubic -  data quasi-regular on */
/*                               latitude lines. */
/*                  11, linear - data quasi-regular on */
/*                               longitude lines. */
/*                  13, cubic -  data quasi-regular on */
/*                               longitude lines. */
/*     PMSVAL     - Value used for missing data indicator. */
/*     Output Parameters. */
/*     ------------------ */
/*     KRET       - return code */
/*                  0 = OK */
/*                  non-zero indicates fatal error */
/*     PFIELD     - Array containing regular grid data. */
/*     Author. */
/*     ------- */
/*     J.D.Chambers     ECMWF      22.07.94 */
/*     J.D.Chambers     ECMWF      13.09.94 */
/*     Add return code KRET and remove calls to ABORT. */


/* ------------------------------ */
/* Section 1. Set initial values. */
/* ------------------------------ */

   *kret = 0;

/* Check input parameters. */

   if (kcode != 1 && kcode != 3 && kcode != 11 && kcode != 13) {
      fprintf(stderr," QU2REG :");
      fprintf(stderr," Invalid interpolation type code = %2d\n",kcode);
      *kret = 1;
      goto L900;
   }

/* Set array indices to 0. */

   ilii = 0;
   ilio = 0;

/* Establish values of loop parameters. */

   if (kcode > 10) {

/*    Quasi-regular along longitude lines. */

      iquano = klon;
      iregno = klat;
      icode = kcode - 10;
   } else {

/*    Quasi-regular along latitude lines. */

      iquano = klat;
      iregno = klon;
      icode = kcode;
   }

/*     -------------------------------------------------------- */
/**    Section 2. Interpolate field from quasi to regular grid. */
/*     -------------------------------------------------------- */

   i_1 = iquano;
   for (int j230 = 1; j230 <= i_1; ++j230) {

      if (iregno != kpoint[j230]) {

/*       Line contains less values than required,so */
/*       extract quasi-regular grid values for a line */

         i_2 = kpoint[j230];
         for (int j210 = 1; j210 <= i_2; ++j210) {
            ++ilii;
            zline[j210 - 1] = pfield[ilii];
         }

/*       and interpolate this line. */

         rowina2(zline, iregno, kpoint[j230], zwork, icode, msval, kret);
         if (*kret != 0) goto L900;

/*       Add regular grid values for this line to the
         temporary array. */

         i_2 = iregno;
         for (int j220 = 1; j220 <= i_2; ++j220) {
            ++ilio;
            ztemp[ilio - 1] = zline[j220 - 1];
         }

      } else {

/*       Line contains the required number of values, so add */
/*       this line to the temporary array. */

         i_2 = iregno;
         for (int j225 = 1; j225 <= i_2; ++j225) {
            ++ilio;
            ++ilii;
            ztemp[ilio - 1] = pfield[ilii];
         }
      }
   }

   /* Copy temporary array to user array. */

   i_1 = klon * klat;
   for (int j240 = 1; j240 <= i_1; ++j240) {
      pfield[j240] = ztemp[j240 - 1];
   }

/* -------------------------------------------------------- */
/* Section 9. Return to calling routine. Format statements. */
/* -------------------------------------------------------- */

L900:

   Free(zline);
   Free(zwork);

   return 0;
} /* qu2reg2 */



#ifdef T
#undef T
#endif
#define T double
#ifdef T

/* calculate_pfactor: source code from grib_api-1.8.0 */
double TEMPLATE(calculate_pfactor,T)(const T *spectralField, long fieldTruncation, long subsetTruncation)
{
  /*long n_vals = ((fieldTruncation+1)*(fieldTruncation+2));*/
  long loop, index, m, n = 0;
  double zeps = 1.0e-15;
  long ismin = (subsetTruncation+1), ismax = (fieldTruncation+1);
  double weightedSumOverX = 0.0, weightedSumOverY = 0.0, sumOfWeights = 0.0;
  double numerator = 0.0, denominator = 0.0;

  // Setup the weights

  double range = (double) (ismax - ismin +1);

  double *weights = (double*) Malloc(((size_t)ismax+1)*sizeof(double));
  for( loop = ismin; loop <= ismax; loop++ )
    weights[loop] = range / (double) (loop-ismin+1);

  // Compute norms
  // Handle values 2 at a time (real and imaginary parts).
  double *norms = (double*) Malloc(((size_t)ismax+1)*sizeof(double));

  for( loop = 0; loop < ismax+1; loop++ ) norms[loop] = 0.0;

  // Form norms for the rows which contain part of the unscaled subset.

  index = -2;
  for( m = 0; m < subsetTruncation; m++ )
    for( n = m; n <= fieldTruncation; n++ ) {
      index += 2;
      if( n >= subsetTruncation ) {
        double tval = spectralField[index];
        tval=tval<0?-tval:tval;
        norms[n] = norms[n] > tval ? norms[n] : tval;
        tval = spectralField[index+1];
        tval=tval<0?-tval:tval;
        norms[n] = norms[n] > tval ? norms[n] : tval;
      }
    }

  // Form norms for the rows which do not contain part of the unscaled subset.

  for( m = subsetTruncation; m <= fieldTruncation; m++ )
    for( n = m; n <= fieldTruncation; n++ ) {
      double tval = spectralField[index];
      index += 2;
      tval=tval<0?-tval:tval;
      norms[n] = norms[n] > tval ? norms[n] : tval;
      tval = spectralField[index+1];
      tval=tval<0?-tval:tval;
      norms[n] = norms[n] > tval ? norms[n] : tval;
    }

  // Ensure the norms have a value which is not too small in case of problems with math functions (e.g. LOG).

  for( loop = ismin; loop <= ismax; loop++ ) {
    norms[n] = norms[n] > zeps ? norms[n] : zeps;
    if( IS_EQUAL(norms[n], zeps) ) weights[n] = 100.0 * zeps;
  }

  // Do linear fit to find the slope

  for( loop = ismin; loop <= ismax; loop++ ) {
    double x = log( (double) (loop*(loop+1)) );
    double y = log( norms[loop] );
    weightedSumOverX += x * weights[loop];
    weightedSumOverY += y * weights[loop];
    sumOfWeights = sumOfWeights + weights[loop];
  }
  weightedSumOverX /= sumOfWeights;
  weightedSumOverY /= sumOfWeights;

  // Perform a least square fit for the equation

  for( loop = ismin; loop <= ismax; loop++ ) {

    double x = log( (double)(loop*(loop+1)) );
    double y = log( norms[loop] );
    numerator += weights[loop] * (y-weightedSumOverY) * (x-weightedSumOverX);
    denominator += weights[loop] * ((x-weightedSumOverX) * (x-weightedSumOverX));
  }
  double slope = numerator / denominator;

  Free(weights);
  Free(norms);

  double pFactor = -slope;
  if( pFactor < -9999.9 ) pFactor = -9999.9;
  if( pFactor > 9999.9 )  pFactor = 9999.9;

  return pFactor;
}

void TEMPLATE(scale_complex,T)(T *fpdata, int pcStart, int pcScale, int trunc, int inv)
{

  if ( pcScale < -10000 || pcScale > 10000 )
    {
      fprintf(stderr, " %s: Invalid power given %6d\n", __func__, pcScale);
      return;
    }

  // Setup scaling factors = n(n+1)^^p for n = 1 to truncation

  if ( pcScale != 0 )
    {
      double *scale = (double*) Malloc(((size_t)trunc+1)*sizeof(double));
      const double power = (double) pcScale / 1000.;
      scale[0] = 1.0;

      if (pcScale != 1000)
        for (int n = 1; n <= trunc; ++n) scale[n] = pow((double) (n*(n+1)), power);
      else
        for (int n = 1; n <= trunc; ++n) scale[n] =     (double) (n*(n+1));

      if ( inv )
        for (int n = 1; n <= trunc; ++n) scale[n] = 1.0 / scale[n];

      // Scale the values

      size_t index = 0;

      for (int m = 0;   m < pcStart; ++m)
        for (int n = m; n <= trunc; n++, index += 2)
          if ( n >= pcStart )
            {
              fpdata[index  ] = (T)(fpdata[index  ] * scale[n]);
              fpdata[index+1] = (T)(fpdata[index+1] * scale[n]);
            }

      for (int m = pcStart; m <= trunc; ++m)
        for (int n = m;     n <= trunc; n++, index += 2)
          {
            fpdata[index  ] = (T)(fpdata[index  ] * scale[n]);
            fpdata[index+1] = (T)(fpdata[index+1] * scale[n]);
          }
      Free(scale);
    }
}


void TEMPLATE(scatter_complex,T)(T *fpdata, int pcStart, int trunc, int nsp)
{
  T *fphelp = (T*) Malloc((size_t)nsp*sizeof(T));
  size_t inext = 0;
  size_t pcStart_ = pcStart >= 0 ? (size_t)pcStart : 0U;
  size_t trunc_ = trunc >= 0 ? (size_t)trunc : 0U;
  for (size_t m = 0, index = 0; m <= pcStart_; ++m)
    {
      size_t n_copies = pcStart_ <= trunc_ ? (pcStart_ + 1 - m) * 2 : 0;
      for (size_t i = 0; i < n_copies; ++i) fphelp[index + i] = fpdata[inext + i];
      inext += n_copies;
      index += m <= trunc_ ? (trunc_ - m + 1) * 2 : 0;
    }
  for (size_t m = 0, index = 0; m <= trunc_; ++m)
    {
      size_t advIdx = m <= pcStart_ ? (pcStart_ - m + 1) * 2 : 0;
      index += advIdx;
      size_t copyStart = m > pcStart_ ? m : pcStart_ + 1;
      size_t n_copies = copyStart <= trunc_ ? (trunc_ - copyStart + 1) * 2 : 0;
      for (size_t i = 0; i < n_copies; ++i) fphelp[index + i] = fpdata[inext + i];
      inext += n_copies;
      index += n_copies;
    }
  for (size_t m = 0; m < (size_t)nsp; ++m) fpdata[m] = fphelp[m];

  Free(fphelp);
}


void TEMPLATE(gather_complex,T)(T *fpdata, size_t pcStart, size_t trunc, size_t nsp)
{
  T *restrict fphelp = (T*) Malloc(nsp*sizeof(T));
  size_t inext = 0;

  for (size_t m = 0, index = 0;   m <= pcStart; ++m)
    for (size_t n = m; n <= trunc; ++n)
      {
	if ( pcStart >= n )
	  {
	    fphelp[inext++] = fpdata[index];
	    fphelp[inext++] = fpdata[index+1];
	  }
	index += 2;
      }

  for (size_t m = 0, index = 0; m <= trunc; ++m)
    for (size_t n = m; n <= trunc; ++n)
      {
	if ( n > pcStart )
	  {
	    fphelp[inext++] = fpdata[index];
	    fphelp[inext++] = fpdata[index+1];
	  }
	index += 2;
      }

  for (size_t m = 0; m < nsp; ++m) fpdata[m] = fphelp[m];

  Free(fphelp);
}


static void TEMPLATE(scm0,T)(T *pdl, T *pdr, T *pfl, T *pfr, int klg)
{
  /* **** SCM0   - Apply SCM0 limiter to derivative estimates. */
  /* output: */
  /*   pdl   = the limited derivative at the left edge of the interval */
  /*   pdr   = the limited derivative at the right edge of the interval */
  /* inputs */
  /*   pdl   = the original derivative at the left edge */
  /*   pdr   = the original derivative at the right edge */
  /*   pfl   = function value at the left edge of the interval */
  /*   pfr   = function value at the right edge of the interval */
  /*   klg   = number of intervals where the derivatives are limited */

  /*  define constants */

  double zeps = 1.0e-12;
  double zfac = (1.0 - zeps) * 3.0;

  for (int jl = 0; jl < klg; ++jl)
    {
      double r_1;
      if ( (r_1 = pfr[jl] - pfl[jl], fabs(r_1)) > zeps )
	{
	  double zalpha = pdl[jl] / (pfr[jl] - pfl[jl]);
	  double zbeta  = pdr[jl] / (pfr[jl] - pfl[jl]);
	  if ( zalpha <= 0.0 ) pdl[jl] = 0.0;
	  if ( zbeta  <= 0.0 ) pdr[jl] = 0.0;
	  if ( zalpha > zfac ) pdl[jl] = (T)(zfac * (pfr[jl] - pfl[jl]));
	  if ( zbeta  > zfac ) pdr[jl] = (T)(zfac * (pfr[jl] - pfl[jl]));
	}
      else
	{
	  pdl[jl] = 0.0;
	  pdr[jl] = 0.0;
	}
    }
} /* scm0 */

static
int TEMPLATE(rowina3,T)(T *p, int ko, int ki, T *pw,
			int kcode, T msval, int *kret, int omisng, int operio, int oveggy)
{
  /*
C---->
C**** ROWINA3 - Interpolation of row of values.
C
C     Purpose.
C     --------
C
C     Interpolate a row of values.
C
C
C**   Interface.
C     ----------
C
C     CALL ROWINA3( P, KO, KI, PW, KCODE, PMSVAL, KRET, OMISNG, OPERIO)
C
C
C     Input Parameters.
C     -----------------
C
C     P      - Row of values to be interpolated.
C              Dimension must be at least KO.
C
C     KO     - Number of values required.
C
C     KI     - Number of values in P on input.
C
C     PW     - Working array.
C              Dimension must be at least (0:KO+2,3).
C
C     KCODE  - Interpolation required.
C              1 , linear.
C              3 , cubic.
C
C     PMSVAL - Value used for missing data indicator.
C
C     OMISNG - True if missing values are present in field.
C
C     OPERIO - True if input field is periodic.
C
C     OVEGGY - True if 'nearest neighbour' processing must be used
C              for interpolation
C
C     Output Parameters.
C     ------------------
C
C     P     - Now contains KO values.
C     KRET  - Return code
C             0, OK
C             Non-zero, error
C
C
C     Method.
C     -------
C
C     Linear or cubic interpolation performed as required.
C
C     Comments.
C     ---------
C
C     This is a version of ROWINA which allows for missing data
C     values and hence for bitmapped fields.
C
C
C     Author.
C     -------
C
C     J.D.Chambers    ECMWF     22.07.94
C
C
C     Modifications.
C     --------------
C
C     J.D.Chambers    ECMWF     13.09.94
C     Add return code KRET and remove calls to ABORT.
C
C     J. Clochard, Meteo France, for ECMWF - January 1998.
C     Addition of OMISNG and OPERIO arguments.
C
C
C     -----------------------------------------------------------------
*/
  /* System generated locals */
  int pw_dim1, pw_offset, i_1;

  /* Local variables */
  int ip;
  double zwt1, zrdi, zpos;
  double zdo, zwt;

  UNUSED(omisng);

  /* Parameter adjustments */
  --p;
  pw_dim1 = ko + 3;
  pw_offset = pw_dim1;
  pw -= pw_offset;

  *kret = 0;

  if ( kcode == 1 )
    {
      /*    Move input values to work array */
      for (int jl = 1; jl <= ki; ++jl)
	pw[jl + pw_dim1] = p[jl];

      if ( operio )
	{
	  /* Arrange wrap-around value in work array */
	  pw[ki + 1 + pw_dim1] = p[1];

	  /* Set up constants to be used to figure out weighting for */
	  /* values in interpolation. */
	  zrdi = (double) ki;
	  zdo = 1.0 / (double) ko;
	}
      else
	{
	  /* Repeat last value, to cope with "implicit truncation" below */
	  pw[ki + 1 + pw_dim1] = p[ki];

	  /* Set up constants to be used to figure out weighting for */
	  /* values in interpolation. */
	  zrdi = (double) (ki-1);
	  zdo = 1.0 / (double) (ko-1);
 	}

      /*    Loop through the output points */
      for (int jl = 1; jl <= ko; ++jl)
	{

	  /* Calculate weight from the start of row */
	  zpos = (jl - 1) * zdo;
	  zwt = zpos * zrdi;

	  /* Get the current array position(minus 1) from the weight - */
	  /* note the implicit truncation. */
	  ip = (int) zwt;

	  /* Adjust the weight to range (0.0 to 1.0) */
	  zwt -= ip;

          /* If 'nearest neighbour' processing must be used */
	  if ( oveggy )
	    {
              if ( zwt < 0.5 )
                p[jl] = pw[ip + 1 + pw_dim1];
	      else
		p[jl] = pw[ip + 2 + pw_dim1];
	    }
	  else
	    {
	      /*    If the left value is missing, use the right value */
	      if ( IS_EQUAL(pw[ip + 1 + pw_dim1], msval) )
		{
		  p[jl] = pw[ip + 2 + pw_dim1];
		}
	      /*    If the right value is missing, use the left value */
	      else if ( IS_EQUAL(pw[ip + 2 + pw_dim1], msval) )
		{
		  p[jl] = pw[ip + 1 + pw_dim1];
		}
	      /*    If neither missing, interpolate ... */
	      else
		{
		  /*  Interpolate using the weighted values on either side */
		  /*  of the output point position */
		  p[jl] = (T)((1.0 - zwt) * pw[ip+1 + pw_dim1]
                              + zwt * pw[ip+2 + pw_dim1]);
		}
	    }
	}
    }
  else if ( kcode == 3 )
    {
      /*     *******************************    */
      /*     Section 2.  Cubic interpolation .. */
      /*     *******************************    */
      i_1 = ki;
      for (int jl = 1; jl <= i_1; ++jl)
	{
          if ( IS_EQUAL(p[jl], msval) )
	    {
	      fprintf(stderr," ROWINA3: ");
	      fprintf(stderr," Cubic interpolation not supported");
	      fprintf(stderr," for fields containing missing data.\n");
	      *kret = 1;
	      goto L900;
	    }
          pw[jl + pw_dim1] = p[jl];
	}
      pw[pw_dim1] = p[ki];
      pw[ki + 1 + pw_dim1] = p[1];
      pw[ki + 2 + pw_dim1] = p[2];
      i_1 = ki;
      for (int jl = 1; jl <= i_1; ++jl)
	{
          pw[jl + (pw_dim1 << 1)] =
            (T)(- pw[jl - 1 + pw_dim1] / 3.0 -
                pw[jl     + pw_dim1] * 0.5 +
                pw[jl + 1 + pw_dim1] - pw[jl + 2 + pw_dim1] / 6.0);
          pw[jl + 1 + pw_dim1 * 3] =
            (T)(pw[jl - 1 + pw_dim1] / 6.0 -
                pw[jl     + pw_dim1] +
                pw[jl + 1 + pw_dim1] * 0.5 +
                pw[jl + 2 + pw_dim1] / 3.0);
	}

      TEMPLATE(scm0,T)(&pw[(pw_dim1 << 1) + 1], &pw[pw_dim1 * 3 + 2],
		       &pw[pw_dim1 + 1], &pw[pw_dim1 + 2], ki);

      zrdi = (double) ki;
      zdo = 1.0 / (double) ko;
      for (int jl = 1; jl <= ko; ++jl)
	{
          zpos = (jl - 1) * zdo;
          zwt = zpos * zrdi;
          ip = (int) zwt + 1;
          zwt = zwt + 1.0 - ip;
          zwt1 = 1.0 - zwt;
          p[jl] = (T)(((3.0 - zwt1 * 2.0) * pw[ip + pw_dim1] +
                       zwt * pw[ip + (pw_dim1 << 1)]) * zwt1 * zwt1 +
                      ((3.0 - zwt * 2.0) * pw[ip + 1 + pw_dim1] -
                       zwt1 * pw[ip + 1 + pw_dim1 * 3]) * zwt * zwt);
	}

    }
  else
    {
      /*    **************************************    */
      /*    Section 3.  Invalid interpolation code .. */
      /*    **************************************    */
      fprintf(stderr," ROWINA3:");
      fprintf(stderr," Invalid interpolation code = %2d\n",kcode);
      *kret = 2;
    }

L900:
    return 0;
} /* rowina3 */


int TEMPLATE(qu2reg3,T)(T *pfield, int *kpoint, int klat, int klon,
			T msval, int *kret, int omisng, int operio, int oveggy)
{
  /*
C**** QU2REG3 - Convert quasi-regular grid data to regular.
C
C     Purpose.
C     --------
C
C     Convert quasi-regular grid data to regular,
C     using either a linear or cubic interpolation.
C
C
C**   Interface.
C     ----------
C
C     CALL QU2REG3(PFIELD,KPOINT,KLAT,KLON,KCODE,PMSVAL,OMISNG,OPERIO,
C    X            OVEGGY)
C
C
C     Input Parameters.
C     -----------------
C
C     PFIELD     - Array containing quasi-regular grid data.
C
C     KPOINT     - Array containing list of the number of
C                  points on each latitude (or longitude) of
C                  the quasi-regular grid.
C
C     KLAT       - Number of latitude lines
C
C     KLON       - Number of longitude lines
C
C     KCODE      - Interpolation required.
C                  1 , linear - data quasi-regular on latitude lines.
C                  3 , cubic -  data quasi-regular on latitude lines.
C                  11, linear - data quasi-regular on longitude lines.
C                  13, cubic -  data quasi-regular on longitude lines.
C
C     PMSVAL     - Value used for missing data indicator.
C
C     OMISNG     - True if missing values are present in field.
C
C     OPERIO     - True if input field is periodic.
C
C     OVEGGY     - True if 'nearest neighbour' processing must be used
C                  for interpolation
C
C
C     Output Parameters.
C     ------------------
C
C     KRET       - return code
C                  0 = OK
C                  non-zero indicates fatal error
C
C
C     Output Parameters.
C     ------------------
C
C     PFIELD     - Array containing regular grid data.
C
C
C     Method.
C     -------
C
C     Data is interpolated and expanded into a temporary array,
C     which is then copied back into the user's array.
C     Returns an error code if an invalid interpolation is requested
C     or field size exceeds array dimensions.
C
C     Comments.
C     ---------
C
C     This routine is an adaptation of QU2REG to allow missing data
C     values, and hence bit mapped fields.
C
C
C     Author.
C     -------
C
C     J.D.Chambers     ECMWF      22.07.94
C
C
C     Modifications.
C     --------------
C
C     J.D.Chambers     ECMWF      13.09.94
C     Add return code KRET and remove calls to ABORT.
C
C     J.D.Chambers     ECMWF        Feb 1997
C     Allow for 64-bit pointers
C
C     J. Clochard, Meteo France, for ECMWF - January 1998.
C     Addition of OMISNG and OPERIO arguments.
C     Fix message for longitude number out of bounds, and routine
C     name in title and formats.
C
*/
   /* System generated locals */
   int i_1, i_2;
   int kcode = 1;

   /* Local variables */
   int ilii, ilio, icode;
   int iregno, iquano;

   T *ztemp = (T*) Malloc((size_t)klon*(size_t)klat*sizeof(T));
   T *zline = (T*) Malloc(2*(size_t)klon*sizeof(T));
   T *zwork = (T*) Malloc(3*(2*(size_t)klon+3)*sizeof(T));

   /* Parameter adjustments */
   --pfield;
   --kpoint;

/* ------------------------------ */
/* Section 1. Set initial values. */
/* ------------------------------ */

   *kret = 0;

/* Check input parameters. */

   if (kcode != 1 && kcode != 3 && kcode != 11 && kcode != 13) {
      fprintf(stderr," QU2REG :");
      fprintf(stderr," Invalid interpolation type code = %2d\n",kcode);
      *kret = 1;
      goto L900;
   }

/* Set array indices to 0. */

   ilii = 0;
   ilio = 0;

/* Establish values of loop parameters. */

   if (kcode > 10) {

/*    Quasi-regular along longitude lines. */

      iquano = klon;
      iregno = klat;
      icode = kcode - 10;
   } else {

/*    Quasi-regular along latitude lines. */

      iquano = klat;
      iregno = klon;
      icode = kcode;
   }

/*     -------------------------------------------------------- */
/**    Section 2. Interpolate field from quasi to regular grid. */
/*     -------------------------------------------------------- */

   i_1 = iquano;
   for (int j230 = 1; j230 <= i_1; ++j230) {

      if (iregno != kpoint[j230]) {

/*       Line contains less values than required,so */
/*       extract quasi-regular grid values for a line */

         i_2 = kpoint[j230];
         for (int j210 = 1; j210 <= i_2; ++j210) {
            ++ilii;
            zline[j210 - 1] = pfield[ilii];
         }

/*       and interpolate this line. */

         TEMPLATE(rowina3,T)(zline, iregno, kpoint[j230], zwork, icode, msval, kret, omisng, operio , oveggy);
         if (*kret != 0) goto L900;

/*       Add regular grid values for this line to the
         temporary array. */

         i_2 = iregno;
         for (int j220 = 1; j220 <= i_2; ++j220) {
            ++ilio;
            ztemp[ilio - 1] = zline[j220 - 1];
         }

      } else {

/*       Line contains the required number of values, so add */
/*       this line to the temporary array. */

         i_2 = iregno;
         for (int j225 = 1; j225 <= i_2; ++j225) {
            ++ilio;
            ++ilii;
            ztemp[ilio - 1] = pfield[ilii];
         }
      }
   }

/* Copy temporary array to user array. */

   i_1 = klon * klat;
   for (int j240 = 1; j240 <= i_1; ++j240) {
      pfield[j240] = ztemp[j240 - 1];
   }

/* -------------------------------------------------------- */
/* Section 9. Return to calling routine. Format statements. */
/* -------------------------------------------------------- */

L900:

   Free(zwork);
   Free(zline);
   Free(ztemp);

   return 0;
} /* qu2reg3 */

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */

#ifdef T
#undef T
#endif
#define T float
#ifdef T

/* calculate_pfactor: source code from grib_api-1.8.0 */
double TEMPLATE(calculate_pfactor,T)(const T *spectralField, long fieldTruncation, long subsetTruncation)
{
  /*long n_vals = ((fieldTruncation+1)*(fieldTruncation+2));*/
  long loop, index, m, n = 0;
  double zeps = 1.0e-15;
  long ismin = (subsetTruncation+1), ismax = (fieldTruncation+1);
  double weightedSumOverX = 0.0, weightedSumOverY = 0.0, sumOfWeights = 0.0;
  double numerator = 0.0, denominator = 0.0;

  // Setup the weights

  double range = (double) (ismax - ismin +1);

  double *weights = (double*) Malloc(((size_t)ismax+1)*sizeof(double));
  for( loop = ismin; loop <= ismax; loop++ )
    weights[loop] = range / (double) (loop-ismin+1);

  // Compute norms
  // Handle values 2 at a time (real and imaginary parts).
  double *norms = (double*) Malloc(((size_t)ismax+1)*sizeof(double));

  for( loop = 0; loop < ismax+1; loop++ ) norms[loop] = 0.0;

  // Form norms for the rows which contain part of the unscaled subset.

  index = -2;
  for( m = 0; m < subsetTruncation; m++ )
    for( n = m; n <= fieldTruncation; n++ ) {
      index += 2;
      if( n >= subsetTruncation ) {
        double tval = spectralField[index];
        tval=tval<0?-tval:tval;
        norms[n] = norms[n] > tval ? norms[n] : tval;
        tval = spectralField[index+1];
        tval=tval<0?-tval:tval;
        norms[n] = norms[n] > tval ? norms[n] : tval;
      }
    }

  // Form norms for the rows which do not contain part of the unscaled subset.

  for( m = subsetTruncation; m <= fieldTruncation; m++ )
    for( n = m; n <= fieldTruncation; n++ ) {
      double tval = spectralField[index];
      index += 2;
      tval=tval<0?-tval:tval;
      norms[n] = norms[n] > tval ? norms[n] : tval;
      tval = spectralField[index+1];
      tval=tval<0?-tval:tval;
      norms[n] = norms[n] > tval ? norms[n] : tval;
    }

  // Ensure the norms have a value which is not too small in case of problems with math functions (e.g. LOG).

  for( loop = ismin; loop <= ismax; loop++ ) {
    norms[n] = norms[n] > zeps ? norms[n] : zeps;
    if( IS_EQUAL(norms[n], zeps) ) weights[n] = 100.0 * zeps;
  }

  // Do linear fit to find the slope

  for( loop = ismin; loop <= ismax; loop++ ) {
    double x = log( (double) (loop*(loop+1)) );
    double y = log( norms[loop] );
    weightedSumOverX += x * weights[loop];
    weightedSumOverY += y * weights[loop];
    sumOfWeights = sumOfWeights + weights[loop];
  }
  weightedSumOverX /= sumOfWeights;
  weightedSumOverY /= sumOfWeights;

  // Perform a least square fit for the equation

  for( loop = ismin; loop <= ismax; loop++ ) {

    double x = log( (double)(loop*(loop+1)) );
    double y = log( norms[loop] );
    numerator += weights[loop] * (y-weightedSumOverY) * (x-weightedSumOverX);
    denominator += weights[loop] * ((x-weightedSumOverX) * (x-weightedSumOverX));
  }
  double slope = numerator / denominator;

  Free(weights);
  Free(norms);

  double pFactor = -slope;
  if( pFactor < -9999.9 ) pFactor = -9999.9;
  if( pFactor > 9999.9 )  pFactor = 9999.9;

  return pFactor;
}

void TEMPLATE(scale_complex,T)(T *fpdata, int pcStart, int pcScale, int trunc, int inv)
{

  if ( pcScale < -10000 || pcScale > 10000 )
    {
      fprintf(stderr, " %s: Invalid power given %6d\n", __func__, pcScale);
      return;
    }

  // Setup scaling factors = n(n+1)^^p for n = 1 to truncation

  if ( pcScale != 0 )
    {
      double *scale = (double*) Malloc(((size_t)trunc+1)*sizeof(double));
      const double power = (double) pcScale / 1000.;
      scale[0] = 1.0;

      if (pcScale != 1000)
        for (int n = 1; n <= trunc; ++n) scale[n] = pow((double) (n*(n+1)), power);
      else
        for (int n = 1; n <= trunc; ++n) scale[n] =     (double) (n*(n+1));

      if ( inv )
        for (int n = 1; n <= trunc; ++n) scale[n] = 1.0 / scale[n];

      // Scale the values

      size_t index = 0;

      for (int m = 0;   m < pcStart; ++m)
        for (int n = m; n <= trunc; n++, index += 2)
          if ( n >= pcStart )
            {
              fpdata[index  ] = (T)(fpdata[index  ] * scale[n]);
              fpdata[index+1] = (T)(fpdata[index+1] * scale[n]);
            }

      for (int m = pcStart; m <= trunc; ++m)
        for (int n = m;     n <= trunc; n++, index += 2)
          {
            fpdata[index  ] = (T)(fpdata[index  ] * scale[n]);
            fpdata[index+1] = (T)(fpdata[index+1] * scale[n]);
          }
      Free(scale);
    }
}


void TEMPLATE(scatter_complex,T)(T *fpdata, int pcStart, int trunc, int nsp)
{
  T *fphelp = (T*) Malloc((size_t)nsp*sizeof(T));
  size_t inext = 0;
  size_t pcStart_ = pcStart >= 0 ? (size_t)pcStart : 0U;
  size_t trunc_ = trunc >= 0 ? (size_t)trunc : 0U;
  for (size_t m = 0, index = 0; m <= pcStart_; ++m)
    {
      size_t n_copies = pcStart_ <= trunc_ ? (pcStart_ + 1 - m) * 2 : 0;
      for (size_t i = 0; i < n_copies; ++i) fphelp[index + i] = fpdata[inext + i];
      inext += n_copies;
      index += m <= trunc_ ? (trunc_ - m + 1) * 2 : 0;
    }
  for (size_t m = 0, index = 0; m <= trunc_; ++m)
    {
      size_t advIdx = m <= pcStart_ ? (pcStart_ - m + 1) * 2 : 0;
      index += advIdx;
      size_t copyStart = m > pcStart_ ? m : pcStart_ + 1;
      size_t n_copies = copyStart <= trunc_ ? (trunc_ - copyStart + 1) * 2 : 0;
      for (size_t i = 0; i < n_copies; ++i) fphelp[index + i] = fpdata[inext + i];
      inext += n_copies;
      index += n_copies;
    }
  for (size_t m = 0; m < (size_t)nsp; ++m) fpdata[m] = fphelp[m];

  Free(fphelp);
}


void TEMPLATE(gather_complex,T)(T *fpdata, size_t pcStart, size_t trunc, size_t nsp)
{
  T *restrict fphelp = (T*) Malloc(nsp*sizeof(T));
  size_t inext = 0;

  for (size_t m = 0, index = 0;   m <= pcStart; ++m)
    for (size_t n = m; n <= trunc; ++n)
      {
	if ( pcStart >= n )
	  {
	    fphelp[inext++] = fpdata[index];
	    fphelp[inext++] = fpdata[index+1];
	  }
	index += 2;
      }

  for (size_t m = 0, index = 0; m <= trunc; ++m)
    for (size_t n = m; n <= trunc; ++n)
      {
	if ( n > pcStart )
	  {
	    fphelp[inext++] = fpdata[index];
	    fphelp[inext++] = fpdata[index+1];
	  }
	index += 2;
      }

  for (size_t m = 0; m < nsp; ++m) fpdata[m] = fphelp[m];

  Free(fphelp);
}


static void TEMPLATE(scm0,T)(T *pdl, T *pdr, T *pfl, T *pfr, int klg)
{
  /* **** SCM0   - Apply SCM0 limiter to derivative estimates. */
  /* output: */
  /*   pdl   = the limited derivative at the left edge of the interval */
  /*   pdr   = the limited derivative at the right edge of the interval */
  /* inputs */
  /*   pdl   = the original derivative at the left edge */
  /*   pdr   = the original derivative at the right edge */
  /*   pfl   = function value at the left edge of the interval */
  /*   pfr   = function value at the right edge of the interval */
  /*   klg   = number of intervals where the derivatives are limited */

  /*  define constants */

  double zeps = 1.0e-12;
  double zfac = (1.0 - zeps) * 3.0;

  for (int jl = 0; jl < klg; ++jl)
    {
      double r_1;
      if ( (r_1 = pfr[jl] - pfl[jl], fabs(r_1)) > zeps )
	{
	  double zalpha = pdl[jl] / (pfr[jl] - pfl[jl]);
	  double zbeta  = pdr[jl] / (pfr[jl] - pfl[jl]);
	  if ( zalpha <= 0.0 ) pdl[jl] = 0.0;
	  if ( zbeta  <= 0.0 ) pdr[jl] = 0.0;
	  if ( zalpha > zfac ) pdl[jl] = (T)(zfac * (pfr[jl] - pfl[jl]));
	  if ( zbeta  > zfac ) pdr[jl] = (T)(zfac * (pfr[jl] - pfl[jl]));
	}
      else
	{
	  pdl[jl] = 0.0;
	  pdr[jl] = 0.0;
	}
    }
} /* scm0 */

static
int TEMPLATE(rowina3,T)(T *p, int ko, int ki, T *pw,
			int kcode, T msval, int *kret, int omisng, int operio, int oveggy)
{
  /*
C---->
C**** ROWINA3 - Interpolation of row of values.
C
C     Purpose.
C     --------
C
C     Interpolate a row of values.
C
C
C**   Interface.
C     ----------
C
C     CALL ROWINA3( P, KO, KI, PW, KCODE, PMSVAL, KRET, OMISNG, OPERIO)
C
C
C     Input Parameters.
C     -----------------
C
C     P      - Row of values to be interpolated.
C              Dimension must be at least KO.
C
C     KO     - Number of values required.
C
C     KI     - Number of values in P on input.
C
C     PW     - Working array.
C              Dimension must be at least (0:KO+2,3).
C
C     KCODE  - Interpolation required.
C              1 , linear.
C              3 , cubic.
C
C     PMSVAL - Value used for missing data indicator.
C
C     OMISNG - True if missing values are present in field.
C
C     OPERIO - True if input field is periodic.
C
C     OVEGGY - True if 'nearest neighbour' processing must be used
C              for interpolation
C
C     Output Parameters.
C     ------------------
C
C     P     - Now contains KO values.
C     KRET  - Return code
C             0, OK
C             Non-zero, error
C
C
C     Method.
C     -------
C
C     Linear or cubic interpolation performed as required.
C
C     Comments.
C     ---------
C
C     This is a version of ROWINA which allows for missing data
C     values and hence for bitmapped fields.
C
C
C     Author.
C     -------
C
C     J.D.Chambers    ECMWF     22.07.94
C
C
C     Modifications.
C     --------------
C
C     J.D.Chambers    ECMWF     13.09.94
C     Add return code KRET and remove calls to ABORT.
C
C     J. Clochard, Meteo France, for ECMWF - January 1998.
C     Addition of OMISNG and OPERIO arguments.
C
C
C     -----------------------------------------------------------------
*/
  /* System generated locals */
  int pw_dim1, pw_offset, i_1;

  /* Local variables */
  int ip;
  double zwt1, zrdi, zpos;
  double zdo, zwt;

  UNUSED(omisng);

  /* Parameter adjustments */
  --p;
  pw_dim1 = ko + 3;
  pw_offset = pw_dim1;
  pw -= pw_offset;

  *kret = 0;

  if ( kcode == 1 )
    {
      /*    Move input values to work array */
      for (int jl = 1; jl <= ki; ++jl)
	pw[jl + pw_dim1] = p[jl];

      if ( operio )
	{
	  /* Arrange wrap-around value in work array */
	  pw[ki + 1 + pw_dim1] = p[1];

	  /* Set up constants to be used to figure out weighting for */
	  /* values in interpolation. */
	  zrdi = (double) ki;
	  zdo = 1.0 / (double) ko;
	}
      else
	{
	  /* Repeat last value, to cope with "implicit truncation" below */
	  pw[ki + 1 + pw_dim1] = p[ki];

	  /* Set up constants to be used to figure out weighting for */
	  /* values in interpolation. */
	  zrdi = (double) (ki-1);
	  zdo = 1.0 / (double) (ko-1);
 	}

      /*    Loop through the output points */
      for (int jl = 1; jl <= ko; ++jl)
	{

	  /* Calculate weight from the start of row */
	  zpos = (jl - 1) * zdo;
	  zwt = zpos * zrdi;

	  /* Get the current array position(minus 1) from the weight - */
	  /* note the implicit truncation. */
	  ip = (int) zwt;

	  /* Adjust the weight to range (0.0 to 1.0) */
	  zwt -= ip;

          /* If 'nearest neighbour' processing must be used */
	  if ( oveggy )
	    {
              if ( zwt < 0.5 )
                p[jl] = pw[ip + 1 + pw_dim1];
	      else
		p[jl] = pw[ip + 2 + pw_dim1];
	    }
	  else
	    {
	      /*    If the left value is missing, use the right value */
	      if ( IS_EQUAL(pw[ip + 1 + pw_dim1], msval) )
		{
		  p[jl] = pw[ip + 2 + pw_dim1];
		}
	      /*    If the right value is missing, use the left value */
	      else if ( IS_EQUAL(pw[ip + 2 + pw_dim1], msval) )
		{
		  p[jl] = pw[ip + 1 + pw_dim1];
		}
	      /*    If neither missing, interpolate ... */
	      else
		{
		  /*  Interpolate using the weighted values on either side */
		  /*  of the output point position */
		  p[jl] = (T)((1.0 - zwt) * pw[ip+1 + pw_dim1]
                              + zwt * pw[ip+2 + pw_dim1]);
		}
	    }
	}
    }
  else if ( kcode == 3 )
    {
      /*     *******************************    */
      /*     Section 2.  Cubic interpolation .. */
      /*     *******************************    */
      i_1 = ki;
      for (int jl = 1; jl <= i_1; ++jl)
	{
          if ( IS_EQUAL(p[jl], msval) )
	    {
	      fprintf(stderr," ROWINA3: ");
	      fprintf(stderr," Cubic interpolation not supported");
	      fprintf(stderr," for fields containing missing data.\n");
	      *kret = 1;
	      goto L900;
	    }
          pw[jl + pw_dim1] = p[jl];
	}
      pw[pw_dim1] = p[ki];
      pw[ki + 1 + pw_dim1] = p[1];
      pw[ki + 2 + pw_dim1] = p[2];
      i_1 = ki;
      for (int jl = 1; jl <= i_1; ++jl)
	{
          pw[jl + (pw_dim1 << 1)] =
            (T)(- pw[jl - 1 + pw_dim1] / 3.0 -
                pw[jl     + pw_dim1] * 0.5 +
                pw[jl + 1 + pw_dim1] - pw[jl + 2 + pw_dim1] / 6.0);
          pw[jl + 1 + pw_dim1 * 3] =
            (T)(pw[jl - 1 + pw_dim1] / 6.0 -
                pw[jl     + pw_dim1] +
                pw[jl + 1 + pw_dim1] * 0.5 +
                pw[jl + 2 + pw_dim1] / 3.0);
	}

      TEMPLATE(scm0,T)(&pw[(pw_dim1 << 1) + 1], &pw[pw_dim1 * 3 + 2],
		       &pw[pw_dim1 + 1], &pw[pw_dim1 + 2], ki);

      zrdi = (double) ki;
      zdo = 1.0 / (double) ko;
      for (int jl = 1; jl <= ko; ++jl)
	{
          zpos = (jl - 1) * zdo;
          zwt = zpos * zrdi;
          ip = (int) zwt + 1;
          zwt = zwt + 1.0 - ip;
          zwt1 = 1.0 - zwt;
          p[jl] = (T)(((3.0 - zwt1 * 2.0) * pw[ip + pw_dim1] +
                       zwt * pw[ip + (pw_dim1 << 1)]) * zwt1 * zwt1 +
                      ((3.0 - zwt * 2.0) * pw[ip + 1 + pw_dim1] -
                       zwt1 * pw[ip + 1 + pw_dim1 * 3]) * zwt * zwt);
	}

    }
  else
    {
      /*    **************************************    */
      /*    Section 3.  Invalid interpolation code .. */
      /*    **************************************    */
      fprintf(stderr," ROWINA3:");
      fprintf(stderr," Invalid interpolation code = %2d\n",kcode);
      *kret = 2;
    }

L900:
    return 0;
} /* rowina3 */


int TEMPLATE(qu2reg3,T)(T *pfield, int *kpoint, int klat, int klon,
			T msval, int *kret, int omisng, int operio, int oveggy)
{
  /*
C**** QU2REG3 - Convert quasi-regular grid data to regular.
C
C     Purpose.
C     --------
C
C     Convert quasi-regular grid data to regular,
C     using either a linear or cubic interpolation.
C
C
C**   Interface.
C     ----------
C
C     CALL QU2REG3(PFIELD,KPOINT,KLAT,KLON,KCODE,PMSVAL,OMISNG,OPERIO,
C    X            OVEGGY)
C
C
C     Input Parameters.
C     -----------------
C
C     PFIELD     - Array containing quasi-regular grid data.
C
C     KPOINT     - Array containing list of the number of
C                  points on each latitude (or longitude) of
C                  the quasi-regular grid.
C
C     KLAT       - Number of latitude lines
C
C     KLON       - Number of longitude lines
C
C     KCODE      - Interpolation required.
C                  1 , linear - data quasi-regular on latitude lines.
C                  3 , cubic -  data quasi-regular on latitude lines.
C                  11, linear - data quasi-regular on longitude lines.
C                  13, cubic -  data quasi-regular on longitude lines.
C
C     PMSVAL     - Value used for missing data indicator.
C
C     OMISNG     - True if missing values are present in field.
C
C     OPERIO     - True if input field is periodic.
C
C     OVEGGY     - True if 'nearest neighbour' processing must be used
C                  for interpolation
C
C
C     Output Parameters.
C     ------------------
C
C     KRET       - return code
C                  0 = OK
C                  non-zero indicates fatal error
C
C
C     Output Parameters.
C     ------------------
C
C     PFIELD     - Array containing regular grid data.
C
C
C     Method.
C     -------
C
C     Data is interpolated and expanded into a temporary array,
C     which is then copied back into the user's array.
C     Returns an error code if an invalid interpolation is requested
C     or field size exceeds array dimensions.
C
C     Comments.
C     ---------
C
C     This routine is an adaptation of QU2REG to allow missing data
C     values, and hence bit mapped fields.
C
C
C     Author.
C     -------
C
C     J.D.Chambers     ECMWF      22.07.94
C
C
C     Modifications.
C     --------------
C
C     J.D.Chambers     ECMWF      13.09.94
C     Add return code KRET and remove calls to ABORT.
C
C     J.D.Chambers     ECMWF        Feb 1997
C     Allow for 64-bit pointers
C
C     J. Clochard, Meteo France, for ECMWF - January 1998.
C     Addition of OMISNG and OPERIO arguments.
C     Fix message for longitude number out of bounds, and routine
C     name in title and formats.
C
*/
   /* System generated locals */
   int i_1, i_2;
   int kcode = 1;

   /* Local variables */
   int ilii, ilio, icode;
   int iregno, iquano;

   T *ztemp = (T*) Malloc((size_t)klon*(size_t)klat*sizeof(T));
   T *zline = (T*) Malloc(2*(size_t)klon*sizeof(T));
   T *zwork = (T*) Malloc(3*(2*(size_t)klon+3)*sizeof(T));

   /* Parameter adjustments */
   --pfield;
   --kpoint;

/* ------------------------------ */
/* Section 1. Set initial values. */
/* ------------------------------ */

   *kret = 0;

/* Check input parameters. */

   if (kcode != 1 && kcode != 3 && kcode != 11 && kcode != 13) {
      fprintf(stderr," QU2REG :");
      fprintf(stderr," Invalid interpolation type code = %2d\n",kcode);
      *kret = 1;
      goto L900;
   }

/* Set array indices to 0. */

   ilii = 0;
   ilio = 0;

/* Establish values of loop parameters. */

   if (kcode > 10) {

/*    Quasi-regular along longitude lines. */

      iquano = klon;
      iregno = klat;
      icode = kcode - 10;
   } else {

/*    Quasi-regular along latitude lines. */

      iquano = klat;
      iregno = klon;
      icode = kcode;
   }

/*     -------------------------------------------------------- */
/**    Section 2. Interpolate field from quasi to regular grid. */
/*     -------------------------------------------------------- */

   i_1 = iquano;
   for (int j230 = 1; j230 <= i_1; ++j230) {

      if (iregno != kpoint[j230]) {

/*       Line contains less values than required,so */
/*       extract quasi-regular grid values for a line */

         i_2 = kpoint[j230];
         for (int j210 = 1; j210 <= i_2; ++j210) {
            ++ilii;
            zline[j210 - 1] = pfield[ilii];
         }

/*       and interpolate this line. */

         TEMPLATE(rowina3,T)(zline, iregno, kpoint[j230], zwork, icode, msval, kret, omisng, operio , oveggy);
         if (*kret != 0) goto L900;

/*       Add regular grid values for this line to the
         temporary array. */

         i_2 = iregno;
         for (int j220 = 1; j220 <= i_2; ++j220) {
            ++ilio;
            ztemp[ilio - 1] = zline[j220 - 1];
         }

      } else {

/*       Line contains the required number of values, so add */
/*       this line to the temporary array. */

         i_2 = iregno;
         for (int j225 = 1; j225 <= i_2; ++j225) {
            ++ilio;
            ++ilii;
            ztemp[ilio - 1] = pfield[ilii];
         }
      }
   }

/* Copy temporary array to user array. */

   i_1 = klon * klat;
   for (int j240 = 1; j240 <= i_1; ++j240) {
      pfield[j240] = ztemp[j240 - 1];
   }

/* -------------------------------------------------------- */
/* Section 9. Return to calling routine. Format statements. */
/* -------------------------------------------------------- */

L900:

   Free(zwork);
   Free(zline);
   Free(ztemp);

   return 0;
} /* qu2reg3 */

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */

// clang-format on
#include <string.h>

int
gribVersion(unsigned char *is, size_t buffersize)
{
  if (buffersize < 8) Error("Buffer too small (current size %d)!", (int) buffersize);

  return GRIB_EDITION(is);
}

static double
GET_Real(unsigned char *grib)
{
  int iexp = GET_UINT1(grib[0]);
  int imant = (int) (GET_UINT3(grib[1], grib[2], grib[3]));

  return decfp2(iexp, imant);
}

static size_t
decodeIS(unsigned char *is, int *isec0, int *iret)
{
  // Octets 1 - 4 : The letters G R I B. Four 8 bit fields.

  // Check letters -> GRIB, BUDG or TIDE.

  // Check that 'GRIB' is found where expected.
  bool lgrib = GRIB_START(is);

  // ECMWF pseudo-grib data uses 'BUDG' and 'TIDE'.
  bool lbudg = BUDG_START(is);
  bool ltide = TIDE_START(is);

  // Data is not GRIB or pseudo-grib.
  if (lgrib == false && lbudg == false && ltide == false)
  {
    *iret = 305;
    gprintf(__func__, "Input data is not GRIB or pseudo-grib.");
    gprintf(__func__, "Return code = %d", *iret);
  }
  if (lbudg || ltide)
  {
    *iret = 305;
    gprintf(__func__, "Pseudo-grib data unsupported.");
    gprintf(__func__, "Return code = %d", *iret);
  }

  // Octets 5 - 7 : Length of message. One 24 bit field.
  ISEC0_GRIB_Len = (int) (GRIB1_SECLEN(is));

  // Octet 8 : GRIB Edition Number. One 8 bit field.
  ISEC0_GRIB_Version = GRIB_EDITION(is);

  if (ISEC0_GRIB_Version > 1) Error("GRIB version %d unsupported!", ISEC0_GRIB_Version);

  int grib1offset = ISEC0_GRIB_Version * 4;

  size_t isLen = 4 + (size_t) grib1offset;

  return isLen;
}

static void
decodePDS_ECMWF_local_Extension_1(unsigned char *pds, int *isec1)
{
  isec1[36] = GET_UINT1(pds[40]);                  /* extension identifier       */
  isec1[37] = GET_UINT1(pds[41]);                  /* Class                      */
  isec1[38] = GET_UINT1(pds[42]);                  /* Type                       */
  isec1[39] = (int) (GET_UINT2(pds[43], pds[44])); /* Stream                     */
  /* isec1[40] = GET_UINT4(pds[45],pds[46],pds[47],pds[48]); */
  memcpy((char *) &isec1[40], &pds[45], 4);
  isec1[41] = GET_UINT1(pds[49]); /* Forecast number            */
  isec1[42] = GET_UINT1(pds[50]); /* Total number of forecasts  */
}

static void
decodePDS_DWD_local_Extension_254(unsigned char *pds, int *isec1)
{
  isec1[36] = GET_UINT1(pds[40]); /* extension identifier */
  for (int i = 0; i < 11; ++i) isec1[37 + i] = GET_UINT1(pds[41 + i]);

  int isvn = (int) (GET_UINT2(pds[52], pds[53]));

  isec1[48] = isvn % 0x8000; /* DWD experiment identifier            */
  isec1[49] = isvn >> 15;    /* DWD run type (0=main, 2=ass, 3=test) */
}

static void
decodePDS_DWD_local_Extension_253(unsigned char *pds, int *isec1)
{
  isec1[36] = GET_UINT1(pds[40]); /* extension identifier */
  for (int i = 0; i < 11; ++i) isec1[37 + i] = GET_UINT1(pds[41 + i]);

  int isvn = (int) (GET_UINT2(pds[52], pds[53]));

  isec1[48] = isvn % 0x8000;                       /* DWD experiment identifier            */
  isec1[49] = isvn >> 15;                          /* DWD run type (0=main, 2=ass, 3=test) */
  isec1[50] = GET_UINT1(pds[54]);                  /* User id, specified by table          */
  isec1[51] = (int) (GET_UINT2(pds[55], pds[56])); /* Experiment identifier                */
  isec1[52] = (int) (GET_UINT2(pds[57], pds[58])); /* Ensemble identification by table     */
  isec1[53] = (int) (GET_UINT2(pds[59], pds[60])); /* Number of ensemble members           */
  isec1[54] = (int) (GET_UINT2(pds[61], pds[62])); /* Actual number of ensemble member     */
  isec1[55] = GET_UINT1(pds[63]);                  /* Model major version number           */
  isec1[56] = GET_UINT1(pds[64]);                  /* Model minor version number           */
}

static void
decodePDS_MPIM_local_Extension_1(unsigned char *pds, int *isec1)
{
  isec1[36] = GET_UINT1(pds[40]);                  /* extension identifier            */
  isec1[37] = GET_UINT1(pds[41]);                  /* type of ensemble forecast       */
  isec1[38] = (int) (GET_UINT2(pds[42], pds[43])); /* individual ensemble member      */
  isec1[39] = (int) (GET_UINT2(pds[44], pds[45])); /* number of forecasts in ensemble */
}

static size_t
decodePDS(unsigned char *pds, int *isec0, int *isec1)
{
  size_t pdsLen = PDS_Len;

  // clang-format off
  ISEC1_CodeTable      = PDS_CodeTable;
  ISEC1_CenterID       = PDS_CenterID;
  ISEC1_ModelID        = PDS_ModelID;
  ISEC1_GridDefinition = PDS_GridDefinition;
  ISEC1_Sec2Or3Flag    = PDS_Sec2Or3Flag;
  ISEC1_Parameter      = PDS_Parameter;
  ISEC1_LevelType      = PDS_LevelType;

  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 != GRIB1_LTYPE_SEADEPTH)     && 
       (ISEC1_LevelType != 210) )
    {
      ISEC1_Level1 = PDS_Level1;
      ISEC1_Level2 = PDS_Level2;
    }
  else
    {
      ISEC1_Level1 = (int)(PDS_Level);
      ISEC1_Level2 = 0;
    }

  /* ISEC1_Year        = PDS_Year; */
  ISEC1_Month          = PDS_Month;
  ISEC1_Day            = PDS_Day;
  ISEC1_Hour           = PDS_Hour;
  ISEC1_Minute         = PDS_Minute;
  ISEC1_TimeUnit       = PDS_TimeUnit;
  ISEC1_TimePeriod1    = PDS_TimePeriod1;
  ISEC1_TimePeriod2    = PDS_TimePeriod2;
  ISEC1_TimeRange      = PDS_TimeRange;
  ISEC1_AvgNum         = (int)(PDS_AvgNum);
  ISEC1_AvgMiss        = PDS_AvgMiss;

  if ( ISEC0_GRIB_Version == 1 )
    {
      ISEC1_Year           = PDS_Year;
      ISEC1_Century        = PDS_Century;
      ISEC1_SubCenterID    = PDS_Subcenter;
      ISEC1_DecScaleFactor = PDS_DecimalScale;
    }
  else
    {
      int year             = GET_UINT1(pds[12]);
      if ( year <= 100 )
	{
	  ISEC1_Year       = year;
	  ISEC1_Century    = 1;
	}
      else
	{
	  ISEC1_Year       = year%100;
	  ISEC1_Century    = 1 + (year-ISEC1_Year)/100;
	}
      ISEC1_SubCenterID    = 0;
      ISEC1_DecScaleFactor = 0;
    }

  if ( ISEC1_Year < 0 )
    {
      ISEC1_Year    = -ISEC1_Year;
      ISEC1_Century = -ISEC1_Century;
    }

  ISEC1_LocalFLag = 0;
  if ( pdsLen > 28 )
    {
      size_t localextlen = pdsLen-28;

      if ( localextlen > 4000 )
	{
	  Warning("PDS larger than 4000 bytes not supported!");
	}
      else
	{
	  ISEC1_LocalFLag = 1;

	  if ( ISEC1_CenterID == 78 || ISEC1_CenterID == 215 || ISEC1_CenterID == 250 )
	    {
	      if ( pds[40] == 254 ) 
                decodePDS_DWD_local_Extension_254(pds, isec1);
	      else if ( pds[40] == 253 )
                decodePDS_DWD_local_Extension_253(pds, isec1);
	    }
	  else if ( (ISEC1_CenterID    == 98 && ISEC1_LocalFLag ==  1) ||
		    (ISEC1_SubCenterID == 98 && ISEC1_LocalFLag ==  1) ||
		    (ISEC1_CenterID    ==  7 && ISEC1_SubCenterID == 98) )
	    {
	      if ( pds[40] == 1 )
		decodePDS_ECMWF_local_Extension_1(pds, isec1);
	    }
	  else if ( ISEC1_CenterID    == 252 && ISEC1_LocalFLag ==  1 )
	    {
	      if ( pds[40] == 1 )
		decodePDS_MPIM_local_Extension_1(pds, isec1);	      
	    }
	  else
	    {
	      for ( size_t i = 0; i < localextlen; i++ )
                isec1[24+i] = pds[28+i];
	    }
	}
    }
  // clang-format on

  return pdsLen;
}

static void
gribPrintSec2_double(int *isec0, int *isec2, double *fsec2)
{
  gribPrintSec2DP(isec0, isec2, fsec2);
}
static void
gribPrintSec3_double(int *isec0, int *isec3, double *fsec3)
{
  gribPrintSec3DP(isec0, isec3, fsec3);
}
static void
gribPrintSec4_double(int *isec0, int *isec4, double *fsec4)
{
  gribPrintSec4DP(isec0, isec4, fsec4);
}
static void
gribPrintSec2_float(int *isec0, int *isec2, float *fsec2)
{
  gribPrintSec2SP(isec0, isec2, fsec2);
}
static void
gribPrintSec3_float(int *isec0, int *isec3, float *fsec3)
{
  gribPrintSec3SP(isec0, isec3, fsec3);
}
static void
gribPrintSec4_float(int *isec0, int *isec4, float *fsec4)
{
  gribPrintSec4SP(isec0, isec4, fsec4);
}

// clang-format off

#ifdef T
#undef T
#endif
#define T double
#ifdef T

#include <inttypes.h>

static 
void TEMPLATE(decode_array_common,T)(const unsigned char *restrict igrib, long jlend, int NumBits, 
				     T fmin, T zscale, T *restrict fpdata)
{
  /* code from wgrib routine BDS_unpack */
  const unsigned char *bits = igrib;
  unsigned int tbits = 0;
  int n_bits = NumBits;
  int t_bits = 0;

  const unsigned jmask = (1U << n_bits) - 1U;
  for (long i = 0; i < jlend; ++i)
    {
      if (n_bits - t_bits > 8)
	{
	  tbits = (tbits << 16) | ((unsigned)bits[0] << 8) | ((unsigned)bits[1]);
	  bits += 2;
	  t_bits += 16;
	}

      while ( t_bits < n_bits )
	{
	  tbits = (tbits * 256) + *bits++;
	  t_bits += 8;
	}
      t_bits -= n_bits;
      fpdata[i] = (float)((tbits >> t_bits) & jmask);
    }
  // at least this vectorizes :)
  for (long i = 0; i < jlend; ++i)
    fpdata[i] = fmin + zscale*fpdata[i];
}

static
void TEMPLATE(decode_array_common2,T)(const unsigned char *restrict igrib, long jlend, int NumBits,
				      T fmin, T zscale, T *restrict fpdata)
{
  static const unsigned mask[] = {0,1,3,7,15,31,63,127,255};
  static const double shift[9] = {1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0};

  // code from wgrib routine BDS_unpack
  const unsigned char *bits = igrib;
  int n_bits = NumBits;
  int c_bits, j_bits;

  // older unoptimized code, not often used
  c_bits = 8;
  for (long i = 0; i < jlend; ++i)
    {
      double jj = 0.0;
      j_bits = n_bits;
      while (c_bits <= j_bits)
	{
	  if (c_bits == 8)
	    {
	      jj = jj * 256.0  + (double) (*bits++);
	      j_bits -= 8;
	    }
	  else
	    {
	      jj = (jj * shift[c_bits]) + (double) (*bits & mask[c_bits]);
	      bits++;
	      j_bits -= c_bits;
	      c_bits = 8;
	    }
	}

      if (j_bits)
	{
	  c_bits -= j_bits;
	  jj = (jj * shift[j_bits]) + (double) (((unsigned)*bits >> c_bits) & mask[j_bits]);
	}
      fpdata[i] = (T)(fmin + zscale*jj);
    }
}

static
void TEMPLATE(decode_array_2byte,T)(size_t jlend, const unsigned char *restrict igrib,
                                    T *fpdata, T fmin, T zscale)
{
  const uint16_t *restrict sgrib = (const uint16_t *)(const void *)(igrib);

  if ( IS_BIGENDIAN() )
    {
      for (size_t i = 0; i < jlend; ++i)
        {
          fpdata[i] = fmin + zscale * sgrib[i];
        }
    }
  else
    {
      for (size_t i = 0; i < jlend; ++i)
        {
          uint16_t ui16 = gribSwapByteOrder_uint16(sgrib[i]);
          fpdata[i] = fmin + zscale * ui16;
        }
    }
}

static 
void TEMPLATE(decode_array,T)(const unsigned char *restrict igrib, long jlend, int numBits, 
			      T fmin, T zscale, T *restrict fpdata)
{
#if defined _GET_X86_COUNTER || defined _GET_MACH_COUNTER 
  uint64_t start_decode, end_decode;
#endif

#ifdef VECTORCODE
  GRIBPACK *lgrib = NULL;

  if ( numBits%8 == 0 )
    {
      long jlenc = jlend * numBits / 8;
      if ( jlenc > 0 ) 
	{
	  lgrib = (GRIBPACK*) Malloc(jlenc*sizeof(GRIBPACK));
	  if ( lgrib == NULL ) SysError("No Memory!");

	  (void) UNPACK_GRIB(igrib, lgrib, jlenc, -1L);
	}
    }

  if ( numBits ==  0 )
    {
      for (long i = 0; i < jlend; ++i)
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (int)lgrib[i];
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 16 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (((int)lgrib[2*i  ] <<  8) +  (int)lgrib[2*i+1]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 24 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (((int)lgrib[3*i  ] << 16) + ((int)lgrib[3*i+1] <<  8) +
	  	 (int)lgrib[3*i+2]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 32 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (((unsigned int)lgrib[4*i  ] << 24) + ((unsigned int)lgrib[4*i+1] << 16) +
		((unsigned int)lgrib[4*i+2] <<  8) +  (unsigned int)lgrib[4*i+3]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits <= 25 )
    {
      TEMPLATE(decode_array_common,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else if ( numBits > 25 && numBits < 32 )
    {
      TEMPLATE(decode_array_common2,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }

  if ( lgrib ) Free(lgrib);

#else
  if ( numBits ==  0 )
    {
      for (long i = 0; i < jlend; ++i)
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (int)igrib[i];
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 16 )
    {
      TEMPLATE(decode_array_2byte,T)((size_t) jlend, igrib, fpdata, fmin, zscale);
    }
  else if ( numBits == 24 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (T)(((int)igrib[3*i  ] << 16) + ((int)igrib[3*i+1] <<  8) +
                     (int)igrib[3*i+2]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 32 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (T)(((unsigned int)igrib[4*i  ] << 24) + ((unsigned int)igrib[4*i+1] << 16) +
                     ((unsigned int)igrib[4*i+2] <<  8) +  (unsigned int)igrib[4*i+3]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits <= 25 )
    {
      TEMPLATE(decode_array_common,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else if ( numBits > 25 && numBits < 32 )
    {
      TEMPLATE(decode_array_common2,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }
#endif
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */

#ifdef T
#undef T
#endif
#define T float
#ifdef T

#include <inttypes.h>

static 
void TEMPLATE(decode_array_common,T)(const unsigned char *restrict igrib, long jlend, int NumBits, 
				     T fmin, T zscale, T *restrict fpdata)
{
  /* code from wgrib routine BDS_unpack */
  const unsigned char *bits = igrib;
  unsigned int tbits = 0;
  int n_bits = NumBits;
  int t_bits = 0;

  const unsigned jmask = (1U << n_bits) - 1U;
  for (long i = 0; i < jlend; ++i)
    {
      if (n_bits - t_bits > 8)
	{
	  tbits = (tbits << 16) | ((unsigned)bits[0] << 8) | ((unsigned)bits[1]);
	  bits += 2;
	  t_bits += 16;
	}

      while ( t_bits < n_bits )
	{
	  tbits = (tbits * 256) + *bits++;
	  t_bits += 8;
	}
      t_bits -= n_bits;
      fpdata[i] = (float)((tbits >> t_bits) & jmask);
    }
  // at least this vectorizes :)
  for (long i = 0; i < jlend; ++i)
    fpdata[i] = fmin + zscale*fpdata[i];
}

static
void TEMPLATE(decode_array_common2,T)(const unsigned char *restrict igrib, long jlend, int NumBits,
				      T fmin, T zscale, T *restrict fpdata)
{
  static const unsigned mask[] = {0,1,3,7,15,31,63,127,255};
  static const double shift[9] = {1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0};

  // code from wgrib routine BDS_unpack
  const unsigned char *bits = igrib;
  int n_bits = NumBits;
  int c_bits, j_bits;

  // older unoptimized code, not often used
  c_bits = 8;
  for (long i = 0; i < jlend; ++i)
    {
      double jj = 0.0;
      j_bits = n_bits;
      while (c_bits <= j_bits)
	{
	  if (c_bits == 8)
	    {
	      jj = jj * 256.0  + (double) (*bits++);
	      j_bits -= 8;
	    }
	  else
	    {
	      jj = (jj * shift[c_bits]) + (double) (*bits & mask[c_bits]);
	      bits++;
	      j_bits -= c_bits;
	      c_bits = 8;
	    }
	}

      if (j_bits)
	{
	  c_bits -= j_bits;
	  jj = (jj * shift[j_bits]) + (double) (((unsigned)*bits >> c_bits) & mask[j_bits]);
	}
      fpdata[i] = (T)(fmin + zscale*jj);
    }
}

static
void TEMPLATE(decode_array_2byte,T)(size_t jlend, const unsigned char *restrict igrib,
                                    T *fpdata, T fmin, T zscale)
{
  const uint16_t *restrict sgrib = (const uint16_t *)(const void *)(igrib);

  if ( IS_BIGENDIAN() )
    {
      for (size_t i = 0; i < jlend; ++i)
        {
          fpdata[i] = fmin + zscale * sgrib[i];
        }
    }
  else
    {
      for (size_t i = 0; i < jlend; ++i)
        {
          uint16_t ui16 = gribSwapByteOrder_uint16(sgrib[i]);
          fpdata[i] = fmin + zscale * ui16;
        }
    }
}

static 
void TEMPLATE(decode_array,T)(const unsigned char *restrict igrib, long jlend, int numBits, 
			      T fmin, T zscale, T *restrict fpdata)
{
#if defined _GET_X86_COUNTER || defined _GET_MACH_COUNTER 
  uint64_t start_decode, end_decode;
#endif

#ifdef VECTORCODE
  GRIBPACK *lgrib = NULL;

  if ( numBits%8 == 0 )
    {
      long jlenc = jlend * numBits / 8;
      if ( jlenc > 0 ) 
	{
	  lgrib = (GRIBPACK*) Malloc(jlenc*sizeof(GRIBPACK));
	  if ( lgrib == NULL ) SysError("No Memory!");

	  (void) UNPACK_GRIB(igrib, lgrib, jlenc, -1L);
	}
    }

  if ( numBits ==  0 )
    {
      for (long i = 0; i < jlend; ++i)
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (int)lgrib[i];
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 16 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (((int)lgrib[2*i  ] <<  8) +  (int)lgrib[2*i+1]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 24 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (((int)lgrib[3*i  ] << 16) + ((int)lgrib[3*i+1] <<  8) +
	  	 (int)lgrib[3*i+2]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 32 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (((unsigned int)lgrib[4*i  ] << 24) + ((unsigned int)lgrib[4*i+1] << 16) +
		((unsigned int)lgrib[4*i+2] <<  8) +  (unsigned int)lgrib[4*i+3]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits <= 25 )
    {
      TEMPLATE(decode_array_common,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else if ( numBits > 25 && numBits < 32 )
    {
      TEMPLATE(decode_array_common2,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }

  if ( lgrib ) Free(lgrib);

#else
  if ( numBits ==  0 )
    {
      for (long i = 0; i < jlend; ++i)
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (int)igrib[i];
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 16 )
    {
      TEMPLATE(decode_array_2byte,T)((size_t) jlend, igrib, fpdata, fmin, zscale);
    }
  else if ( numBits == 24 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (T)(((int)igrib[3*i  ] << 16) + ((int)igrib[3*i+1] <<  8) +
                     (int)igrib[3*i+2]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 32 )
    for (long i = 0; i < jlend; ++i)
      {
	T dval = (T)(((unsigned int)igrib[4*i  ] << 24) + ((unsigned int)igrib[4*i+1] << 16) +
                     ((unsigned int)igrib[4*i+2] <<  8) +  (unsigned int)igrib[4*i+3]);
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits <= 25 )
    {
      TEMPLATE(decode_array_common,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else if ( numBits > 25 && numBits < 32 )
    {
      TEMPLATE(decode_array_common2,T)(igrib, jlend, numBits, fmin, zscale, fpdata);
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }
#endif
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */


#ifdef T
#undef T
#endif
#define T double
#ifdef T

static
size_t TEMPLATE(decodeGDS,T)(unsigned char  *gds, int *isec0, int *isec2, T *fsec2, size_t *numGridVals)
{
  // int imisng = 0;
  bool ReducedGrid = false, VertCoorTab = false;
#ifdef VECTORCODE
  unsigned char *igrib;
  GRIBPACK *lgrib = NULL;
  size_t lGribLen = 0;
#endif

  *numGridVals = 0;

  memset(isec2, 0, 22*sizeof(int));

  const unsigned gdsLen = GDS_Len;

  unsigned ipvpl = GDS_PVPL;
  if ( ipvpl == 0 ) ipvpl = 0xFF;

  if ( ipvpl != 0xFF )
    { // Either vct or reduced grid
      if ( GDS_NV != 0 )
	{ // we have vct
	  VertCoorTab = true;
	  const unsigned ipl =  4*GDS_NV + ipvpl - 1;
	  if ( ipl < gdsLen ) ReducedGrid = true;
	}
      else
	{
	  VertCoorTab = false;
	  ReducedGrid = true;
	}
      // ReducedGrid = (gdsLen - 32 - 4*GDS_NV);
    }
 
  if ( ISEC0_GRIB_Version == 0 ) VertCoorTab = ((gdsLen - 32) > 0);
  
  if ( ReducedGrid )
    {
      const unsigned locnl = GDS_PVPL - 1U + (VertCoorTab * 4U * GDS_NV);
      const unsigned jlenl = (gdsLen - locnl)  >> 1;
      if ( jlenl == GDS_NumLat )
	{
	  ISEC2_Reduced = true;
          size_t accum = 0;
	  for ( size_t i = 0; i < jlenl; ++i )
	    {
              unsigned rpi = GET_UINT2(gds[locnl+2*i], gds[locnl+2*i+1]);
              ISEC2_ReducedPoints(i) = (int)rpi;
              accum += rpi;
	    }
          *numGridVals = accum;
	}
      else
	{
	  ReducedGrid = false;
	}
    }

  ISEC2_GridType = GDS_GridType;

  // Gaussian grid definition.

  if ( ISEC2_GridType == GRIB1_GTYPE_LATLON    ||
       ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN  ||
       ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
    {
      ISEC2_NumLat    = (int)(GDS_NumLat);
      if ( ! ReducedGrid )
	{
	  ISEC2_NumLon = (int)(GDS_NumLon);
	  *numGridVals  = (size_t)ISEC2_NumLon*(size_t)ISEC2_NumLat;
	}
      ISEC2_FirstLat  = GDS_FirstLat;
      ISEC2_FirstLon  = GDS_FirstLon;
      ISEC2_ResFlag   = GDS_ResFlag;
      ISEC2_LastLat   = GDS_LastLat;
      ISEC2_LastLon   = GDS_LastLon;
      ISEC2_LonIncr   = (int)(GDS_LonIncr);

      ISEC2_NumPar    = (int)GDS_NumPar;
      ISEC2_ScanFlag  = GDS_ScanFlag;
      if ( ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
	{
	  ISEC2_LatSP     = GDS_LatSP;
	  ISEC2_LonSP     = GDS_LonSP;
	  FSEC2_RotAngle  = (T)GDS_RotAngle;
	}
      // if ( Lons != Longitudes || Lats != Latitudes ) Error("Latitude/Longitude Conflict");
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN     ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN_ROT ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN_STR ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN_ROTSTR )
    {
      // iret = decodeGDS_GG(gds, gdspos, isec0, isec2, imisng);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LATLON     ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_STR ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_ROTSTR )
    {
      // iret = decodeGDS_LL(gds, gdspos, isec0, isec2, imisng);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LCC )
    {
      ISEC2_NumLon    = (int)(GDS_NumLon);
      ISEC2_NumLat    = (int)(GDS_NumLat);
      *numGridVals  = (size_t)ISEC2_NumLon*(size_t)ISEC2_NumLat;
      ISEC2_FirstLat  = GDS_FirstLat;
      ISEC2_FirstLon  = GDS_FirstLon;
      ISEC2_ResFlag   = GDS_ResFlag;
      ISEC2_Lambert_Lov   = GDS_Lambert_Lov;
      ISEC2_Lambert_dx    = GDS_Lambert_dx;
      ISEC2_Lambert_dy    = GDS_Lambert_dy;
      ISEC2_Lambert_LatS1 = GDS_Lambert_LatS1;
      ISEC2_Lambert_LatS2 = GDS_Lambert_LatS2;
      ISEC2_Lambert_LatSP = GDS_Lambert_LatSP;
      ISEC2_Lambert_LonSP = GDS_Lambert_LonSP;
      ISEC2_Lambert_ProjFlag = GDS_Lambert_ProjFlag;
      ISEC2_ScanFlag      = GDS_ScanFlag;
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_SPECTRAL )
    {
      ISEC2_PentaJ  = (int)(GDS_PentaJ); // Truncation
      ISEC2_PentaK  = (int)(GDS_PentaK);
      ISEC2_PentaM  = (int)(GDS_PentaM);
      ISEC2_RepType = GDS_RepType;
      ISEC2_RepMode = GDS_RepMode;
      *numGridVals  = ((size_t)ISEC2_PentaJ+1)*((size_t)ISEC2_PentaJ+2);
      isec2[ 6] = 0;
      isec2[ 7] = 0;
      isec2[ 8] = 0;
      isec2[ 9] = 0;
      isec2[10] = 0;
      // iret = decodeGDS_SH(gds, gdspos, isec0, isec2, imisng);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_GME )
    {
      ISEC2_GME_NI2    = (int)(GDS_GME_NI2);
      ISEC2_GME_NI3    = (int)(GDS_GME_NI3);
      ISEC2_GME_ND     = (int)(GDS_GME_ND);
      ISEC2_GME_NI     = (int)(GDS_GME_NI);
      ISEC2_GME_AFlag  = GDS_GME_AFlag;
      ISEC2_GME_LatPP  = GDS_GME_LatPP;
      ISEC2_GME_LonPP  = GDS_GME_LonPP;
      ISEC2_GME_LonMPL = GDS_GME_LonMPL;
      ISEC2_GME_BFlag  = GDS_GME_BFlag;
      *numGridVals  = ((size_t)ISEC2_GME_NI+1)*((size_t)ISEC2_GME_NI+1)*10;
      // iret = decodeGDS_TR(gds, gdspos, isec0, isec2, imisng);
    }
  else
    {
      static bool lwarn = true;
      unsigned nlon = GDS_NumLon, nlat = GDS_NumLat;
      ISEC2_NumLon = (int)nlon;
      ISEC2_NumLat = (int)nlat;
      *numGridVals  = (size_t)nlon*(size_t)nlat;
      if ( lwarn )
        {
          lwarn = false;
          Message("GRIB gridtype %d unsupported", ISEC2_GridType);
        }
    }

  // Vertical coordinate parameters for hybrid levels.
  // Get number of vertical coordinate parameters, if any.

  ISEC2_NumVCP = 0;

  isec2[17] = 0;
  isec2[18] = 0;

  if ( VertCoorTab )
    {
      int locnv;
      if ( ISEC0_GRIB_Version  == 0 )
	{
	  locnv = 32;
	  ISEC2_NumVCP = ((int)gdsLen - 32) >> 2;
	}
      else
	{
	  locnv = (int)GDS_PVPL - 1;
	  ISEC2_NumVCP = GDS_NV;
	}
#if defined (SX)
      lGribLen = 4*ISEC2_NumVCP;	      
      lgrib    = (GRIBPACK*) Malloc(lGribLen*sizeof(GRIBPACK));

      igrib = &gds[locnv];
      if ( ISEC2_NumVCP > 0 ) (void) UNPACK_GRIB(igrib, lgrib, lGribLen, -1L);
      for (int i = 0; i < ISEC2_NumVCP; ++i)
	{
	  const int iexp  = lgrib[4*i];
	  const int imant = GET_UINT3(lgrib[4*i+1], lgrib[4*i+2], lgrib[4*i+3]);
	  fsec2[10+i] = POW_2_M24 * imant * ldexp(1.0, 4 * (iexp - 64));
	}

      Free(lgrib);
#else
      for (int i = 0; i < ISEC2_NumVCP; ++i)
	{
	  const int iexp  = gds[locnv+4*i];
	  const int imant = (int)(GET_UINT3(gds[locnv+4*i+1], gds[locnv+4*i+2], gds[locnv+4*i+3]));
	  fsec2[10+i] = (T)decfp2(iexp,imant);
	}
#endif
    }

  return gdsLen;
}

#define ldexp_double ldexp
#define ldexp_float  ldexpf
#define pow_double pow
#define pow_float powf

static
void TEMPLATE(decodeBDS,T)(int decscale, unsigned char *bds, int *isec2, int *isec4, 
                           T *fsec4, int fsec4len, int dfunc, size_t bdsLen, size_t numGridVals, int *iret)
{
  size_t ioff = 0;
  enum { bds_head = 11 };
  T zscale = 0.;
  T fmin = 0.;
  T *fpdata = fsec4;

  *iret = 0;
  unsigned char *igrib = bds;

  memset(isec4, 0, 42*sizeof(int));

  // 4 bit flag / 4 bit count of unused bits at end of block octet.

  const int bds_flag = BDS_Flag;

  // 0------- grid point
  // 1------- spherical harmonics

  const bool lspherc = (bds_flag >> 7)&1;
  if ( lspherc ) isec4[2] = 128;
  else           isec4[2] = 0;

  // -0------  simple packing
  // -1------ complex packing

  const bool lcomplex = (bds_flag >> 6)&1;
  if ( lcomplex ) isec4[3] = 64;
  else            isec4[3] =  0;

  // ---0---- No additional flags
  // ---1---- No additional flags

  const bool lcompress = (bds_flag >> 4)&1;

  unsigned zoff;
  if ( lcompress )
    { isec4[5] = 16; isec4[6] = BDS_Z; zoff = 12; }
  else
    { isec4[5] =  0; isec4[6] = 0;     zoff =  0; }

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

  const unsigned bds_ubits = bds_flag & 0xF;
  
  // scale factor (2 bytes)
  const int jscale = BDS_BinScale;

  // check for missing data indicators.

  const int iexp  = bds[ 6];
  const int imant = (int)(GET_UINT3(bds[ 7], bds[ 8], bds[ 9]));

  const int imiss = (jscale == 0xFFFF && iexp == 0xFF && imant == 0xFFFFFF);

  // convert reference value and scale factor.

  if ( ! (dfunc == 'J') && imiss == 0 )
    {
      fmin = (T)BDS_RefValue;
      zscale = TEMPLATE(ldexp,T)((T)1.0, jscale);
    }

  // get number of bits in each data value.

  unsigned dvbits = BDS_NumBits;
  ISEC4_NumBits = BDS_NumBits;

  // octet number of start of packed data calculated from start of block 4 - 1

  size_t locnd = zoff + bds_head;

  // if data is in spherical harmonic form, distinguish  between simple/complex packing (lcomplex = 0/1)

  if ( lspherc )
    {
      if ( !lcomplex )
	{
	  // no unpacked binary data present octet number of start of packed data
	  // calculated from start of block 4 - 1

	  ioff   = 1;
	  locnd += 4*ioff;  // RealCoef

	  // get real (0,0) coefficient in grib format and convert to floating point.
	  if ( dfunc != 'J' )
	    {
	      if ( imiss ) *fpdata++ = 0.0;
	      else         *fpdata++ = (T)BDS_RealCoef;
	    }
	}
      else // complex packed spherical harmonics
	{
	  isec4[15] = BDS_PackData;
	  // scaling factor
	  isec4[16] = BDS_Power;

	  // pentagonal resolution parameters of the unpacked section of data field

	  const int jup = bds[zoff+15];
	  const int kup = bds[zoff+16];
	  const int mup = bds[zoff+17];

	  isec4[zoff+17] = jup;
	  isec4[zoff+18] = kup;
	  isec4[zoff+19] = mup;

	  // unpacked binary data

	  locnd += 4; // 2 + power
	  locnd += 3; // j, k, m
	  ioff   = ((size_t)jup+1)*((size_t)jup+2);

	  if ( dfunc != 'J' )
	    for ( size_t i = 0; i < ioff; ++i )
	      {
		if ( imiss )
		  fpdata[i] = 0.0;
		else
		  {
		    const int iexp2  = (int)(bds[locnd+4*i]);
		    const int imant2 = (int)(GET_UINT3(bds[locnd+4*i+1], bds[locnd+4*i+2], bds[locnd+4*i+3]));
		    fpdata[i] = (T)decfp2(iexp2,imant2);
		  }
	      }
          fpdata += ioff;
	  locnd += 4*ioff;  /* RealCoef */
	}
    }
  else
    {
      if ( lcomplex )
	{
	  *iret = 1999;
	  gprintf(__func__, " Second order packed grids unsupported!");
	  gprintf(__func__, " Return code =  %d", *iret);
	  return;
	}
    }

  // Decode data values to floating point and store in fsec4.
  // First calculate the number of data values.
  // Take into account that spherical harmonics can be packed
  // simple (lcomplex = 0) or complex (lcomplex = 1)

  size_t jlend = bdsLen - locnd;

  if ( dvbits == 0 )
    {
      if ( jlend > 1 )
	{
	  *iret = 2001;
	  gprintf(__func__, " Number of bits per data value = 0!");
	  gprintf(__func__, " Return code =  %d", *iret);
	  return;
	}

      if ( numGridVals == 0 )
	{
	  *iret = 2002;
	  gprintf(__func__, " Constant field unsupported for this grid type!");
	  gprintf(__func__, " Return code =  %d", *iret);
	  return;
	}

      jlend = numGridVals - ioff;
    }
  else
    {
      jlend = (jlend*8 - bds_ubits) / dvbits;
    }

  ISEC4_NumValues        = (int)(jlend + ioff);
  ISEC4_NumNonMissValues = 0;

  if ( lcompress )
    {
      const size_t len = ((size_t) ((bds[17]<<16)+(bds[18]<<8)+bds[19]));

      ISEC4_NumValues = (int)(len*8/dvbits);

      if ( lspherc ) ISEC4_NumValues += lcomplex ? (int)ioff : 1;
    }

  if ( dfunc == 'J' ) return;

  // check length of output array.
  
  if ( ISEC4_NumValues > fsec4len )
    {
      *iret = 710;
      gprintf(__func__, " Output array too small. Length = %d", fsec4len);
      gprintf(__func__, " Number of values = %d", ISEC4_NumValues);
      gprintf(__func__, " Return code =  %d", *iret);
      return;
    }

  if ( imiss ) memset((char *)fpdata, 0, jlend*sizeof(T));
  else
    {
      igrib += locnd;

      TEMPLATE(decode_array,T)(igrib, (long)jlend, ISEC4_NumBits, fmin, zscale, fpdata);
    }

  if ( lspherc && lcomplex )
    {
      int pcStart = isec4[19], pcScale = isec4[16];
      TEMPLATE(scatter_complex,T)(fsec4, pcStart, ISEC2_PentaJ, ISEC4_NumValues);
      TEMPLATE(scale_complex,T)(fsec4, pcStart, pcScale, ISEC2_PentaJ, 1);
    }

  if ( CGRIBEX_Fix_ZSE )  // Fix ZeroShiftError of simple packed spherical harmonics
    if ( lspherc && !lcomplex )
      {
        // 20100705: Fix ZeroShiftError - Edi Kirk
	if ( IS_NOT_EQUAL(fsec4[1], 0.0) )
	  {
	    const T zserr = fsec4[1];
	    for (int i = 1; i < ISEC4_NumValues; ++i) fsec4[i] -= zserr;
	  }
      }

  if ( decscale )
    {
      const T scale = TEMPLATE(pow,T)((T)10.0, (T)-decscale);
      for (int i = 0; i < ISEC4_NumValues; ++i) fsec4[i] *= scale;
    }
}


void TEMPLATE(grib_decode,T)(int *isec0, int *isec1, int *isec2, T *fsec2, int *isec3,
			     T *fsec3, int *isec4, T *fsec4, int fsec4len, int *kgrib,
			     int kleng, int *kword, int dfunc, int *iret)
{
  UCHAR *bms = NULL;
  bool lsect2 = false, lsect3 = false;
  static bool lmissvalinfo = true;

  UNUSED(kleng);

  *iret = 0;

  grsdef();

  ISEC2_Reduced = false;

  // ----------------------------------------------------------------
  // IS Indicator Section (Section 0)
  // ----------------------------------------------------------------
  UCHAR *is = (UCHAR *) &kgrib[0];
  size_t isLen = decodeIS(is, isec0, iret);

  size_t gribLen = (size_t)ISEC0_GRIB_Len;

  /*
    When decoding or calculating length, previous editions
    of the GRIB code must be taken into account.

    In the table below, covering sections 0 and 1 of the GRIB
    code, octet numbering is from the beginning of the GRIB
    message;
    * indicates that the value is not available in the code edition;
    R indicates reserved, should be set to 0;
    Experimental edition is considered as edition -1.

    GRIB code edition -1 has fixed length of 20 octets for
    section 1, the length not included in the message.
    GRIB code edition 0 has fixed length of 24 octets for
    section 1, the length being included in the message.
    GRIB code edition 1 can have different lengths for section
    1, the minimum being 28 octets, length being included in
    the message.

                                         Octet numbers for code
                                                  editions

                 Contents.                   -1      0      1
                 ---------                ----------------------
       Letters GRIB                          1-4    1-4    1-4
       Total length of GRIB message.          *      *     5-7
       GRIB code edition number               *      *      8
       Length of Section 1.                   *     5-7    9-11
       Reserved octet (R).                    *      8(R)   *
       Version no. of Code Table 2.           *      *     12
       Identification of centre.              5      9     13
       Generating process.                    6     10     14
       Grid definition .                      7     11     15
       Flag (Code Table 1).                   8     12     16
       Indicator of parameter.                9     13     17
       Indicator of type of level.           10     14     18
       Height, pressure etc of levels.      11-12  15-16  19-20
       Year of century.                      13     17     21
       Month.                                14     18     22
       Day.                                  15     19     23
       Hour.                                 16     20     24
       Minute.                               17     21     25
       Indicator of unit of time.            18     22     26
       P1 - Period of time.                  19     23     27
       P2 - Period of time                  20(R)   24     28
       or reserved octet (R).
       Time range indicator.                21(R)   25     29
       or reserved octet (R).
       Number included in average.       22-23(R)  26-27  30-31
       or reserved octet (R).
       Number missing from average.         24(R)  28(R)   32
       or reserved octet (R).
       Century of data.                       *      *     33
       Designates sub-centre if not 0.        *      *     34
       Decimal scale factor.                  *      *    35-36
       Reserved. Set to 0.                    *      *    37-48
       (Need not be present)
       For originating centre use only.       *      *    49-nn
       (Need not be present)

    Identify which GRIB code edition is being decoded.

    In GRIB edition 1, the edition number is in octet 8.
    In GRIB edition 0, octet 8 is reserved and set to 0.
    In GRIB edition -1, octet 8 is a flag field and can have a
    a valid value of 0, 1, 2 or 3.

    However, GRIB edition number 0 has a fixed
    length of 24, included in the message, for section 1, so
    if the value extracted from octets 5-7 is 24 and that from
    octet 8 is 0, it is safe to assume edition 0 of the code.

  */

  // Set length of GRIB message to missing data value.
  if ( ISEC0_GRIB_Len == 24 && ISEC0_GRIB_Version == 0 ) ISEC0_GRIB_Len = 0;

  // ----------------------------------------------------------------
  // PDS Product Definition Section (Section 1)
  // ----------------------------------------------------------------
  UCHAR *pds = is + isLen;
  size_t pdsLen = decodePDS(pds, isec0, isec1);

  // ----------------------------------------------------------------
  // GDS Grid Description Section (Section 2)
  // ----------------------------------------------------------------
  size_t numGridVals = 0;
  size_t gdsLen = 0;
  const bool gdsIncluded = ISEC1_Sec2Or3Flag & 128;
  if ( gdsIncluded )
    {
      UCHAR *gds = is + isLen + pdsLen;
      gdsLen = TEMPLATE(decodeGDS,T)(gds, isec0, isec2, fsec2, &numGridVals);
    }

  // ----------------------------------------------------------------
  // BMS Bit-Map Section Section (Section 3)
  // ----------------------------------------------------------------
  isec3[0] = 0;
  size_t bmsLen = 0, bitmapSize = 0, imaskSize = 0;
  const bool bmsIncluded = ISEC1_Sec2Or3Flag & 64;
  if ( bmsIncluded )
    {
      bms = is + isLen + pdsLen + gdsLen;
      bmsLen = BMS_Len;

      imaskSize = (bmsLen > 6) ? (bmsLen - 6)<<3 : 0;
      bitmapSize = imaskSize - BMS_UnusedBits;
    }

  // ----------------------------------------------------------------
  // BDS Binary Data Section (Section 4)
  // ----------------------------------------------------------------
  UCHAR *bds = is + isLen + pdsLen + gdsLen + bmsLen;
  unsigned bdsLen = BDS_Len;
  /*
    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.
  */
  const bool llarge = (gribLen > JP23SET && bdsLen <= 120);
  if ( llarge )
    {
      gribLen &= JP23SET;
      gribLen *= 120;
      ISEC0_GRIB_Len = (int)gribLen;
      bdsLen = correct_bdslen(bdsLen, (int)gribLen, (long)(isLen+pdsLen+gdsLen+bmsLen));
    }

  TEMPLATE(decodeBDS,T)(ISEC1_DecScaleFactor, bds, isec2, isec4, fsec4, fsec4len, dfunc, bdsLen, numGridVals, iret);

  if ( *iret != 0 ) return;

  ISEC4_NumNonMissValues = ISEC4_NumValues;

  if ( bitmapSize > 0 )
    {
      if ( dfunc != 'L' && dfunc != 'J' )
	if ( DBL_IS_NAN(FSEC3_MissVal) && lmissvalinfo )
	  {
	    lmissvalinfo = false;
	    FSEC3_MissVal = (T)GRIB_MISSVAL;
	    Message("Missing value = NaN is unsupported, set to %g!", GRIB_MISSVAL);
	  }

      // ISEC4_NumNonMissValues = ISEC4_NumValues;
      ISEC4_NumValues = (int)bitmapSize;

      if ( dfunc != 'J' || bitmapSize == (size_t)ISEC4_NumNonMissValues )
	{
	  GRIBPACK bitmap;
	  /*
	  unsigned char *bitmap;
	  bitmap = BMS_Bitmap;
	  int j = ISEC4_NumNonMissValues;
	  for (int i = ISEC4_NumValues-1; i >= 0; --i)
	    {
	      fsec4[i] = ((bitmap[i/8]>>(7-(i&7)))&1) ? fsec4[--j] : FSEC3_MissVal;
	    }
	  */

	  GRIBPACK *imask = (GRIBPACK*) Malloc((size_t)imaskSize*sizeof(GRIBPACK));

#ifdef VECTORCODE
	  (void) UNPACK_GRIB(BMS_Bitmap, imask, imaskSize/8, -1L);
	  GRIBPACK *pbitmap = imask;
#else
	  GRIBPACK *pbitmap = BMS_Bitmap;
#endif

#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
	  for ( size_t i = imaskSize/8-1; i != (size_t)-1; --i )
	    {
	      bitmap = pbitmap[i];
	      imask[i*8+0] = 1 & (bitmap >> 7);
	      imask[i*8+1] = 1 & (bitmap >> 6);
	      imask[i*8+2] = 1 & (bitmap >> 5);
	      imask[i*8+3] = 1 & (bitmap >> 4);
	      imask[i*8+4] = 1 & (bitmap >> 3);
	      imask[i*8+5] = 1 & (bitmap >> 2);
	      imask[i*8+6] = 1 & (bitmap >> 1);
	      imask[i*8+7] = 1 & (bitmap);
	    }

	  int j = 0;
	  for (int i = 0; i < ISEC4_NumValues; ++i)
	    if ( imask[i] ) j++;

	  if ( ISEC4_NumNonMissValues != j )
	    {
	      if ( dfunc != 'J' && ISEC4_NumBits != 0 )
		Warning("Bitmap (%d) and data (%d) section differ, using bitmap section!", j, ISEC4_NumNonMissValues);

	      ISEC4_NumNonMissValues = j;
	    }

	  if ( dfunc != 'J' )
	    {
#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
	      for (int i = ISEC4_NumValues-1; i >= 0; --i)
		fsec4[i] = imask[i] ? fsec4[--j] : FSEC3_MissVal;
	    }

	  Free(imask);
	}
    }

  if ( ISEC2_Reduced )
    {
      int nvalues = 0;
      int nlat = ISEC2_NumLat;
      int nlon = ISEC2_ReducedPointsPtr[0];
      for (int ilat = 0; ilat < nlat; ++ilat) nvalues += ISEC2_ReducedPoints(ilat);
      for (int ilat = 1; ilat < nlat; ++ilat)
	if ( ISEC2_ReducedPoints(ilat) > nlon ) nlon = ISEC2_ReducedPoints(ilat);

      // int dlon = ISEC2_LastLon-ISEC2_FirstLon;
      // if ( dlon < 0 ) dlon += 360000;
	  
      if ( nvalues != ISEC4_NumValues ) *iret = -801;

      //printf("nlat %d  nlon %d \n", nlat, nlon);
      //printf("nvalues %d %d\n", nvalues, ISEC4_NumValues);

      if ( dfunc == 'R' && *iret == -801 )
	gprintf(__func__, "Number of values (%d) and sum of lons per row (%d) differ, abort conversion to regular Gaussian grid!",
		ISEC4_NumValues, nvalues);
      
      if ( dfunc == 'R' && *iret != -801 )
	{
	  ISEC2_Reduced = 0;
	  ISEC2_NumLon = nlon;
	  ISEC4_NumValues = nlon*nlat;

	  lsect3 = bitmapSize > 0;
          int lperio = 1;
	  int lveggy = (ISEC1_CodeTable == 128) && (ISEC1_CenterID == 98) && 
                      ((ISEC1_Parameter == 27) || (ISEC1_Parameter == 28) || 
                       (ISEC1_Parameter == 29) || (ISEC1_Parameter == 30) ||
                       (ISEC1_Parameter == 39) || (ISEC1_Parameter == 40) ||
                       (ISEC1_Parameter == 41) || (ISEC1_Parameter == 42) ||
                       (ISEC1_Parameter == 43));
	
	  (void) TEMPLATE(qu2reg3,T)(fsec4, ISEC2_ReducedPointsPtr, nlat, nlon, FSEC3_MissVal, iret, lsect3, lperio, lveggy);
	      
	  if ( bitmapSize > 0 )
	    {
	      int j = 0;	      
	      for (int i = 0; i < ISEC4_NumValues; ++i)
		if ( IS_NOT_EQUAL(fsec4[i], FSEC3_MissVal) ) j++;
		  
	      ISEC4_NumNonMissValues = j;
	    }
	}
    }

  if ( ISEC0_GRIB_Version == 1 ) isLen = 8;
  enum { esLen = 4 };
  gribLen = isLen + pdsLen + gdsLen + bmsLen + bdsLen + esLen;

  if ( !llarge && ISEC0_GRIB_Len && (size_t)ISEC0_GRIB_Len < gribLen )
    Warning("Inconsistent length of GRIB message (grib_message_size=%d < grib_record_size=%zu)!", ISEC0_GRIB_Len, gribLen);

  ISEC0_GRIB_Len = (int)gribLen;

  *kword = (int)((gribLen + sizeof(int) - 1) / sizeof(int));

  // ----------------------------------------------------------------
  // Section 9 . Abort/return to calling routine.
  // ----------------------------------------------------------------
  bool ldebug = false, l_iorj = false;
  if ( ldebug )
    {
      gprintf(__func__, "Section 9.");
      gprintf(__func__, "Output values set -");

      gribPrintSec0(isec0);
      gribPrintSec1(isec0, isec1);
      // Print section 2 if present.
      if ( lsect2 ) TEMPLATE(gribPrintSec2,T)(isec0, isec2, fsec2);

      if ( ! l_iorj )
	{
	  // Print section 3 if present.
	  if ( lsect3 ) TEMPLATE(gribPrintSec3,T)(isec0, isec3, fsec3);

	  TEMPLATE(gribPrintSec4,T)(isec0, isec4, fsec4);
	  // Special print for 2D spectra wave field real values in section 4
	  if ( (isec1[ 0] ==  140) && 
	       (isec1[ 1] ==   98) && 
	       (isec1[23] ==    1) && 
	       ((isec1[39] == 1045) || (isec1[39] == 1081))  && 
	       ((isec1[ 5] ==  250) || (isec1[ 5] ==  251)) )
	    gribPrintSec4Wave(isec4);
	}
    }
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */

#ifdef T
#undef T
#endif
#define T float
#ifdef T

static
size_t TEMPLATE(decodeGDS,T)(unsigned char  *gds, int *isec0, int *isec2, T *fsec2, size_t *numGridVals)
{
  // int imisng = 0;
  bool ReducedGrid = false, VertCoorTab = false;
#ifdef VECTORCODE
  unsigned char *igrib;
  GRIBPACK *lgrib = NULL;
  size_t lGribLen = 0;
#endif

  *numGridVals = 0;

  memset(isec2, 0, 22*sizeof(int));

  const unsigned gdsLen = GDS_Len;

  unsigned ipvpl = GDS_PVPL;
  if ( ipvpl == 0 ) ipvpl = 0xFF;

  if ( ipvpl != 0xFF )
    { // Either vct or reduced grid
      if ( GDS_NV != 0 )
	{ // we have vct
	  VertCoorTab = true;
	  const unsigned ipl =  4*GDS_NV + ipvpl - 1;
	  if ( ipl < gdsLen ) ReducedGrid = true;
	}
      else
	{
	  VertCoorTab = false;
	  ReducedGrid = true;
	}
      // ReducedGrid = (gdsLen - 32 - 4*GDS_NV);
    }
 
  if ( ISEC0_GRIB_Version == 0 ) VertCoorTab = ((gdsLen - 32) > 0);
  
  if ( ReducedGrid )
    {
      const unsigned locnl = GDS_PVPL - 1U + (VertCoorTab * 4U * GDS_NV);
      const unsigned jlenl = (gdsLen - locnl)  >> 1;
      if ( jlenl == GDS_NumLat )
	{
	  ISEC2_Reduced = true;
          size_t accum = 0;
	  for ( size_t i = 0; i < jlenl; ++i )
	    {
              unsigned rpi = GET_UINT2(gds[locnl+2*i], gds[locnl+2*i+1]);
              ISEC2_ReducedPoints(i) = (int)rpi;
              accum += rpi;
	    }
          *numGridVals = accum;
	}
      else
	{
	  ReducedGrid = false;
	}
    }

  ISEC2_GridType = GDS_GridType;

  // Gaussian grid definition.

  if ( ISEC2_GridType == GRIB1_GTYPE_LATLON    ||
       ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN  ||
       ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
    {
      ISEC2_NumLat    = (int)(GDS_NumLat);
      if ( ! ReducedGrid )
	{
	  ISEC2_NumLon = (int)(GDS_NumLon);
	  *numGridVals  = (size_t)ISEC2_NumLon*(size_t)ISEC2_NumLat;
	}
      ISEC2_FirstLat  = GDS_FirstLat;
      ISEC2_FirstLon  = GDS_FirstLon;
      ISEC2_ResFlag   = GDS_ResFlag;
      ISEC2_LastLat   = GDS_LastLat;
      ISEC2_LastLon   = GDS_LastLon;
      ISEC2_LonIncr   = (int)(GDS_LonIncr);

      ISEC2_NumPar    = (int)GDS_NumPar;
      ISEC2_ScanFlag  = GDS_ScanFlag;
      if ( ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
	{
	  ISEC2_LatSP     = GDS_LatSP;
	  ISEC2_LonSP     = GDS_LonSP;
	  FSEC2_RotAngle  = (T)GDS_RotAngle;
	}
      // if ( Lons != Longitudes || Lats != Latitudes ) Error("Latitude/Longitude Conflict");
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN     ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN_ROT ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN_STR ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN_ROTSTR )
    {
      // iret = decodeGDS_GG(gds, gdspos, isec0, isec2, imisng);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LATLON     ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_STR ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_ROTSTR )
    {
      // iret = decodeGDS_LL(gds, gdspos, isec0, isec2, imisng);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LCC )
    {
      ISEC2_NumLon    = (int)(GDS_NumLon);
      ISEC2_NumLat    = (int)(GDS_NumLat);
      *numGridVals  = (size_t)ISEC2_NumLon*(size_t)ISEC2_NumLat;
      ISEC2_FirstLat  = GDS_FirstLat;
      ISEC2_FirstLon  = GDS_FirstLon;
      ISEC2_ResFlag   = GDS_ResFlag;
      ISEC2_Lambert_Lov   = GDS_Lambert_Lov;
      ISEC2_Lambert_dx    = GDS_Lambert_dx;
      ISEC2_Lambert_dy    = GDS_Lambert_dy;
      ISEC2_Lambert_LatS1 = GDS_Lambert_LatS1;
      ISEC2_Lambert_LatS2 = GDS_Lambert_LatS2;
      ISEC2_Lambert_LatSP = GDS_Lambert_LatSP;
      ISEC2_Lambert_LonSP = GDS_Lambert_LonSP;
      ISEC2_Lambert_ProjFlag = GDS_Lambert_ProjFlag;
      ISEC2_ScanFlag      = GDS_ScanFlag;
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_SPECTRAL )
    {
      ISEC2_PentaJ  = (int)(GDS_PentaJ); // Truncation
      ISEC2_PentaK  = (int)(GDS_PentaK);
      ISEC2_PentaM  = (int)(GDS_PentaM);
      ISEC2_RepType = GDS_RepType;
      ISEC2_RepMode = GDS_RepMode;
      *numGridVals  = ((size_t)ISEC2_PentaJ+1)*((size_t)ISEC2_PentaJ+2);
      isec2[ 6] = 0;
      isec2[ 7] = 0;
      isec2[ 8] = 0;
      isec2[ 9] = 0;
      isec2[10] = 0;
      // iret = decodeGDS_SH(gds, gdspos, isec0, isec2, imisng);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_GME )
    {
      ISEC2_GME_NI2    = (int)(GDS_GME_NI2);
      ISEC2_GME_NI3    = (int)(GDS_GME_NI3);
      ISEC2_GME_ND     = (int)(GDS_GME_ND);
      ISEC2_GME_NI     = (int)(GDS_GME_NI);
      ISEC2_GME_AFlag  = GDS_GME_AFlag;
      ISEC2_GME_LatPP  = GDS_GME_LatPP;
      ISEC2_GME_LonPP  = GDS_GME_LonPP;
      ISEC2_GME_LonMPL = GDS_GME_LonMPL;
      ISEC2_GME_BFlag  = GDS_GME_BFlag;
      *numGridVals  = ((size_t)ISEC2_GME_NI+1)*((size_t)ISEC2_GME_NI+1)*10;
      // iret = decodeGDS_TR(gds, gdspos, isec0, isec2, imisng);
    }
  else
    {
      static bool lwarn = true;
      unsigned nlon = GDS_NumLon, nlat = GDS_NumLat;
      ISEC2_NumLon = (int)nlon;
      ISEC2_NumLat = (int)nlat;
      *numGridVals  = (size_t)nlon*(size_t)nlat;
      if ( lwarn )
        {
          lwarn = false;
          Message("GRIB gridtype %d unsupported", ISEC2_GridType);
        }
    }

  // Vertical coordinate parameters for hybrid levels.
  // Get number of vertical coordinate parameters, if any.

  ISEC2_NumVCP = 0;

  isec2[17] = 0;
  isec2[18] = 0;

  if ( VertCoorTab )
    {
      int locnv;
      if ( ISEC0_GRIB_Version  == 0 )
	{
	  locnv = 32;
	  ISEC2_NumVCP = ((int)gdsLen - 32) >> 2;
	}
      else
	{
	  locnv = (int)GDS_PVPL - 1;
	  ISEC2_NumVCP = GDS_NV;
	}
#if defined (SX)
      lGribLen = 4*ISEC2_NumVCP;	      
      lgrib    = (GRIBPACK*) Malloc(lGribLen*sizeof(GRIBPACK));

      igrib = &gds[locnv];
      if ( ISEC2_NumVCP > 0 ) (void) UNPACK_GRIB(igrib, lgrib, lGribLen, -1L);
      for (int i = 0; i < ISEC2_NumVCP; ++i)
	{
	  const int iexp  = lgrib[4*i];
	  const int imant = GET_UINT3(lgrib[4*i+1], lgrib[4*i+2], lgrib[4*i+3]);
	  fsec2[10+i] = POW_2_M24 * imant * ldexp(1.0, 4 * (iexp - 64));
	}

      Free(lgrib);
#else
      for (int i = 0; i < ISEC2_NumVCP; ++i)
	{
	  const int iexp  = gds[locnv+4*i];
	  const int imant = (int)(GET_UINT3(gds[locnv+4*i+1], gds[locnv+4*i+2], gds[locnv+4*i+3]));
	  fsec2[10+i] = (T)decfp2(iexp,imant);
	}
#endif
    }

  return gdsLen;
}

#define ldexp_double ldexp
#define ldexp_float  ldexpf
#define pow_double pow
#define pow_float powf

static
void TEMPLATE(decodeBDS,T)(int decscale, unsigned char *bds, int *isec2, int *isec4, 
                           T *fsec4, int fsec4len, int dfunc, size_t bdsLen, size_t numGridVals, int *iret)
{
  size_t ioff = 0;
  enum { bds_head = 11 };
  T zscale = 0.;
  T fmin = 0.;
  T *fpdata = fsec4;

  *iret = 0;
  unsigned char *igrib = bds;

  memset(isec4, 0, 42*sizeof(int));

  // 4 bit flag / 4 bit count of unused bits at end of block octet.

  const int bds_flag = BDS_Flag;

  // 0------- grid point
  // 1------- spherical harmonics

  const bool lspherc = (bds_flag >> 7)&1;
  if ( lspherc ) isec4[2] = 128;
  else           isec4[2] = 0;

  // -0------  simple packing
  // -1------ complex packing

  const bool lcomplex = (bds_flag >> 6)&1;
  if ( lcomplex ) isec4[3] = 64;
  else            isec4[3] =  0;

  // ---0---- No additional flags
  // ---1---- No additional flags

  const bool lcompress = (bds_flag >> 4)&1;

  unsigned zoff;
  if ( lcompress )
    { isec4[5] = 16; isec4[6] = BDS_Z; zoff = 12; }
  else
    { isec4[5] =  0; isec4[6] = 0;     zoff =  0; }

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

  const unsigned bds_ubits = bds_flag & 0xF;
  
  // scale factor (2 bytes)
  const int jscale = BDS_BinScale;

  // check for missing data indicators.

  const int iexp  = bds[ 6];
  const int imant = (int)(GET_UINT3(bds[ 7], bds[ 8], bds[ 9]));

  const int imiss = (jscale == 0xFFFF && iexp == 0xFF && imant == 0xFFFFFF);

  // convert reference value and scale factor.

  if ( ! (dfunc == 'J') && imiss == 0 )
    {
      fmin = (T)BDS_RefValue;
      zscale = TEMPLATE(ldexp,T)((T)1.0, jscale);
    }

  // get number of bits in each data value.

  unsigned dvbits = BDS_NumBits;
  ISEC4_NumBits = BDS_NumBits;

  // octet number of start of packed data calculated from start of block 4 - 1

  size_t locnd = zoff + bds_head;

  // if data is in spherical harmonic form, distinguish  between simple/complex packing (lcomplex = 0/1)

  if ( lspherc )
    {
      if ( !lcomplex )
	{
	  // no unpacked binary data present octet number of start of packed data
	  // calculated from start of block 4 - 1

	  ioff   = 1;
	  locnd += 4*ioff;  // RealCoef

	  // get real (0,0) coefficient in grib format and convert to floating point.
	  if ( dfunc != 'J' )
	    {
	      if ( imiss ) *fpdata++ = 0.0;
	      else         *fpdata++ = (T)BDS_RealCoef;
	    }
	}
      else // complex packed spherical harmonics
	{
	  isec4[15] = BDS_PackData;
	  // scaling factor
	  isec4[16] = BDS_Power;

	  // pentagonal resolution parameters of the unpacked section of data field

	  const int jup = bds[zoff+15];
	  const int kup = bds[zoff+16];
	  const int mup = bds[zoff+17];

	  isec4[zoff+17] = jup;
	  isec4[zoff+18] = kup;
	  isec4[zoff+19] = mup;

	  // unpacked binary data

	  locnd += 4; // 2 + power
	  locnd += 3; // j, k, m
	  ioff   = ((size_t)jup+1)*((size_t)jup+2);

	  if ( dfunc != 'J' )
	    for ( size_t i = 0; i < ioff; ++i )
	      {
		if ( imiss )
		  fpdata[i] = 0.0;
		else
		  {
		    const int iexp2  = (int)(bds[locnd+4*i]);
		    const int imant2 = (int)(GET_UINT3(bds[locnd+4*i+1], bds[locnd+4*i+2], bds[locnd+4*i+3]));
		    fpdata[i] = (T)decfp2(iexp2,imant2);
		  }
	      }
          fpdata += ioff;
	  locnd += 4*ioff;  /* RealCoef */
	}
    }
  else
    {
      if ( lcomplex )
	{
	  *iret = 1999;
	  gprintf(__func__, " Second order packed grids unsupported!");
	  gprintf(__func__, " Return code =  %d", *iret);
	  return;
	}
    }

  // Decode data values to floating point and store in fsec4.
  // First calculate the number of data values.
  // Take into account that spherical harmonics can be packed
  // simple (lcomplex = 0) or complex (lcomplex = 1)

  size_t jlend = bdsLen - locnd;

  if ( dvbits == 0 )
    {
      if ( jlend > 1 )
	{
	  *iret = 2001;
	  gprintf(__func__, " Number of bits per data value = 0!");
	  gprintf(__func__, " Return code =  %d", *iret);
	  return;
	}

      if ( numGridVals == 0 )
	{
	  *iret = 2002;
	  gprintf(__func__, " Constant field unsupported for this grid type!");
	  gprintf(__func__, " Return code =  %d", *iret);
	  return;
	}

      jlend = numGridVals - ioff;
    }
  else
    {
      jlend = (jlend*8 - bds_ubits) / dvbits;
    }

  ISEC4_NumValues        = (int)(jlend + ioff);
  ISEC4_NumNonMissValues = 0;

  if ( lcompress )
    {
      const size_t len = ((size_t) ((bds[17]<<16)+(bds[18]<<8)+bds[19]));

      ISEC4_NumValues = (int)(len*8/dvbits);

      if ( lspherc ) ISEC4_NumValues += lcomplex ? (int)ioff : 1;
    }

  if ( dfunc == 'J' ) return;

  // check length of output array.
  
  if ( ISEC4_NumValues > fsec4len )
    {
      *iret = 710;
      gprintf(__func__, " Output array too small. Length = %d", fsec4len);
      gprintf(__func__, " Number of values = %d", ISEC4_NumValues);
      gprintf(__func__, " Return code =  %d", *iret);
      return;
    }

  if ( imiss ) memset((char *)fpdata, 0, jlend*sizeof(T));
  else
    {
      igrib += locnd;

      TEMPLATE(decode_array,T)(igrib, (long)jlend, ISEC4_NumBits, fmin, zscale, fpdata);
    }

  if ( lspherc && lcomplex )
    {
      int pcStart = isec4[19], pcScale = isec4[16];
      TEMPLATE(scatter_complex,T)(fsec4, pcStart, ISEC2_PentaJ, ISEC4_NumValues);
      TEMPLATE(scale_complex,T)(fsec4, pcStart, pcScale, ISEC2_PentaJ, 1);
    }

  if ( CGRIBEX_Fix_ZSE )  // Fix ZeroShiftError of simple packed spherical harmonics
    if ( lspherc && !lcomplex )
      {
        // 20100705: Fix ZeroShiftError - Edi Kirk
	if ( IS_NOT_EQUAL(fsec4[1], 0.0) )
	  {
	    const T zserr = fsec4[1];
	    for (int i = 1; i < ISEC4_NumValues; ++i) fsec4[i] -= zserr;
	  }
      }

  if ( decscale )
    {
      const T scale = TEMPLATE(pow,T)((T)10.0, (T)-decscale);
      for (int i = 0; i < ISEC4_NumValues; ++i) fsec4[i] *= scale;
    }
}


void TEMPLATE(grib_decode,T)(int *isec0, int *isec1, int *isec2, T *fsec2, int *isec3,
			     T *fsec3, int *isec4, T *fsec4, int fsec4len, int *kgrib,
			     int kleng, int *kword, int dfunc, int *iret)
{
  UCHAR *bms = NULL;
  bool lsect2 = false, lsect3 = false;
  static bool lmissvalinfo = true;

  UNUSED(kleng);

  *iret = 0;

  grsdef();

  ISEC2_Reduced = false;

  // ----------------------------------------------------------------
  // IS Indicator Section (Section 0)
  // ----------------------------------------------------------------
  UCHAR *is = (UCHAR *) &kgrib[0];
  size_t isLen = decodeIS(is, isec0, iret);

  size_t gribLen = (size_t)ISEC0_GRIB_Len;

  /*
    When decoding or calculating length, previous editions
    of the GRIB code must be taken into account.

    In the table below, covering sections 0 and 1 of the GRIB
    code, octet numbering is from the beginning of the GRIB
    message;
    * indicates that the value is not available in the code edition;
    R indicates reserved, should be set to 0;
    Experimental edition is considered as edition -1.

    GRIB code edition -1 has fixed length of 20 octets for
    section 1, the length not included in the message.
    GRIB code edition 0 has fixed length of 24 octets for
    section 1, the length being included in the message.
    GRIB code edition 1 can have different lengths for section
    1, the minimum being 28 octets, length being included in
    the message.

                                         Octet numbers for code
                                                  editions

                 Contents.                   -1      0      1
                 ---------                ----------------------
       Letters GRIB                          1-4    1-4    1-4
       Total length of GRIB message.          *      *     5-7
       GRIB code edition number               *      *      8
       Length of Section 1.                   *     5-7    9-11
       Reserved octet (R).                    *      8(R)   *
       Version no. of Code Table 2.           *      *     12
       Identification of centre.              5      9     13
       Generating process.                    6     10     14
       Grid definition .                      7     11     15
       Flag (Code Table 1).                   8     12     16
       Indicator of parameter.                9     13     17
       Indicator of type of level.           10     14     18
       Height, pressure etc of levels.      11-12  15-16  19-20
       Year of century.                      13     17     21
       Month.                                14     18     22
       Day.                                  15     19     23
       Hour.                                 16     20     24
       Minute.                               17     21     25
       Indicator of unit of time.            18     22     26
       P1 - Period of time.                  19     23     27
       P2 - Period of time                  20(R)   24     28
       or reserved octet (R).
       Time range indicator.                21(R)   25     29
       or reserved octet (R).
       Number included in average.       22-23(R)  26-27  30-31
       or reserved octet (R).
       Number missing from average.         24(R)  28(R)   32
       or reserved octet (R).
       Century of data.                       *      *     33
       Designates sub-centre if not 0.        *      *     34
       Decimal scale factor.                  *      *    35-36
       Reserved. Set to 0.                    *      *    37-48
       (Need not be present)
       For originating centre use only.       *      *    49-nn
       (Need not be present)

    Identify which GRIB code edition is being decoded.

    In GRIB edition 1, the edition number is in octet 8.
    In GRIB edition 0, octet 8 is reserved and set to 0.
    In GRIB edition -1, octet 8 is a flag field and can have a
    a valid value of 0, 1, 2 or 3.

    However, GRIB edition number 0 has a fixed
    length of 24, included in the message, for section 1, so
    if the value extracted from octets 5-7 is 24 and that from
    octet 8 is 0, it is safe to assume edition 0 of the code.

  */

  // Set length of GRIB message to missing data value.
  if ( ISEC0_GRIB_Len == 24 && ISEC0_GRIB_Version == 0 ) ISEC0_GRIB_Len = 0;

  // ----------------------------------------------------------------
  // PDS Product Definition Section (Section 1)
  // ----------------------------------------------------------------
  UCHAR *pds = is + isLen;
  size_t pdsLen = decodePDS(pds, isec0, isec1);

  // ----------------------------------------------------------------
  // GDS Grid Description Section (Section 2)
  // ----------------------------------------------------------------
  size_t numGridVals = 0;
  size_t gdsLen = 0;
  const bool gdsIncluded = ISEC1_Sec2Or3Flag & 128;
  if ( gdsIncluded )
    {
      UCHAR *gds = is + isLen + pdsLen;
      gdsLen = TEMPLATE(decodeGDS,T)(gds, isec0, isec2, fsec2, &numGridVals);
    }

  // ----------------------------------------------------------------
  // BMS Bit-Map Section Section (Section 3)
  // ----------------------------------------------------------------
  isec3[0] = 0;
  size_t bmsLen = 0, bitmapSize = 0, imaskSize = 0;
  const bool bmsIncluded = ISEC1_Sec2Or3Flag & 64;
  if ( bmsIncluded )
    {
      bms = is + isLen + pdsLen + gdsLen;
      bmsLen = BMS_Len;

      imaskSize = (bmsLen > 6) ? (bmsLen - 6)<<3 : 0;
      bitmapSize = imaskSize - BMS_UnusedBits;
    }

  // ----------------------------------------------------------------
  // BDS Binary Data Section (Section 4)
  // ----------------------------------------------------------------
  UCHAR *bds = is + isLen + pdsLen + gdsLen + bmsLen;
  unsigned bdsLen = BDS_Len;
  /*
    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.
  */
  const bool llarge = (gribLen > JP23SET && bdsLen <= 120);
  if ( llarge )
    {
      gribLen &= JP23SET;
      gribLen *= 120;
      ISEC0_GRIB_Len = (int)gribLen;
      bdsLen = correct_bdslen(bdsLen, (int)gribLen, (long)(isLen+pdsLen+gdsLen+bmsLen));
    }

  TEMPLATE(decodeBDS,T)(ISEC1_DecScaleFactor, bds, isec2, isec4, fsec4, fsec4len, dfunc, bdsLen, numGridVals, iret);

  if ( *iret != 0 ) return;

  ISEC4_NumNonMissValues = ISEC4_NumValues;

  if ( bitmapSize > 0 )
    {
      if ( dfunc != 'L' && dfunc != 'J' )
	if ( DBL_IS_NAN(FSEC3_MissVal) && lmissvalinfo )
	  {
	    lmissvalinfo = false;
	    FSEC3_MissVal = (T)GRIB_MISSVAL;
	    Message("Missing value = NaN is unsupported, set to %g!", GRIB_MISSVAL);
	  }

      // ISEC4_NumNonMissValues = ISEC4_NumValues;
      ISEC4_NumValues = (int)bitmapSize;

      if ( dfunc != 'J' || bitmapSize == (size_t)ISEC4_NumNonMissValues )
	{
	  GRIBPACK bitmap;
	  /*
	  unsigned char *bitmap;
	  bitmap = BMS_Bitmap;
	  int j = ISEC4_NumNonMissValues;
	  for (int i = ISEC4_NumValues-1; i >= 0; --i)
	    {
	      fsec4[i] = ((bitmap[i/8]>>(7-(i&7)))&1) ? fsec4[--j] : FSEC3_MissVal;
	    }
	  */

	  GRIBPACK *imask = (GRIBPACK*) Malloc((size_t)imaskSize*sizeof(GRIBPACK));

#ifdef VECTORCODE
	  (void) UNPACK_GRIB(BMS_Bitmap, imask, imaskSize/8, -1L);
	  GRIBPACK *pbitmap = imask;
#else
	  GRIBPACK *pbitmap = BMS_Bitmap;
#endif

#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
	  for ( size_t i = imaskSize/8-1; i != (size_t)-1; --i )
	    {
	      bitmap = pbitmap[i];
	      imask[i*8+0] = 1 & (bitmap >> 7);
	      imask[i*8+1] = 1 & (bitmap >> 6);
	      imask[i*8+2] = 1 & (bitmap >> 5);
	      imask[i*8+3] = 1 & (bitmap >> 4);
	      imask[i*8+4] = 1 & (bitmap >> 3);
	      imask[i*8+5] = 1 & (bitmap >> 2);
	      imask[i*8+6] = 1 & (bitmap >> 1);
	      imask[i*8+7] = 1 & (bitmap);
	    }

	  int j = 0;
	  for (int i = 0; i < ISEC4_NumValues; ++i)
	    if ( imask[i] ) j++;

	  if ( ISEC4_NumNonMissValues != j )
	    {
	      if ( dfunc != 'J' && ISEC4_NumBits != 0 )
		Warning("Bitmap (%d) and data (%d) section differ, using bitmap section!", j, ISEC4_NumNonMissValues);

	      ISEC4_NumNonMissValues = j;
	    }

	  if ( dfunc != 'J' )
	    {
#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
	      for (int i = ISEC4_NumValues-1; i >= 0; --i)
		fsec4[i] = imask[i] ? fsec4[--j] : FSEC3_MissVal;
	    }

	  Free(imask);
	}
    }

  if ( ISEC2_Reduced )
    {
      int nvalues = 0;
      int nlat = ISEC2_NumLat;
      int nlon = ISEC2_ReducedPointsPtr[0];
      for (int ilat = 0; ilat < nlat; ++ilat) nvalues += ISEC2_ReducedPoints(ilat);
      for (int ilat = 1; ilat < nlat; ++ilat)
	if ( ISEC2_ReducedPoints(ilat) > nlon ) nlon = ISEC2_ReducedPoints(ilat);

      // int dlon = ISEC2_LastLon-ISEC2_FirstLon;
      // if ( dlon < 0 ) dlon += 360000;
	  
      if ( nvalues != ISEC4_NumValues ) *iret = -801;

      //printf("nlat %d  nlon %d \n", nlat, nlon);
      //printf("nvalues %d %d\n", nvalues, ISEC4_NumValues);

      if ( dfunc == 'R' && *iret == -801 )
	gprintf(__func__, "Number of values (%d) and sum of lons per row (%d) differ, abort conversion to regular Gaussian grid!",
		ISEC4_NumValues, nvalues);
      
      if ( dfunc == 'R' && *iret != -801 )
	{
	  ISEC2_Reduced = 0;
	  ISEC2_NumLon = nlon;
	  ISEC4_NumValues = nlon*nlat;

	  lsect3 = bitmapSize > 0;
          int lperio = 1;
	  int lveggy = (ISEC1_CodeTable == 128) && (ISEC1_CenterID == 98) && 
                      ((ISEC1_Parameter == 27) || (ISEC1_Parameter == 28) || 
                       (ISEC1_Parameter == 29) || (ISEC1_Parameter == 30) ||
                       (ISEC1_Parameter == 39) || (ISEC1_Parameter == 40) ||
                       (ISEC1_Parameter == 41) || (ISEC1_Parameter == 42) ||
                       (ISEC1_Parameter == 43));
	
	  (void) TEMPLATE(qu2reg3,T)(fsec4, ISEC2_ReducedPointsPtr, nlat, nlon, FSEC3_MissVal, iret, lsect3, lperio, lveggy);
	      
	  if ( bitmapSize > 0 )
	    {
	      int j = 0;	      
	      for (int i = 0; i < ISEC4_NumValues; ++i)
		if ( IS_NOT_EQUAL(fsec4[i], FSEC3_MissVal) ) j++;
		  
	      ISEC4_NumNonMissValues = j;
	    }
	}
    }

  if ( ISEC0_GRIB_Version == 1 ) isLen = 8;
  enum { esLen = 4 };
  gribLen = isLen + pdsLen + gdsLen + bmsLen + bdsLen + esLen;

  if ( !llarge && ISEC0_GRIB_Len && (size_t)ISEC0_GRIB_Len < gribLen )
    Warning("Inconsistent length of GRIB message (grib_message_size=%d < grib_record_size=%zu)!", ISEC0_GRIB_Len, gribLen);

  ISEC0_GRIB_Len = (int)gribLen;

  *kword = (int)((gribLen + sizeof(int) - 1) / sizeof(int));

  // ----------------------------------------------------------------
  // Section 9 . Abort/return to calling routine.
  // ----------------------------------------------------------------
  bool ldebug = false, l_iorj = false;
  if ( ldebug )
    {
      gprintf(__func__, "Section 9.");
      gprintf(__func__, "Output values set -");

      gribPrintSec0(isec0);
      gribPrintSec1(isec0, isec1);
      // Print section 2 if present.
      if ( lsect2 ) TEMPLATE(gribPrintSec2,T)(isec0, isec2, fsec2);

      if ( ! l_iorj )
	{
	  // Print section 3 if present.
	  if ( lsect3 ) TEMPLATE(gribPrintSec3,T)(isec0, isec3, fsec3);

	  TEMPLATE(gribPrintSec4,T)(isec0, isec4, fsec4);
	  // Special print for 2D spectra wave field real values in section 4
	  if ( (isec1[ 0] ==  140) && 
	       (isec1[ 1] ==   98) && 
	       (isec1[23] ==    1) && 
	       ((isec1[39] == 1045) || (isec1[39] == 1081))  && 
	       ((isec1[ 5] ==  250) || (isec1[ 5] ==  251)) )
	    gribPrintSec4Wave(isec4);
	}
    }
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */
// clang-format on

// 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


#ifdef T
#undef T
#endif
#define T double
#ifdef T


#define CGRIBEX_FPSCALE(data) (((data) - zref) * factor + 0.5)

static
void TEMPLATE(encode_array_common,T)(int numBits, size_t packStart, size_t datasize, GRIBPACK *lGrib,
				     const T *data, T zref, T factor, size_t *gz)
{
  size_t z = *gz;
  unsigned int ival;
  int cbits, jbits;
  unsigned int c;

  // code from gribw routine flist2bitstream

  cbits = 8;
  c = 0;
  for (size_t i = packStart; i < datasize; ++i)
    {
      // note float -> unsigned int .. truncate
      ival = (unsigned int)(CGRIBEX_FPSCALE(data[i]));
      /*
	if ( ival > max_nbpv_pow2 ) ival = max_nbpv_pow2;
	if ( ival < 0 ) ival = 0;
      */
      jbits = numBits;
      while ( cbits <= jbits ) 
	{
	  if ( cbits == 8 )
	    {
	      jbits -= 8;
	      lGrib[z++] = (ival >> jbits) & 0xFF;
	    }
	  else
	    {
	      jbits -= cbits;
	      lGrib[z++] = (GRIBPACK)((c << cbits)
                                      + ((ival >> jbits) & ((1U << cbits) - 1)));
	      cbits = 8;
	      c = 0;
	    }
	}
      /* now jbits < cbits */
      if ( jbits )
	{
	  c = (c << jbits) + (ival & ((1U << jbits)-1));
	  cbits -= jbits;
	}
    }
  if ( cbits != 8 ) lGrib[z++] = (GRIBPACK)(c << cbits);

  *gz = z;
}


static
void TEMPLATE(encode_array_2byte,T)(size_t datasize, GRIBPACK *restrict lGrib,
				    const T *restrict data, T zref, T factor, size_t *gz)
{
  uint16_t *restrict sgrib = (uint16_t *)(void *)(lGrib+*gz);

  if (IS_BIGENDIAN())
    {
      for (size_t i = 0; i < datasize; ++i)
        sgrib[i] = (uint16_t) CGRIBEX_FPSCALE(data[i]);
    }
  else
    {
      for (size_t i = 0; i < datasize; ++i)
        {
          uint16_t ui16 = (uint16_t) CGRIBEX_FPSCALE(data[i]);
          sgrib[i] = gribSwapByteOrder_uint16(ui16);
        }
    }

  *gz += 2*datasize;
}

static
void TEMPLATE(encode_array,T)(int numBits, size_t packStart, size_t datasize, 
			      GRIBPACK *restrict lGrib,
			      const T *restrict data, 
			      T zref, T factor, size_t *gz)
{
  size_t z = *gz;

  data += packStart;
  datasize -= packStart;

  if (numBits ==  8)
    {
      for (size_t i = 0; i < datasize; ++i)
	{
	  lGrib[z++] = (GRIBPACK) CGRIBEX_FPSCALE(data[i]);
	}
    }
  else if (numBits == 16)
    {
      if (sizeof(T) == sizeof(double))
      	{
          grib_encode_array_2byte_double(datasize, lGrib, (const double *)(const void *)data, zref, factor, &z);
        }
      else
        {
          TEMPLATE(encode_array_2byte,T)(datasize, lGrib, data, zref, factor, &z);
        }
    }
  else if (numBits == 24)
    {
      for (size_t i = 0; i < datasize; ++i)
	{
          uint32_t ui32 = (uint32_t) CGRIBEX_FPSCALE(data[i]);
          lGrib[z++] = (GRIBPACK)(ui32 >> 16);
          lGrib[z++] = (GRIBPACK)(ui32 >>  8);
          lGrib[z++] = (GRIBPACK)ui32;
	}
    }
  else if (numBits == 32)
    {
      for (size_t i = 0; i < datasize; ++i)
	{
          uint32_t ui32 = (uint32_t) CGRIBEX_FPSCALE(data[i]);
          lGrib[z++] = (GRIBPACK)(ui32 >> 24);
          lGrib[z++] = (GRIBPACK)(ui32 >> 16);
          lGrib[z++] = (GRIBPACK)(ui32 >>  8);
          lGrib[z++] = (GRIBPACK)ui32;
	}
    }
  else if (numBits > 0 && numBits <= 32)
    {
      TEMPLATE(encode_array_common,T)(numBits, 0, datasize, lGrib, data, zref, factor, &z);
    }
  else if (numBits == 0)
    {
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }

  *gz = z;
}

static
void TEMPLATE(encode_array_unrolled,T)(int numBits, size_t packStart, size_t datasize, 
				       GRIBPACK *restrict lGrib,
				       const T *restrict data, 
				       T zref, T factor, size_t *gz)
{
  size_t z = *gz;
#ifdef _ARCH_PWR6
  enum { CGRIBEX__UNROLL_DEPTH_2 = 8 };
#else
  enum { CGRIBEX__UNROLL_DEPTH_2 = 128 };
#endif
  size_t residual;
  size_t ofs;
  double dval[CGRIBEX__UNROLL_DEPTH_2];

  data += packStart;
  datasize -= packStart;
  residual =  datasize % CGRIBEX__UNROLL_DEPTH_2;
  ofs = datasize - residual;

  // reducing FP operations to single FMA is slowing down on pwr6 ...

  if      ( numBits ==  8 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(2, "pack 8 bit unrolled");
#endif
      unsigned char *cgrib = (unsigned char *) (lGrib + z);
      size_t i;
      for (i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
	{
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	    }
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
#ifdef _ARCH_PWR6
	      *cgrib++ =  (unsigned long) dval[j];
#else
	      *cgrib++ =  (unsigned char) dval[j];
#endif
	    }
	  z += CGRIBEX__UNROLL_DEPTH_2;
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	}
      for (size_t j = 0; j < residual; ++j) 
	{
#ifdef _ARCH_PWR6
	  *cgrib++ = (unsigned long) dval[j];
#else
	  *cgrib++ = (unsigned char) dval[j];
#endif
	}
      z += residual;

#ifdef _GET_IBM_COUNTER 
      hpmStop(2);
#endif
    }
  else if ( numBits == 16 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(3, "pack 16 bit unrolled");
#endif
#ifdef _ARCH_PWR6
      unsigned long ival;
#else
      uint16_t ival;
#endif
      uint16_t *sgrib = (uint16_t *)(void *)(lGrib+z);

      for (size_t i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
	{
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    dval[j] = CGRIBEX_FPSCALE(data[j]);
	  if ( IS_BIGENDIAN() )
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
#ifdef _ARCH_PWR6
		  *sgrib++ = (unsigned long) dval[j];
#else
		  *sgrib++ = (uint16_t) dval[j];
#endif
		}
	      z += 2*CGRIBEX__UNROLL_DEPTH_2;
	    }
	  else
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
		  ival = (uint16_t) dval[j];
                  *sgrib++ = gribSwapByteOrder_uint16(ival);
		}
	      z += 2*CGRIBEX__UNROLL_DEPTH_2;
	    }
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (size_t j = 0; j < residual; ++j) 
	    {
#ifdef _ARCH_PWR6
	      *sgrib++ = (unsigned long) dval[j];
#else
              *sgrib++ = (uint16_t) dval[j];
#endif
	    }
	  z += 2*residual;
	}
      else
	{
	  for (size_t j = 0; j < residual; ++j) 
	    {
              ival = (uint16_t) dval[j];
	      lGrib[z  ] = (GRIBPACK)(ival >>  8);
	      lGrib[z+1] = (GRIBPACK)ival;
	      z += 2;
	    }
	}
#ifdef _GET_IBM_COUNTER 
      hpmStop(3);
#endif
    }
  else if ( numBits == 24 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(4, "pack 24 bit unrolled");
#endif
#ifdef _ARCH_PWR6
      unsigned long ival;
#else
      uint32_t ival;
#endif
      for (size_t i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
	{
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[j]);
	    }
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
#ifdef _ARCH_PWR6
	      ival = (unsigned long) dval[j];
#else
	      ival = (uint32_t) dval[j];
#endif
	      lGrib[z  ] =  (GRIBPACK)(ival >> 16);
	      lGrib[z+1] =  (GRIBPACK)(ival >>  8);
	      lGrib[z+2] =  (GRIBPACK)ival;
	      z += 3;
	    }
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  ival = (uint32_t) dval[j];
	  lGrib[z  ] =  (GRIBPACK)(ival >> 16);
	  lGrib[z+1] =  (GRIBPACK)(ival >>  8);
	  lGrib[z+2] =  (GRIBPACK)ival;
	  z += 3;
	}
#ifdef _GET_IBM_COUNTER 
      hpmStop(4);
#endif
    }
  else if ( numBits == 32 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(5, "pack 32 bit unrolled");
#endif
#ifdef _ARCH_PWR6
      unsigned long ival;
#else
      uint32_t ival;
#endif
      unsigned int *igrib = (unsigned int *)(void *)(lGrib + z);
      for (size_t i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
        {
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j) dval[j] = CGRIBEX_FPSCALE(data[i+j]);

	  if ( IS_BIGENDIAN() )
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
#ifdef _ARCH_PWR6
		  *igrib = (unsigned long) dval[j];
#else
		  *igrib = (uint32_t) dval[j];
#endif
		  igrib++;
		  z += 4;
		}
	    }
	  else
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
                  ival = (uint32_t) dval[j];
		  lGrib[z  ] =  (GRIBPACK)(ival >> 24);
		  lGrib[z+1] =  (GRIBPACK)(ival >> 16);
		  lGrib[z+2] =  (GRIBPACK)(ival >>  8);
		  lGrib[z+3] =  (GRIBPACK)ival;
		  z += 4;
		}
	    }
	}
      for (size_t j = 0; j < residual; ++j) 
	{
          dval[j] = CGRIBEX_FPSCALE(data[ofs+j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (size_t j = 0; j < residual; ++j) 
	    {
#ifdef _ARCH_PWR6
	      *igrib = (unsigned long) dval[j];
#else
	      *igrib = (uint32_t) dval[j];
#endif
	      igrib++;
	      z += 4;
	    }
	}
      else
	{
          for (size_t j = 0; j < residual; ++j) 
	    {
	      ival = (uint32_t) dval[j];
	      lGrib[z  ] =  (GRIBPACK)(ival >> 24);
	      lGrib[z+1] =  (GRIBPACK)(ival >> 16);
	      lGrib[z+2] =  (GRIBPACK)(ival >>  8);
	      lGrib[z+3] =  (GRIBPACK)ival;
	      z += 4;
	    }
	}
#ifdef _GET_IBM_COUNTER 
      hpmStop(5);
#endif
    }
  else if ( numBits > 0 && numBits <= 32 )
    {
      TEMPLATE(encode_array_common,T)(numBits, 0, datasize, lGrib, data, zref, factor, &z);
    }
  else if ( numBits == 0 )
    {
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }

  *gz = z;
}

#ifdef CGRIBEX_FPSCALE
#undef CGRIBEX_FPSCALE
#endif

#endif /* T */

#ifdef T
#undef T
#endif
#define T float
#ifdef T


#define CGRIBEX_FPSCALE(data) (((data) - zref) * factor + 0.5)

static
void TEMPLATE(encode_array_common,T)(int numBits, size_t packStart, size_t datasize, GRIBPACK *lGrib,
				     const T *data, T zref, T factor, size_t *gz)
{
  size_t z = *gz;
  unsigned int ival;
  int cbits, jbits;
  unsigned int c;

  // code from gribw routine flist2bitstream

  cbits = 8;
  c = 0;
  for (size_t i = packStart; i < datasize; ++i)
    {
      // note float -> unsigned int .. truncate
      ival = (unsigned int)(CGRIBEX_FPSCALE(data[i]));
      /*
	if ( ival > max_nbpv_pow2 ) ival = max_nbpv_pow2;
	if ( ival < 0 ) ival = 0;
      */
      jbits = numBits;
      while ( cbits <= jbits ) 
	{
	  if ( cbits == 8 )
	    {
	      jbits -= 8;
	      lGrib[z++] = (ival >> jbits) & 0xFF;
	    }
	  else
	    {
	      jbits -= cbits;
	      lGrib[z++] = (GRIBPACK)((c << cbits)
                                      + ((ival >> jbits) & ((1U << cbits) - 1)));
	      cbits = 8;
	      c = 0;
	    }
	}
      /* now jbits < cbits */
      if ( jbits )
	{
	  c = (c << jbits) + (ival & ((1U << jbits)-1));
	  cbits -= jbits;
	}
    }
  if ( cbits != 8 ) lGrib[z++] = (GRIBPACK)(c << cbits);

  *gz = z;
}


static
void TEMPLATE(encode_array_2byte,T)(size_t datasize, GRIBPACK *restrict lGrib,
				    const T *restrict data, T zref, T factor, size_t *gz)
{
  uint16_t *restrict sgrib = (uint16_t *)(void *)(lGrib+*gz);

  if (IS_BIGENDIAN())
    {
      for (size_t i = 0; i < datasize; ++i)
        sgrib[i] = (uint16_t) CGRIBEX_FPSCALE(data[i]);
    }
  else
    {
      for (size_t i = 0; i < datasize; ++i)
        {
          uint16_t ui16 = (uint16_t) CGRIBEX_FPSCALE(data[i]);
          sgrib[i] = gribSwapByteOrder_uint16(ui16);
        }
    }

  *gz += 2*datasize;
}

static
void TEMPLATE(encode_array,T)(int numBits, size_t packStart, size_t datasize, 
			      GRIBPACK *restrict lGrib,
			      const T *restrict data, 
			      T zref, T factor, size_t *gz)
{
  size_t z = *gz;

  data += packStart;
  datasize -= packStart;

  if (numBits ==  8)
    {
      for (size_t i = 0; i < datasize; ++i)
	{
	  lGrib[z++] = (GRIBPACK) CGRIBEX_FPSCALE(data[i]);
	}
    }
  else if (numBits == 16)
    {
      if (sizeof(T) == sizeof(double))
      	{
          grib_encode_array_2byte_double(datasize, lGrib, (const double *)(const void *)data, zref, factor, &z);
        }
      else
        {
          TEMPLATE(encode_array_2byte,T)(datasize, lGrib, data, zref, factor, &z);
        }
    }
  else if (numBits == 24)
    {
      for (size_t i = 0; i < datasize; ++i)
	{
          uint32_t ui32 = (uint32_t) CGRIBEX_FPSCALE(data[i]);
          lGrib[z++] = (GRIBPACK)(ui32 >> 16);
          lGrib[z++] = (GRIBPACK)(ui32 >>  8);
          lGrib[z++] = (GRIBPACK)ui32;
	}
    }
  else if (numBits == 32)
    {
      for (size_t i = 0; i < datasize; ++i)
	{
          uint32_t ui32 = (uint32_t) CGRIBEX_FPSCALE(data[i]);
          lGrib[z++] = (GRIBPACK)(ui32 >> 24);
          lGrib[z++] = (GRIBPACK)(ui32 >> 16);
          lGrib[z++] = (GRIBPACK)(ui32 >>  8);
          lGrib[z++] = (GRIBPACK)ui32;
	}
    }
  else if (numBits > 0 && numBits <= 32)
    {
      TEMPLATE(encode_array_common,T)(numBits, 0, datasize, lGrib, data, zref, factor, &z);
    }
  else if (numBits == 0)
    {
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }

  *gz = z;
}

static
void TEMPLATE(encode_array_unrolled,T)(int numBits, size_t packStart, size_t datasize, 
				       GRIBPACK *restrict lGrib,
				       const T *restrict data, 
				       T zref, T factor, size_t *gz)
{
  size_t z = *gz;
#ifdef _ARCH_PWR6
  enum { CGRIBEX__UNROLL_DEPTH_2 = 8 };
#else
  enum { CGRIBEX__UNROLL_DEPTH_2 = 128 };
#endif
  size_t residual;
  size_t ofs;
  double dval[CGRIBEX__UNROLL_DEPTH_2];

  data += packStart;
  datasize -= packStart;
  residual =  datasize % CGRIBEX__UNROLL_DEPTH_2;
  ofs = datasize - residual;

  // reducing FP operations to single FMA is slowing down on pwr6 ...

  if      ( numBits ==  8 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(2, "pack 8 bit unrolled");
#endif
      unsigned char *cgrib = (unsigned char *) (lGrib + z);
      size_t i;
      for (i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
	{
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	    }
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
#ifdef _ARCH_PWR6
	      *cgrib++ =  (unsigned long) dval[j];
#else
	      *cgrib++ =  (unsigned char) dval[j];
#endif
	    }
	  z += CGRIBEX__UNROLL_DEPTH_2;
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	}
      for (size_t j = 0; j < residual; ++j) 
	{
#ifdef _ARCH_PWR6
	  *cgrib++ = (unsigned long) dval[j];
#else
	  *cgrib++ = (unsigned char) dval[j];
#endif
	}
      z += residual;

#ifdef _GET_IBM_COUNTER 
      hpmStop(2);
#endif
    }
  else if ( numBits == 16 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(3, "pack 16 bit unrolled");
#endif
#ifdef _ARCH_PWR6
      unsigned long ival;
#else
      uint16_t ival;
#endif
      uint16_t *sgrib = (uint16_t *)(void *)(lGrib+z);

      for (size_t i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
	{
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    dval[j] = CGRIBEX_FPSCALE(data[j]);
	  if ( IS_BIGENDIAN() )
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
#ifdef _ARCH_PWR6
		  *sgrib++ = (unsigned long) dval[j];
#else
		  *sgrib++ = (uint16_t) dval[j];
#endif
		}
	      z += 2*CGRIBEX__UNROLL_DEPTH_2;
	    }
	  else
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
		  ival = (uint16_t) dval[j];
                  *sgrib++ = gribSwapByteOrder_uint16(ival);
		}
	      z += 2*CGRIBEX__UNROLL_DEPTH_2;
	    }
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (size_t j = 0; j < residual; ++j) 
	    {
#ifdef _ARCH_PWR6
	      *sgrib++ = (unsigned long) dval[j];
#else
              *sgrib++ = (uint16_t) dval[j];
#endif
	    }
	  z += 2*residual;
	}
      else
	{
	  for (size_t j = 0; j < residual; ++j) 
	    {
              ival = (uint16_t) dval[j];
	      lGrib[z  ] = (GRIBPACK)(ival >>  8);
	      lGrib[z+1] = (GRIBPACK)ival;
	      z += 2;
	    }
	}
#ifdef _GET_IBM_COUNTER 
      hpmStop(3);
#endif
    }
  else if ( numBits == 24 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(4, "pack 24 bit unrolled");
#endif
#ifdef _ARCH_PWR6
      unsigned long ival;
#else
      uint32_t ival;
#endif
      for (size_t i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
	{
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[j]);
	    }
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
	    {
#ifdef _ARCH_PWR6
	      ival = (unsigned long) dval[j];
#else
	      ival = (uint32_t) dval[j];
#endif
	      lGrib[z  ] =  (GRIBPACK)(ival >> 16);
	      lGrib[z+1] =  (GRIBPACK)(ival >>  8);
	      lGrib[z+2] =  (GRIBPACK)ival;
	      z += 3;
	    }
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      for (size_t j = 0; j < residual; ++j) 
	{
	  ival = (uint32_t) dval[j];
	  lGrib[z  ] =  (GRIBPACK)(ival >> 16);
	  lGrib[z+1] =  (GRIBPACK)(ival >>  8);
	  lGrib[z+2] =  (GRIBPACK)ival;
	  z += 3;
	}
#ifdef _GET_IBM_COUNTER 
      hpmStop(4);
#endif
    }
  else if ( numBits == 32 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(5, "pack 32 bit unrolled");
#endif
#ifdef _ARCH_PWR6
      unsigned long ival;
#else
      uint32_t ival;
#endif
      unsigned int *igrib = (unsigned int *)(void *)(lGrib + z);
      for (size_t i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2)
        {
	  for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j) dval[j] = CGRIBEX_FPSCALE(data[i+j]);

	  if ( IS_BIGENDIAN() )
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
#ifdef _ARCH_PWR6
		  *igrib = (unsigned long) dval[j];
#else
		  *igrib = (uint32_t) dval[j];
#endif
		  igrib++;
		  z += 4;
		}
	    }
	  else
	    {
	      for (size_t j = 0; j < CGRIBEX__UNROLL_DEPTH_2; ++j)
		{
                  ival = (uint32_t) dval[j];
		  lGrib[z  ] =  (GRIBPACK)(ival >> 24);
		  lGrib[z+1] =  (GRIBPACK)(ival >> 16);
		  lGrib[z+2] =  (GRIBPACK)(ival >>  8);
		  lGrib[z+3] =  (GRIBPACK)ival;
		  z += 4;
		}
	    }
	}
      for (size_t j = 0; j < residual; ++j) 
	{
          dval[j] = CGRIBEX_FPSCALE(data[ofs+j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (size_t j = 0; j < residual; ++j) 
	    {
#ifdef _ARCH_PWR6
	      *igrib = (unsigned long) dval[j];
#else
	      *igrib = (uint32_t) dval[j];
#endif
	      igrib++;
	      z += 4;
	    }
	}
      else
	{
          for (size_t j = 0; j < residual; ++j) 
	    {
	      ival = (uint32_t) dval[j];
	      lGrib[z  ] =  (GRIBPACK)(ival >> 24);
	      lGrib[z+1] =  (GRIBPACK)(ival >> 16);
	      lGrib[z+2] =  (GRIBPACK)(ival >>  8);
	      lGrib[z+3] =  (GRIBPACK)ival;
	      z += 4;
	    }
	}
#ifdef _GET_IBM_COUNTER 
      hpmStop(5);
#endif
    }
  else if ( numBits > 0 && numBits <= 32 )
    {
      TEMPLATE(encode_array_common,T)(numBits, 0, datasize, lGrib, data, zref, factor, &z);
    }
  else if ( numBits == 0 )
    {
    }
  else
    {
      Error("Unimplemented packing factor %d!", numBits);
    }

  *gz = z;
}

#ifdef CGRIBEX_FPSCALE
#undef CGRIBEX_FPSCALE
#endif

#endif /* T */


#ifdef T
#undef T
#endif
#define T double
#ifdef T

// GRIB BLOCK 2 - GRID DESCRIPTION SECTION
static
void TEMPLATE(encodeGDS,T)(GRIBPACK *lGrib, long *gribLen, int *isec2, T *fsec2)
{
  long z = *gribLen;
  int exponent, mantissa;
  int ival;
  int gdslen = 32;

  if ( ISEC2_GridType == GRIB1_GTYPE_LCC ) gdslen += 10;

  if ( ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )  gdslen += 10;

  const int pvoffset = (ISEC2_NumVCP || ISEC2_Reduced) ? gdslen + 1 : 0xFF;

  if ( ISEC2_Reduced ) gdslen += 2 * ISEC2_NumLat;

  gdslen += ISEC2_NumVCP * 4;

  Put3Byte(gdslen);             /*  0- 2 Length of Block 2 Byte 0 */
  Put1Byte(ISEC2_NumVCP);       /*  3    NV */
  Put1Byte(pvoffset);           /*  4    PV */
  Put1Byte(ISEC2_GridType);     /*  5    LatLon=0 Gauss=4 Spectral=50 */

  if ( ISEC2_GridType == GRIB1_GTYPE_SPECTRAL )
    {
      Put2Byte(ISEC2_PentaJ);   /*  6- 7 Pentagonal resolution J  */
      Put2Byte(ISEC2_PentaK);   /*  8- 9 Pentagonal resolution K  */
      Put2Byte(ISEC2_PentaM);   /* 10-11 Pentagonal resolution M  */
      Put1Byte(ISEC2_RepType);  /* 12    Representation type      */
      Put1Byte(ISEC2_RepMode);  /* 13    Representation mode      */
      PutnZero(18);             /* 14-31 reserved                 */
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_GME )
    {
      Put2Byte(ISEC2_GME_NI2);
      Put2Byte(ISEC2_GME_NI3);
      Put3Byte(ISEC2_GME_ND);
      Put3Byte(ISEC2_GME_NI);
      Put1Byte(ISEC2_GME_AFlag);
      Put3Int(ISEC2_GME_LatPP);
      Put3Int(ISEC2_GME_LonPP);
      Put3Int(ISEC2_GME_LonMPL);
      Put1Byte(ISEC2_GME_BFlag);
      PutnZero(5);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LCC )
    {
      Put2Byte(ISEC2_NumLon);          /*  6- 7 Longitudes               */

      Put2Byte(ISEC2_NumLat);          /*  8- 9 Latitudes                */
      Put3Int(ISEC2_FirstLat);
      Put3Int(ISEC2_FirstLon);
      Put1Byte(ISEC2_ResFlag);         /* 16    Resolution flag          */
      Put3Int(ISEC2_Lambert_Lov);      /* 17-19 */
      Put3Int(ISEC2_Lambert_dx);       /* 20-22 */
      Put3Int(ISEC2_Lambert_dy);       /* 23-25 */
      Put1Byte(ISEC2_Lambert_ProjFlag);/* 26    Projection flag          */
      Put1Byte(ISEC2_ScanFlag);        /* 27    Scanning mode            */
      Put3Int(ISEC2_Lambert_LatS1);    /* 28-30 */  
      Put3Int(ISEC2_Lambert_LatS2);    /* 31-33 */
      Put3Int(ISEC2_Lambert_LatSP);    /* 34-36 */  
      Put3Int(ISEC2_Lambert_LonSP);    /* 37-39 */
      PutnZero(2);                     /* 34-41 */
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LATLON    ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN  ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
    {
      const int numlon = ISEC2_Reduced ? 0xFFFF : ISEC2_NumLon;
      Put2Byte(numlon);                /*  6- 7 Number of Longitudes     */

      Put2Byte(ISEC2_NumLat);          /*  8- 9 Number of Latitudes      */
      Put3Int(ISEC2_FirstLat);
      Put3Int(ISEC2_FirstLon);
      Put1Byte(ISEC2_ResFlag);         /* 16    Resolution flag          */
      Put3Int(ISEC2_LastLat);
      Put3Int(ISEC2_LastLon);
      const unsigned lonIncr = (ISEC2_ResFlag == 0) ? 0xFFFF : (unsigned)ISEC2_LonIncr;
      const unsigned latIncr = (ISEC2_ResFlag == 0) ? 0xFFFF : (unsigned)ISEC2_LatIncr;
      Put2Byte(lonIncr);               /* 23-24 i - direction increment  */
      if ( ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN )
	Put2Byte(ISEC2_NumPar);        /* 25-26 Latitudes Pole->Equator  */
      else
	Put2Byte(latIncr);             /* 25-26 j - direction increment  */

      Put1Byte(ISEC2_ScanFlag);        /* 27    Scanning mode            */
      PutnZero(4);                     /* 28-31 reserved                 */

      if ( ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
	{
	  Put3Int(ISEC2_LatSP);
	  Put3Int(ISEC2_LonSP);
	  Put1Real((double)(FSEC2_RotAngle));
	}
    }
  else
    {
      Error("Unsupported grid type %d", ISEC2_GridType);
    }

#if defined (SX)
#pragma vdir novector     /* vectorization gives wrong results on NEC */
#endif
  for (long i = 0; i < ISEC2_NumVCP; ++i)
    {
      Put1Real((double)(fsec2[10+i]));
    }

  if ( ISEC2_Reduced )
    for (long i = 0; i < ISEC2_NumLat; ++i) Put2Byte(ISEC2_ReducedPoints(i));

  *gribLen = z;
}

// GRIB BLOCK 3 - BIT MAP SECTION
static
void TEMPLATE(encodeBMS,T)(GRIBPACK *lGrib, long *gribLen, T *fsec3, int *isec4, T *data, long *datasize)
{
  long z = *gribLen;
  static bool lmissvalinfo = true;
  //  unsigned int c, imask;

  if ( DBL_IS_NAN(FSEC3_MissVal) && lmissvalinfo)
    {
      lmissvalinfo = false;
      Message("Missing value = NaN is unsupported!");
    }

  const long bitmapSize = ISEC4_NumValues;
  const long imaskSize = ((bitmapSize+7)>>3)<<3;
  GRIBPACK *bitmap = &lGrib[z+6];
  long fsec4size = 0;

#ifdef VECTORCODE
  unsigned int *imask = (unsigned int*) Malloc(imaskSize*sizeof(unsigned int));
  memset(imask, 0, imaskSize*sizeof(int));

#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
  for (long i = 0; i < bitmapSize; ++i)
    {
      if ( IS_NOT_EQUAL(data[i], FSEC3_MissVal) )
	{
	  data[fsec4size++] = data[i];
	  imask[i] = 1;
	}
    }

#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
  for (long i = 0; i < imaskSize/8; ++i)
    {
      bitmap[i] = (imask[i*8+0] << 7) | (imask[i*8+1] << 6) |
	          (imask[i*8+2] << 5) | (imask[i*8+3] << 4) |
	          (imask[i*8+4] << 3) | (imask[i*8+5] << 2) |
	          (imask[i*8+6] << 1) | (imask[i*8+7]);
    }

  Free(imask);
#else
  for (long i = 0; i < imaskSize/8; ++i) bitmap[i] = 0;

  for (long i = 0; i < bitmapSize; ++i)
    {
      if ( IS_NOT_EQUAL(data[i], FSEC3_MissVal) )
	{
	  data[fsec4size++] = data[i];
	  bitmap[i/8] |= (GRIBPACK)(1<<(7-(i&7)));
	}
    }
#endif

  const long bmsLen = imaskSize/8 + 6;
  const long bmsUnusedBits = imaskSize - bitmapSize;

  Put3Byte(bmsLen);   /*  0- 2 Length of Block 3 Byte 0 */
  Put1Byte(bmsUnusedBits);
  Put2Byte(0);

  *gribLen += bmsLen;

  *datasize = fsec4size;
}

#define pow_double pow
#define pow_float powf

// GRIB BLOCK 4 - BINARY DATA SECTION
static
int TEMPLATE(encodeBDS,T)(GRIBPACK *lGrib, long *gribLen, int decscale, int *isec2, int *isec4, long datasize, T *data,
			  long *datstart, long *datsize, int code)
{
  // Uwe Schulzweida, 11/04/2003 : Check that number of bits per value is not exceeded
  // Uwe Schulzweida,  6/05/2003 : Copy result to fpval to prevent integer overflow

  size_t z = (size_t)*gribLen;
  int numBits;
  int ival;
  long PackStart = 0;
  int Flag = 0;
  int binscale = 0;
  int bds_head = 11;
  int bds_ext = 0;
  /* ibits = BitsPerInt; */
  int exponent, mantissa;
  bool lspherc = false;
  int isubset = 0, itemp = 0, itrunc = 0;
  T factor = 1, fmin, fmax;
  const double jpepsln = 1.0e-12; // -----> tolerance used to check equality
                                  //        of floating point numbers - needed
		                  //        on some platforms (eg vpp700, linux)
  extern int CGRIBEX_Const;       // 1: Don't pack constant fields on regular grids

  if ( isec2 )
    {
      /* If section 2 is present, it says if data is spherical harmonic */

      lspherc =  ( isec2[0] == 50 || isec2[0] == 60 ||
                   isec2[0] == 70 || isec2[0] == 80 );

      isec4[2] = lspherc ? 128 : 0;
    }
  else
    {
      /* Section 4 says if it's spherical harmonic data.. */

      lspherc = ( isec4[2] == 128 );
    }

  /* Complex packing supported for spherical harmonics. */

  const bool lcomplex = ( lspherc && ( isec4[3] == 64 ) ) ||
                        ( lspherc && isec2 && ( isec2[5] == 2 ) );

  // Check input specification is consistent

  if ( lcomplex && isec2 )
    {
      if ( ( isec4[3] != 64 ) && ( isec2[5] == 2 ) )
	{
	  gprintf(__func__, "  COMPLEX mismatch. isec4[3] = %d\n", isec4[3]);
	  gprintf(__func__, "  COMPLEX mismatch. isec2[5] = %d\n", isec2[5]);
	  return (807);
	}
      else if ( ( isec4[3] == 64 ) && ( isec2[5] != 2 ) )
	{
	  gprintf(__func__, "  COMPLEX mismatch. isec4[3] = %d\n", isec4[3]);
	  gprintf(__func__, "  COMPLEX mismatch. isec2[5] = %d\n", isec2[5]);
	  return (807);
        }
      else if ( lcomplex )
	{
          // Truncation of full spectrum, which is supposed triangular, has to be diagnosed. Define also sub-set truncation.
	  isubset = isec4[17];
	  // When encoding, use the total number of data.
	  itemp   = isec4[0];
	  itrunc  = (int) (sqrt(itemp*4 + 1.) - 3) / 2;
	}
    }

  if ( decscale )
    {
      const T scale = TEMPLATE(pow,T)((T)10.0, (T)decscale);
      for (long i = 0; i < datasize; ++i) data[i] *= scale;
    }

  if ( lspherc )
    {
      if ( lcomplex )
	{
	  const int jup  = isubset;
	  const int ioff = (jup+1)*(jup+2);
	  bds_ext = 4 + 3 + 4*ioff;
	  PackStart = ioff;
	  Flag = 192;
	}
      else
	{
	  bds_ext = 4;
	  PackStart = 1;
	  Flag = 128;
	}
    }

  *datstart = bds_head + bds_ext;

  int nbpv = numBits = ISEC4_NumBits;

  if ( lspherc && lcomplex )
    {
      const int pcStart = isubset;
      const int pcScale = isec4[16];
      TEMPLATE(scale_complex,T)(data, pcStart, pcScale, itrunc, 0);
      TEMPLATE(gather_complex,T)(data, (size_t)pcStart, (size_t)itrunc, (size_t)datasize);
    }

  fmin = fmax = data[PackStart];

  TEMPLATE(minmax_val,T)(data+PackStart, datasize-PackStart, &fmin, &fmax);

  double zref = (double)fmin;
  if (!(zref < DBL_MAX && zref > -DBL_MAX))
    {
      gprintf(__func__, "Minimum value out of range: %g!", zref);
      return (707);
    }

  if ( CGRIBEX_Const && !lspherc )
    {
      if ( IS_EQUAL(fmin, fmax) ) nbpv = 0;
    }

  long blockLength = (*datstart) + (nbpv*(datasize - PackStart) + 7)/8;
  blockLength += blockLength & 1;

  const long unused_bits = blockLength*8 - (*datstart)*8 - nbpv*(datasize - PackStart);

  Flag += (int)unused_bits;


  // Adjust number of bits per value if full integer length to avoid hitting most significant bit (sign bit).
  // if( nbpv == ibits ) nbpv = nbpv - 1;
  /*
    Calculate the binary scaling factor to spread the range of values over the number of bits per value.
    Limit scaling to 2**-126 to 2**127 (using IEEE 32-bit floatsas a guideline).
  */
  const double range = fabs(fmax - fmin);

  if ( fabs(fmin) < FLT_MIN ) fmin = 0;
  /*
    Have to allow tolerance in comparisons on some platforms (eg vpp700 and linux),
    such as 0.9999999999999999 = 1.0, to avoid clipping ranges which are a power of 2.
  */
  if ( range <= jpepsln )
    {
      binscale = 0;
    }
  else if ( IS_NOT_EQUAL(fmin, 0.0) && (fabs(range/fmin) <= jpepsln) )
    {
      binscale = 0;
    }
  else if ( fabs(range-1.0) <= jpepsln )
    {
      binscale = 1 - nbpv;
    }
  else if ( range > 1.0 )
    {
      const double rangec = range + jpepsln;
      double p2 = 2.0;
      int jloop = 1;
      while ( jloop < 128 && p2 <= rangec )
        {
          p2 *= 2.0;
          ++jloop;
        }
      if (jloop < 128)
        binscale = jloop - nbpv;
      else
        {
          gprintf(__func__, "Problem calculating binary scale value for encode code %d!", code);
          gprintf(__func__, "> range %g rangec %g fmin %g fmax %g", range, rangec, fmin, fmax);
          return (707);
        }
    }
  else
    {
      const double rangec = range - jpepsln;
      double p05 = 0.5;
      int jloop = 1;
      while ( jloop < 127 && p05 >= rangec )
	{
          p05 *= 0.5;
          jloop++;
	}
      if ( jloop < 127 )
	{
	  binscale = 1 - jloop - nbpv;
	}
      else
	{
	  gprintf(__func__, "Problem calculating binary scale value for encode code %d!", code);
	  gprintf(__func__, "< range %g rangec %g fmin %g fmax %g", range, rangec, fmin, fmax);
	  return (707);
	}
    }

  const uint64_t max_nbpv_pow2 = (uint64_t) ((1ULL << nbpv) - 1);

  if ( binscale != 0 )
    {
      while ( (uint64_t)(ldexp(range, -binscale)+0.5) > max_nbpv_pow2 ) binscale++;

      factor = (T)intpow2(-binscale);
    }

  ref2ibm(&zref, BitsPerInt);

  Put3Byte(blockLength);      //  0-2 Length of Block 4
  Put1Byte(Flag);             //  3   Flag & Unused bits
  if ( binscale < 0 ) binscale = 32768 - binscale;
  Put2Byte(binscale);         //  4-5 Scale factor
  Put1Real(zref);             //  6-9 Reference value
  Put1Byte(nbpv);             //   10 Packing size

  if ( lspherc )
    {
      if ( lcomplex )
	{
	  const int jup = isubset;
	  int ioff = (int)z + bds_ext;
	  if ( ioff > 0xFFFF ) ioff = 0;
	  Put2Byte(ioff);
	  Put2Int(isec4[16]);
	  Put1Byte(jup);
	  Put1Byte(jup);
	  Put1Byte(jup);
	  for (long i = 0; i < ((jup+1)*(jup+2)); ++i) Put1Real((double)(data[i]));
	}
      else
	{
	  Put1Real((double)(data[0]));
	}
    }

  *datsize  = ((datasize-PackStart)*nbpv + 7)/8;

#if  defined  (_ARCH_PWR6)
  TEMPLATE(encode_array_unrolled,T)(nbpv, (size_t)PackStart, (size_t)datasize, lGrib, data, (T)zref, factor, &z);
#else
  TEMPLATE(encode_array,T)(nbpv, (size_t)PackStart, (size_t)datasize, lGrib, data, (T)zref, factor, &z);
#endif

  if ( unused_bits >= 8 ) Put1Byte(0);  //  Fillbyte

  *gribLen = (long)z;

  return 0;
}


void TEMPLATE(grib_encode,T)(int *isec0, int *isec1, int *isec2, T *fsec2, int *isec3,
			     T *fsec3, int *isec4, T *fsec4, int klenp, int *kgrib,
			     int kleng, int *kword, int efunc, int *kret)
{
  long gribLen = 0; // Counter of GRIB length for output
  long fsec4size = 0;
  long datstart, datsize;

  UNUSED(isec3);
  UNUSED(efunc);

  grsdef();

  unsigned char *CGrib = (unsigned char *) kgrib;

  const bool gdsIncluded = ISEC1_Sec2Or3Flag & 128;
  const bool bmsIncluded = ISEC1_Sec2Or3Flag & 64;

  // set max header len
  size_t len = 16384;

  // add data len
  const size_t numBytes = (size_t)((ISEC4_NumBits+7)>>3);

  len += numBytes*(size_t)klenp;

  // add bitmap len
  if ( bmsIncluded ) len += (size_t)((klenp+7)>>3);

#ifdef VECTORCODE
  GRIBPACK *lGrib = (GRIBPACK*) Malloc(len*sizeof(GRIBPACK));
  if ( lGrib == NULL ) SysError("No Memory!");
#else
  GRIBPACK *lGrib = CGrib;
#endif

  const long isLen = 8;
  encodeIS(lGrib, &gribLen);
  GRIBPACK *lpds = &lGrib[isLen];
  const long pdsLen = getPdsLen(isec1);

  encodePDS(lpds, pdsLen,  isec1);
  gribLen += pdsLen;
  /*
  if ( ( isec4[3] == 64 ) && ( isec2[5] == 2 ) )
    {
      static bool lwarn_cplx = true;

      if ( lwarn_cplx )
	Message("Complex packing of spectral data unsupported, using simple packing!");

      isec2[5] = 1;
      isec4[3] = 0;

      lwarn_cplx = false;
    }
  */
  if ( gdsIncluded ) TEMPLATE(encodeGDS,T)(lGrib, &gribLen, isec2, fsec2);
  /*
    ----------------------------------------------------------------
    BMS Bit-Map Section Section (Section 3)
    ----------------------------------------------------------------
  */ 
  if ( bmsIncluded )
    {
      TEMPLATE(encodeBMS,T)(lGrib, &gribLen, fsec3, isec4, fsec4, &fsec4size);
    }
  else
    {
      fsec4size = ISEC4_NumValues;
    }

  const long bdsstart = gribLen;
  int status = TEMPLATE(encodeBDS,T)(lGrib, &gribLen, ISEC1_DecScaleFactor, isec2,
                                     isec4, fsec4size, fsec4, &datstart, &datsize, ISEC1_Parameter);
  if ( status )
    {
      *kret = status;
      return;
    }

  encodeES(lGrib, &gribLen, bdsstart);

  if ( (size_t) gribLen > (size_t)kleng*sizeof(int) )
    Error("kgrib buffer too small! kleng = %d  gribLen = %d", kleng, gribLen);

#ifdef VECTORCODE
  if ( (size_t) gribLen > len )
    Error("lGrib buffer too small! len = %d  gribLen = %d", len, gribLen);

  (void) PACK_GRIB(lGrib, (unsigned char *)CGrib, gribLen, -1L);

  Free(lGrib);
#endif

  ISEC0_GRIB_Len     = (int)gribLen;
  ISEC0_GRIB_Version = 1;

  *kword = (int)((gribLen + (long)sizeof(int) - 1) / (long)sizeof(int));

  *kret = status;
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */

#ifdef T
#undef T
#endif
#define T float
#ifdef T

// GRIB BLOCK 2 - GRID DESCRIPTION SECTION
static
void TEMPLATE(encodeGDS,T)(GRIBPACK *lGrib, long *gribLen, int *isec2, T *fsec2)
{
  long z = *gribLen;
  int exponent, mantissa;
  int ival;
  int gdslen = 32;

  if ( ISEC2_GridType == GRIB1_GTYPE_LCC ) gdslen += 10;

  if ( ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )  gdslen += 10;

  const int pvoffset = (ISEC2_NumVCP || ISEC2_Reduced) ? gdslen + 1 : 0xFF;

  if ( ISEC2_Reduced ) gdslen += 2 * ISEC2_NumLat;

  gdslen += ISEC2_NumVCP * 4;

  Put3Byte(gdslen);             /*  0- 2 Length of Block 2 Byte 0 */
  Put1Byte(ISEC2_NumVCP);       /*  3    NV */
  Put1Byte(pvoffset);           /*  4    PV */
  Put1Byte(ISEC2_GridType);     /*  5    LatLon=0 Gauss=4 Spectral=50 */

  if ( ISEC2_GridType == GRIB1_GTYPE_SPECTRAL )
    {
      Put2Byte(ISEC2_PentaJ);   /*  6- 7 Pentagonal resolution J  */
      Put2Byte(ISEC2_PentaK);   /*  8- 9 Pentagonal resolution K  */
      Put2Byte(ISEC2_PentaM);   /* 10-11 Pentagonal resolution M  */
      Put1Byte(ISEC2_RepType);  /* 12    Representation type      */
      Put1Byte(ISEC2_RepMode);  /* 13    Representation mode      */
      PutnZero(18);             /* 14-31 reserved                 */
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_GME )
    {
      Put2Byte(ISEC2_GME_NI2);
      Put2Byte(ISEC2_GME_NI3);
      Put3Byte(ISEC2_GME_ND);
      Put3Byte(ISEC2_GME_NI);
      Put1Byte(ISEC2_GME_AFlag);
      Put3Int(ISEC2_GME_LatPP);
      Put3Int(ISEC2_GME_LonPP);
      Put3Int(ISEC2_GME_LonMPL);
      Put1Byte(ISEC2_GME_BFlag);
      PutnZero(5);
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LCC )
    {
      Put2Byte(ISEC2_NumLon);          /*  6- 7 Longitudes               */

      Put2Byte(ISEC2_NumLat);          /*  8- 9 Latitudes                */
      Put3Int(ISEC2_FirstLat);
      Put3Int(ISEC2_FirstLon);
      Put1Byte(ISEC2_ResFlag);         /* 16    Resolution flag          */
      Put3Int(ISEC2_Lambert_Lov);      /* 17-19 */
      Put3Int(ISEC2_Lambert_dx);       /* 20-22 */
      Put3Int(ISEC2_Lambert_dy);       /* 23-25 */
      Put1Byte(ISEC2_Lambert_ProjFlag);/* 26    Projection flag          */
      Put1Byte(ISEC2_ScanFlag);        /* 27    Scanning mode            */
      Put3Int(ISEC2_Lambert_LatS1);    /* 28-30 */  
      Put3Int(ISEC2_Lambert_LatS2);    /* 31-33 */
      Put3Int(ISEC2_Lambert_LatSP);    /* 34-36 */  
      Put3Int(ISEC2_Lambert_LonSP);    /* 37-39 */
      PutnZero(2);                     /* 34-41 */
    }
  else if ( ISEC2_GridType == GRIB1_GTYPE_LATLON    ||
	    ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN  ||
	    ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
    {
      const int numlon = ISEC2_Reduced ? 0xFFFF : ISEC2_NumLon;
      Put2Byte(numlon);                /*  6- 7 Number of Longitudes     */

      Put2Byte(ISEC2_NumLat);          /*  8- 9 Number of Latitudes      */
      Put3Int(ISEC2_FirstLat);
      Put3Int(ISEC2_FirstLon);
      Put1Byte(ISEC2_ResFlag);         /* 16    Resolution flag          */
      Put3Int(ISEC2_LastLat);
      Put3Int(ISEC2_LastLon);
      const unsigned lonIncr = (ISEC2_ResFlag == 0) ? 0xFFFF : (unsigned)ISEC2_LonIncr;
      const unsigned latIncr = (ISEC2_ResFlag == 0) ? 0xFFFF : (unsigned)ISEC2_LatIncr;
      Put2Byte(lonIncr);               /* 23-24 i - direction increment  */
      if ( ISEC2_GridType == GRIB1_GTYPE_GAUSSIAN )
	Put2Byte(ISEC2_NumPar);        /* 25-26 Latitudes Pole->Equator  */
      else
	Put2Byte(latIncr);             /* 25-26 j - direction increment  */

      Put1Byte(ISEC2_ScanFlag);        /* 27    Scanning mode            */
      PutnZero(4);                     /* 28-31 reserved                 */

      if ( ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT )
	{
	  Put3Int(ISEC2_LatSP);
	  Put3Int(ISEC2_LonSP);
	  Put1Real((double)(FSEC2_RotAngle));
	}
    }
  else
    {
      Error("Unsupported grid type %d", ISEC2_GridType);
    }

#if defined (SX)
#pragma vdir novector     /* vectorization gives wrong results on NEC */
#endif
  for (long i = 0; i < ISEC2_NumVCP; ++i)
    {
      Put1Real((double)(fsec2[10+i]));
    }

  if ( ISEC2_Reduced )
    for (long i = 0; i < ISEC2_NumLat; ++i) Put2Byte(ISEC2_ReducedPoints(i));

  *gribLen = z;
}

// GRIB BLOCK 3 - BIT MAP SECTION
static
void TEMPLATE(encodeBMS,T)(GRIBPACK *lGrib, long *gribLen, T *fsec3, int *isec4, T *data, long *datasize)
{
  long z = *gribLen;
  static bool lmissvalinfo = true;
  //  unsigned int c, imask;

  if ( DBL_IS_NAN(FSEC3_MissVal) && lmissvalinfo)
    {
      lmissvalinfo = false;
      Message("Missing value = NaN is unsupported!");
    }

  const long bitmapSize = ISEC4_NumValues;
  const long imaskSize = ((bitmapSize+7)>>3)<<3;
  GRIBPACK *bitmap = &lGrib[z+6];
  long fsec4size = 0;

#ifdef VECTORCODE
  unsigned int *imask = (unsigned int*) Malloc(imaskSize*sizeof(unsigned int));
  memset(imask, 0, imaskSize*sizeof(int));

#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
  for (long i = 0; i < bitmapSize; ++i)
    {
      if ( IS_NOT_EQUAL(data[i], FSEC3_MissVal) )
	{
	  data[fsec4size++] = data[i];
	  imask[i] = 1;
	}
    }

#if defined (CRAY)
#pragma _CRI ivdep
#endif
#if defined (SX)
#pragma vdir nodep
#endif
#ifdef __uxpch__
#pragma loop novrec
#endif
  for (long i = 0; i < imaskSize/8; ++i)
    {
      bitmap[i] = (imask[i*8+0] << 7) | (imask[i*8+1] << 6) |
	          (imask[i*8+2] << 5) | (imask[i*8+3] << 4) |
	          (imask[i*8+4] << 3) | (imask[i*8+5] << 2) |
	          (imask[i*8+6] << 1) | (imask[i*8+7]);
    }

  Free(imask);
#else
  for (long i = 0; i < imaskSize/8; ++i) bitmap[i] = 0;

  for (long i = 0; i < bitmapSize; ++i)
    {
      if ( IS_NOT_EQUAL(data[i], FSEC3_MissVal) )
	{
	  data[fsec4size++] = data[i];
	  bitmap[i/8] |= (GRIBPACK)(1<<(7-(i&7)));
	}
    }
#endif

  const long bmsLen = imaskSize/8 + 6;
  const long bmsUnusedBits = imaskSize - bitmapSize;

  Put3Byte(bmsLen);   /*  0- 2 Length of Block 3 Byte 0 */
  Put1Byte(bmsUnusedBits);
  Put2Byte(0);

  *gribLen += bmsLen;

  *datasize = fsec4size;
}

#define pow_double pow
#define pow_float powf

// GRIB BLOCK 4 - BINARY DATA SECTION
static
int TEMPLATE(encodeBDS,T)(GRIBPACK *lGrib, long *gribLen, int decscale, int *isec2, int *isec4, long datasize, T *data,
			  long *datstart, long *datsize, int code)
{
  // Uwe Schulzweida, 11/04/2003 : Check that number of bits per value is not exceeded
  // Uwe Schulzweida,  6/05/2003 : Copy result to fpval to prevent integer overflow

  size_t z = (size_t)*gribLen;
  int numBits;
  int ival;
  long PackStart = 0;
  int Flag = 0;
  int binscale = 0;
  int bds_head = 11;
  int bds_ext = 0;
  /* ibits = BitsPerInt; */
  int exponent, mantissa;
  bool lspherc = false;
  int isubset = 0, itemp = 0, itrunc = 0;
  T factor = 1, fmin, fmax;
  const double jpepsln = 1.0e-12; // -----> tolerance used to check equality
                                  //        of floating point numbers - needed
		                  //        on some platforms (eg vpp700, linux)
  extern int CGRIBEX_Const;       // 1: Don't pack constant fields on regular grids

  if ( isec2 )
    {
      /* If section 2 is present, it says if data is spherical harmonic */

      lspherc =  ( isec2[0] == 50 || isec2[0] == 60 ||
                   isec2[0] == 70 || isec2[0] == 80 );

      isec4[2] = lspherc ? 128 : 0;
    }
  else
    {
      /* Section 4 says if it's spherical harmonic data.. */

      lspherc = ( isec4[2] == 128 );
    }

  /* Complex packing supported for spherical harmonics. */

  const bool lcomplex = ( lspherc && ( isec4[3] == 64 ) ) ||
                        ( lspherc && isec2 && ( isec2[5] == 2 ) );

  // Check input specification is consistent

  if ( lcomplex && isec2 )
    {
      if ( ( isec4[3] != 64 ) && ( isec2[5] == 2 ) )
	{
	  gprintf(__func__, "  COMPLEX mismatch. isec4[3] = %d\n", isec4[3]);
	  gprintf(__func__, "  COMPLEX mismatch. isec2[5] = %d\n", isec2[5]);
	  return (807);
	}
      else if ( ( isec4[3] == 64 ) && ( isec2[5] != 2 ) )
	{
	  gprintf(__func__, "  COMPLEX mismatch. isec4[3] = %d\n", isec4[3]);
	  gprintf(__func__, "  COMPLEX mismatch. isec2[5] = %d\n", isec2[5]);
	  return (807);
        }
      else if ( lcomplex )
	{
          // Truncation of full spectrum, which is supposed triangular, has to be diagnosed. Define also sub-set truncation.
	  isubset = isec4[17];
	  // When encoding, use the total number of data.
	  itemp   = isec4[0];
	  itrunc  = (int) (sqrt(itemp*4 + 1.) - 3) / 2;
	}
    }

  if ( decscale )
    {
      const T scale = TEMPLATE(pow,T)((T)10.0, (T)decscale);
      for (long i = 0; i < datasize; ++i) data[i] *= scale;
    }

  if ( lspherc )
    {
      if ( lcomplex )
	{
	  const int jup  = isubset;
	  const int ioff = (jup+1)*(jup+2);
	  bds_ext = 4 + 3 + 4*ioff;
	  PackStart = ioff;
	  Flag = 192;
	}
      else
	{
	  bds_ext = 4;
	  PackStart = 1;
	  Flag = 128;
	}
    }

  *datstart = bds_head + bds_ext;

  int nbpv = numBits = ISEC4_NumBits;

  if ( lspherc && lcomplex )
    {
      const int pcStart = isubset;
      const int pcScale = isec4[16];
      TEMPLATE(scale_complex,T)(data, pcStart, pcScale, itrunc, 0);
      TEMPLATE(gather_complex,T)(data, (size_t)pcStart, (size_t)itrunc, (size_t)datasize);
    }

  fmin = fmax = data[PackStart];

  TEMPLATE(minmax_val,T)(data+PackStart, datasize-PackStart, &fmin, &fmax);

  double zref = (double)fmin;
  if (!(zref < DBL_MAX && zref > -DBL_MAX))
    {
      gprintf(__func__, "Minimum value out of range: %g!", zref);
      return (707);
    }

  if ( CGRIBEX_Const && !lspherc )
    {
      if ( IS_EQUAL(fmin, fmax) ) nbpv = 0;
    }

  long blockLength = (*datstart) + (nbpv*(datasize - PackStart) + 7)/8;
  blockLength += blockLength & 1;

  const long unused_bits = blockLength*8 - (*datstart)*8 - nbpv*(datasize - PackStart);

  Flag += (int)unused_bits;


  // Adjust number of bits per value if full integer length to avoid hitting most significant bit (sign bit).
  // if( nbpv == ibits ) nbpv = nbpv - 1;
  /*
    Calculate the binary scaling factor to spread the range of values over the number of bits per value.
    Limit scaling to 2**-126 to 2**127 (using IEEE 32-bit floatsas a guideline).
  */
  const double range = fabs(fmax - fmin);

  if ( fabs(fmin) < FLT_MIN ) fmin = 0;
  /*
    Have to allow tolerance in comparisons on some platforms (eg vpp700 and linux),
    such as 0.9999999999999999 = 1.0, to avoid clipping ranges which are a power of 2.
  */
  if ( range <= jpepsln )
    {
      binscale = 0;
    }
  else if ( IS_NOT_EQUAL(fmin, 0.0) && (fabs(range/fmin) <= jpepsln) )
    {
      binscale = 0;
    }
  else if ( fabs(range-1.0) <= jpepsln )
    {
      binscale = 1 - nbpv;
    }
  else if ( range > 1.0 )
    {
      const double rangec = range + jpepsln;
      double p2 = 2.0;
      int jloop = 1;
      while ( jloop < 128 && p2 <= rangec )
        {
          p2 *= 2.0;
          ++jloop;
        }
      if (jloop < 128)
        binscale = jloop - nbpv;
      else
        {
          gprintf(__func__, "Problem calculating binary scale value for encode code %d!", code);
          gprintf(__func__, "> range %g rangec %g fmin %g fmax %g", range, rangec, fmin, fmax);
          return (707);
        }
    }
  else
    {
      const double rangec = range - jpepsln;
      double p05 = 0.5;
      int jloop = 1;
      while ( jloop < 127 && p05 >= rangec )
	{
          p05 *= 0.5;
          jloop++;
	}
      if ( jloop < 127 )
	{
	  binscale = 1 - jloop - nbpv;
	}
      else
	{
	  gprintf(__func__, "Problem calculating binary scale value for encode code %d!", code);
	  gprintf(__func__, "< range %g rangec %g fmin %g fmax %g", range, rangec, fmin, fmax);
	  return (707);
	}
    }

  const uint64_t max_nbpv_pow2 = (uint64_t) ((1ULL << nbpv) - 1);

  if ( binscale != 0 )
    {
      while ( (uint64_t)(ldexp(range, -binscale)+0.5) > max_nbpv_pow2 ) binscale++;

      factor = (T)intpow2(-binscale);
    }

  ref2ibm(&zref, BitsPerInt);

  Put3Byte(blockLength);      //  0-2 Length of Block 4
  Put1Byte(Flag);             //  3   Flag & Unused bits
  if ( binscale < 0 ) binscale = 32768 - binscale;
  Put2Byte(binscale);         //  4-5 Scale factor
  Put1Real(zref);             //  6-9 Reference value
  Put1Byte(nbpv);             //   10 Packing size

  if ( lspherc )
    {
      if ( lcomplex )
	{
	  const int jup = isubset;
	  int ioff = (int)z + bds_ext;
	  if ( ioff > 0xFFFF ) ioff = 0;
	  Put2Byte(ioff);
	  Put2Int(isec4[16]);
	  Put1Byte(jup);
	  Put1Byte(jup);
	  Put1Byte(jup);
	  for (long i = 0; i < ((jup+1)*(jup+2)); ++i) Put1Real((double)(data[i]));
	}
      else
	{
	  Put1Real((double)(data[0]));
	}
    }

  *datsize  = ((datasize-PackStart)*nbpv + 7)/8;

#if  defined  (_ARCH_PWR6)
  TEMPLATE(encode_array_unrolled,T)(nbpv, (size_t)PackStart, (size_t)datasize, lGrib, data, (T)zref, factor, &z);
#else
  TEMPLATE(encode_array,T)(nbpv, (size_t)PackStart, (size_t)datasize, lGrib, data, (T)zref, factor, &z);
#endif

  if ( unused_bits >= 8 ) Put1Byte(0);  //  Fillbyte

  *gribLen = (long)z;

  return 0;
}


void TEMPLATE(grib_encode,T)(int *isec0, int *isec1, int *isec2, T *fsec2, int *isec3,
			     T *fsec3, int *isec4, T *fsec4, int klenp, int *kgrib,
			     int kleng, int *kword, int efunc, int *kret)
{
  long gribLen = 0; // Counter of GRIB length for output
  long fsec4size = 0;
  long datstart, datsize;

  UNUSED(isec3);
  UNUSED(efunc);

  grsdef();

  unsigned char *CGrib = (unsigned char *) kgrib;

  const bool gdsIncluded = ISEC1_Sec2Or3Flag & 128;
  const bool bmsIncluded = ISEC1_Sec2Or3Flag & 64;

  // set max header len
  size_t len = 16384;

  // add data len
  const size_t numBytes = (size_t)((ISEC4_NumBits+7)>>3);

  len += numBytes*(size_t)klenp;

  // add bitmap len
  if ( bmsIncluded ) len += (size_t)((klenp+7)>>3);

#ifdef VECTORCODE
  GRIBPACK *lGrib = (GRIBPACK*) Malloc(len*sizeof(GRIBPACK));
  if ( lGrib == NULL ) SysError("No Memory!");
#else
  GRIBPACK *lGrib = CGrib;
#endif

  const long isLen = 8;
  encodeIS(lGrib, &gribLen);
  GRIBPACK *lpds = &lGrib[isLen];
  const long pdsLen = getPdsLen(isec1);

  encodePDS(lpds, pdsLen,  isec1);
  gribLen += pdsLen;
  /*
  if ( ( isec4[3] == 64 ) && ( isec2[5] == 2 ) )
    {
      static bool lwarn_cplx = true;

      if ( lwarn_cplx )
	Message("Complex packing of spectral data unsupported, using simple packing!");

      isec2[5] = 1;
      isec4[3] = 0;

      lwarn_cplx = false;
    }
  */
  if ( gdsIncluded ) TEMPLATE(encodeGDS,T)(lGrib, &gribLen, isec2, fsec2);
  /*
    ----------------------------------------------------------------
    BMS Bit-Map Section Section (Section 3)
    ----------------------------------------------------------------
  */ 
  if ( bmsIncluded )
    {
      TEMPLATE(encodeBMS,T)(lGrib, &gribLen, fsec3, isec4, fsec4, &fsec4size);
    }
  else
    {
      fsec4size = ISEC4_NumValues;
    }

  const long bdsstart = gribLen;
  int status = TEMPLATE(encodeBDS,T)(lGrib, &gribLen, ISEC1_DecScaleFactor, isec2,
                                     isec4, fsec4size, fsec4, &datstart, &datsize, ISEC1_Parameter);
  if ( status )
    {
      *kret = status;
      return;
    }

  encodeES(lGrib, &gribLen, bdsstart);

  if ( (size_t) gribLen > (size_t)kleng*sizeof(int) )
    Error("kgrib buffer too small! kleng = %d  gribLen = %d", kleng, gribLen);

#ifdef VECTORCODE
  if ( (size_t) gribLen > len )
    Error("lGrib buffer too small! len = %d  gribLen = %d", len, gribLen);

  (void) PACK_GRIB(lGrib, (unsigned char *)CGrib, gribLen, -1L);

  Free(lGrib);
#endif

  ISEC0_GRIB_Len     = (int)gribLen;
  ISEC0_GRIB_Version = 1;

  *kword = (int)((gribLen + (long)sizeof(int) - 1) / (long)sizeof(int));

  *kret = status;
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */
// 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);
}
static const char grb_libvers[] = "2.3.1";
const char *
cgribexLibraryVersion(void)
{
  return (grb_libvers);
}

#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)
#pragma GCC diagnostic pop
#endif
#ifdef HAVE_CONFIG_H
#endif

#include <inttypes.h>
#include <stdlib.h>
#include <sys/types.h>
#ifdef WORDS_BIGENDIAN
#include <limits.h>
#endif


static const uint32_t crctab[]
    = { 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f,
        0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
        0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3,
        0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
        0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027,
        0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
        0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c,
        0x2e003dc5, 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
        0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08,
        0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
        0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044,
        0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
        0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59,
        0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
        0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5,
        0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
        0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601,
        0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
        0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad,
        0x81b02d74, 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
        0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e,
        0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
        0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12,
        0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
        0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06,
        0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 };

uint32_t
memcrc(const unsigned char *b, size_t n)
{
  /*  Input arguments:
   *  const char*   b == byte sequence to checksum
   *  size_t        n == length of sequence
   */

  uint32_t s = 0;

  memcrc_r(&s, b, n);

  /* Extend with the length of the string. */
  while (n != 0)
  {
    register uint32_t c = n & 0377;
    n >>= 8;
    s = (s << 8) ^ crctab[(s >> 24) ^ c];
  }

  return ~s;
}

void
memcrc_r(uint32_t *state, const unsigned char *block, size_t block_len)
{
  /*  Input arguments:
   *  const char*   b == byte sequence to checksum
   *  size_t        n == length of sequence
   */

  register uint32_t c, s = *state;
  register size_t n = block_len;
  register const unsigned char *b = block;

  for (; n > 0; --n)
  {
    c = (uint32_t) (*b++);
    s = (s << 8) ^ crctab[(s >> 24) ^ c];
  }

  *state = s;
}

#ifdef WORDS_BIGENDIAN
#define SWAP_CSUM(BITWIDTH, BYTEWIDTH, NACC)                             \
  do {                                                                   \
    register const uint##BITWIDTH##_t *b = (uint##BITWIDTH##_t *) elems; \
    for (size_t i = 0; i < num_elems; ++i)                               \
    {                                                                    \
      for (size_t aofs = NACC; aofs > 0; --aofs)                         \
      {                                                                  \
        uint##BITWIDTH##_t accum = b[i + aofs - 1];                      \
        for (size_t j = 0; j < BYTEWIDTH; ++j)                           \
        {                                                                \
          uint32_t c = (uint32_t) (accum & UCHAR_MAX);                   \
          s = (s << 8) ^ crctab[(s >> 24) ^ c];                          \
          accum >>= 8;                                                   \
        }                                                                \
      }                                                                  \
    }                                                                    \
  } while (0)
#endif

/**
 *  Does endian-swapping prior to checksumming in case platform is big-endian
 *
 *  @param elems points to first first element with alignment elem_size
 *  @param num_elems number of elements to process
 *  @param elem_size size of each element in bytes
 */
void
memcrc_r_eswap(uint32_t *state, const unsigned char *elems, size_t num_elems, size_t elem_size)
{
#ifdef WORDS_BIGENDIAN
  register uint32_t s = *state;

  switch (elem_size)
  {
    case 1: memcrc_r(state, elems, num_elems * elem_size); return;
    case 2: SWAP_CSUM(16, 2, 1); break;
    case 4: SWAP_CSUM(32, 4, 1); break;
    case 8: SWAP_CSUM(64, 8, 1); break;
    case 16: SWAP_CSUM(64, 8, 2); break;
  }
  *state = s;
#else
  memcrc_r(state, elems, num_elems * elem_size);
#endif
}

uint32_t
memcrc_finish(uint32_t *state, off_t total_size)
{
  register uint32_t c, s = *state;
  register uint64_t n = (uint64_t) total_size;

  /* Extend with the length of the string. */
  while (n != 0)
  {
    c = n & 0377;
    n >>= 8;
    s = (s << 8) ^ crctab[(s >> 24) ^ c];
  }

  return ~s;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <errno.h>

#if !defined(HAVE_CONFIG_H) && !defined(HAVE_MALLOC_H) && defined(SX)
#define HAVE_MALLOC_H
#endif

#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif


enum
{
  MALLOC_FUNC = 0,
  CALLOC_FUNC,
  REALLOC_FUNC,
  FREE_FUNC
};

static const char *const memfunc[] = { "Malloc", "Calloc", "Realloc", "Free" };

#undef MEM_UNDEFID
#define MEM_UNDEFID -1

enum
{
  MEM_MAXNAME = 32 /* Min = 8, for  "unknown" ! */

};
static int dmemory_ExitOnError = 1;

typedef struct
{
  void *ptr;
  size_t size;
  size_t nobj;
  int item;
  int mtype;
  int line;
  char filename[MEM_MAXNAME];
  char functionname[MEM_MAXNAME];
} MemTable_t;

static MemTable_t *memTable;
static size_t memTableSize = 0;
static long memAccess = 0;

static size_t MemObjs = 0;
static size_t MaxMemObjs = 0;
static size_t MemUsed = 0;
static size_t MaxMemUsed = 0;

static int MEM_Debug = 0; /* If set to 1, debugging */
static int MEM_Info = 0;  /* If set to 1, print mem table at exit */

static const char *
get_filename(const char *file)
{
  const char *fnptr = strrchr(file, '/');
  if (fnptr)
    fnptr++;
  else
    fnptr = (char *) file;

  return fnptr;
}

void
memDebug(int debug)
{
  MEM_Debug = debug;
  if (MEM_Debug && !MEM_Info) MEM_Info = 1;
}

// If we're not using GNU C, elide __attribute__
#if !defined __GNUC__ && !defined __attribute__
#define __attribute__(x) /*NOTHING*/
#endif

static void memInternalProblem(const char *caller, const char *fmt, ...) __attribute__((noreturn));

static void memError(const char *caller, const char *file, int line, size_t size) __attribute__((noreturn));

static void
memInternalProblem(const char *caller, const char *fmt, ...)
{
  va_list args;
  va_start(args, fmt);

  printf("\n");
  fprintf(stderr, "Internal problem (%s) : ", caller);
  vfprintf(stderr, fmt, args);
  fprintf(stderr, "\n");

  va_end(args);

  exit(EXIT_FAILURE);
}

static void
memError(const char *caller, const char *file, int line, size_t size)
{
  fputs("\n", stdout);
  fprintf(stderr, "Error (%s) : Allocation of %zu bytes failed. [ line %d file %s ]\n", caller, size, line, get_filename(file));

  if (errno) perror("System error message ");

  exit(EXIT_FAILURE);
}

static void
memListPrintEntry(int mtype, int item, size_t size, void *ptr, const char *caller, const char *file, int line)
{
  fprintf(stderr, "[%-7s ", memfunc[mtype]);

  fprintf(stderr, "memory item %3d ", item);
  fprintf(stderr, "(%6zu byte) ", size);
  fprintf(stderr, "at %p", ptr);
  if (file != NULL)
  {
    fprintf(stderr, " line %4d", line);
    fprintf(stderr, " file %s", get_filename(file));
  }
  if (caller != NULL) fprintf(stderr, " (%s)", caller);
  fprintf(stderr, "]\n");
}

static void
memListPrintTable(void)
{
  if (MemObjs) fprintf(stderr, "\nMemory table:\n");

  for (size_t memID = 0; memID < memTableSize; memID++)
  {
    if (memTable[memID].item != MEM_UNDEFID)
      memListPrintEntry(memTable[memID].mtype, memTable[memID].item, memTable[memID].size * memTable[memID].nobj,
                        memTable[memID].ptr, memTable[memID].functionname, memTable[memID].filename, memTable[memID].line);
  }

  if (MemObjs)
  {
    fprintf(stderr, "  Memory access             : %6u\n", (unsigned) memAccess);
    fprintf(stderr, "  Maximum objects           : %6zu\n", memTableSize);
    fprintf(stderr, "  Objects used              : %6u\n", (unsigned) MaxMemObjs);
    fprintf(stderr, "  Objects in use            : %6u\n", (unsigned) MemObjs);
    fprintf(stderr, "  Memory allocated          : ");
    if (MemUsed > 1024 * 1024 * 1024)
      fprintf(stderr, " %5d GB\n", (int) (MemUsed / (1024 * 1024 * 1024)));
    else if (MemUsed > 1024 * 1024)
      fprintf(stderr, " %5d MB\n", (int) (MemUsed / (1024 * 1024)));
    else if (MemUsed > 1024)
      fprintf(stderr, " %5d KB\n", (int) (MemUsed / (1024)));
    else
      fprintf(stderr, " %5d Byte\n", (int) MemUsed);
  }

  if (MaxMemUsed)
  {
    fprintf(stderr, "  Maximum memory allocated  : ");
    if (MaxMemUsed > 1024 * 1024 * 1024)
      fprintf(stderr, " %5d GB\n", (int) (MaxMemUsed / (1024 * 1024 * 1024)));
    else if (MaxMemUsed > 1024 * 1024)
      fprintf(stderr, " %5d MB\n", (int) (MaxMemUsed / (1024 * 1024)));
    else if (MaxMemUsed > 1024)
      fprintf(stderr, " %5d KB\n", (int) (MaxMemUsed / (1024)));
    else
      fprintf(stderr, " %5d Byte\n", (int) MaxMemUsed);
  }
}

static void
memGetDebugLevel(void)
{
  const char *envstr = getenv("MEMORY_INFO");
  if (envstr && isdigit((int) envstr[0])) MEM_Info = atoi(envstr);

  envstr = getenv("MEMORY_DEBUG");
  if (envstr && isdigit((int) envstr[0])) MEM_Debug = atoi(envstr);

  if (MEM_Debug && !MEM_Info) MEM_Info = 1;

  if (MEM_Info) atexit(memListPrintTable);
}

static void
memInit(void)
{
  static int initDebugLevel = 0;

  if (!initDebugLevel)
  {
    memGetDebugLevel();
    initDebugLevel = 1;
  }
}

static int
memListDeleteEntry(void *ptr, size_t *size)
{
  int item = MEM_UNDEFID;
  size_t memID = 0;

  for (memID = 0; memID < memTableSize; memID++)
  {
    if (memTable[memID].item == MEM_UNDEFID) continue;
    if (memTable[memID].ptr == ptr) break;
  }

  if (memID != memTableSize)
  {
    MemObjs--;
    MemUsed -= memTable[memID].size * memTable[memID].nobj;
    *size = memTable[memID].size * memTable[memID].nobj;
    item = memTable[memID].item;
    memTable[memID].item = MEM_UNDEFID;
  }

  return item;
}

static void
memTableInitEntry(size_t memID)
{
  if (memID >= memTableSize) memInternalProblem(__func__, "memID %d undefined!", memID);

  memTable[memID].ptr = NULL;
  memTable[memID].item = MEM_UNDEFID;
  memTable[memID].size = 0;
  memTable[memID].nobj = 0;
  memTable[memID].mtype = MEM_UNDEFID;
  memTable[memID].line = MEM_UNDEFID;
}

static void
set_filename(const char *file, char *memEntyFilename)
{
  if (file)
  {
    const char *filename = get_filename(file);
    size_t len = strlen(filename);
    if (len > MEM_MAXNAME - 1) len = MEM_MAXNAME - 1;

    (void) memcpy(memEntyFilename, filename, len);
    memEntyFilename[len] = '\0';
  }
  else { (void) strcpy(memEntyFilename, "unknown"); }
}

static void
set_functionname(const char *functionname, char *memEntyFunctionname)
{
  if (functionname)
  {
    size_t len = strlen(functionname);
    if (len > MEM_MAXNAME - 1) len = MEM_MAXNAME - 1;

    (void) memcpy(memEntyFunctionname, functionname, len);
    memEntyFunctionname[len] = '\0';
  }
  else { (void) strcpy(memEntyFunctionname, "unknown"); }
}

static int
memListNewEntry(int mtype, void *ptr, size_t size, size_t nobj, const char *functionname, const char *file, int line)
{
  static int item = 0;
  size_t memID = 0;

  // Look for a free slot in memTable (Create the table the first time through).
  if (memTableSize == 0)
  {
    memTableSize = 8;
    size_t memSize = memTableSize * sizeof(MemTable_t);
    memTable = (MemTable_t *) malloc(memSize);
    if (memTable == NULL) memError(__func__, __FILE__, __LINE__, memSize);

    for (size_t i = 0; i < memTableSize; i++) memTableInitEntry(i);
  }
  else
  {
    while (memID < memTableSize)
    {
      if (memTable[memID].item == MEM_UNDEFID) break;
      memID++;
    }
  }

  // If the table overflows, double its size.
  if (memID == memTableSize)
  {
    memTableSize = 2 * memTableSize;
    size_t memSize = memTableSize * sizeof(MemTable_t);
    memTable = (MemTable_t *) realloc(memTable, memSize);
    if (memTable == NULL) memError(__func__, __FILE__, __LINE__, memSize);

    for (size_t i = memID; i < memTableSize; i++) memTableInitEntry(i);
  }

  memTable[memID].item = item;
  memTable[memID].ptr = ptr;
  memTable[memID].size = size;
  memTable[memID].nobj = nobj;
  memTable[memID].mtype = mtype;
  memTable[memID].line = line;

  set_filename(file, memTable[memID].filename);
  set_functionname(functionname, memTable[memID].functionname);

  MaxMemObjs++;
  MemObjs++;
  MemUsed += size * nobj;
  if (MemUsed > MaxMemUsed) MaxMemUsed = MemUsed;

  return item++;
}

static int
memListChangeEntry(intptr_t ptrold, void *ptr, size_t size, const char *functionname, const char *file, int line)
{
  int item = MEM_UNDEFID;
  size_t memID = 0;

  while (memID < memTableSize)
  {
    if (memTable[memID].item != MEM_UNDEFID && (intptr_t) memTable[memID].ptr == ptrold) break;
    memID++;
  }

  if (memID == memTableSize)
  {
    if ((void *) ptrold != NULL) memInternalProblem(__func__, "Item at %p not found.", ptrold);
  }
  else
  {
    item = memTable[memID].item;

    size_t sizeold = memTable[memID].size * memTable[memID].nobj;

    memTable[memID].ptr = ptr;
    memTable[memID].size = size;
    memTable[memID].nobj = 1;
    memTable[memID].mtype = REALLOC_FUNC;
    memTable[memID].line = line;

    set_filename(file, memTable[memID].filename);
    set_functionname(functionname, memTable[memID].functionname);

    MemUsed -= sizeold;
    MemUsed += size;
    if (MemUsed > MaxMemUsed) MaxMemUsed = MemUsed;
  }

  return item;
}

void *
memCalloc(size_t nobjs, size_t size, const char *file, const char *functionname, int line)
{
  void *ptr = NULL;

  memInit();

  if (nobjs * size > 0)
  {
    ptr = calloc(nobjs, size);

    if (MEM_Info)
    {
      memAccess++;

      int item = MEM_UNDEFID;
      if (ptr) item = memListNewEntry(CALLOC_FUNC, ptr, size, nobjs, functionname, file, line);

      if (MEM_Debug) memListPrintEntry(CALLOC_FUNC, item, size * nobjs, ptr, functionname, file, line);
    }

    if (ptr == NULL && dmemory_ExitOnError) memError(functionname, file, line, size * nobjs);
  }
  else
    fprintf(stderr, "Warning (%s) : Allocation of 0 bytes! [ line %d file %s ]\n", functionname, line, file);

  return ptr;
}

void *
memMalloc(size_t size, const char *file, const char *functionname, int line)
{
  void *ptr = NULL;

  memInit();

  if (size > 0)
  {
    ptr = malloc(size);

    if (MEM_Info)
    {
      memAccess++;

      int item = MEM_UNDEFID;
      if (ptr) item = memListNewEntry(MALLOC_FUNC, ptr, size, 1, functionname, file, line);

      if (MEM_Debug) memListPrintEntry(MALLOC_FUNC, item, size, ptr, functionname, file, line);
    }

    if (ptr == NULL && dmemory_ExitOnError) memError(functionname, file, line, size);
  }
  else
    fprintf(stderr, "Warning (%s) : Allocation of 0 bytes! [ line %d file %s ]\n", functionname, line, file);

  return ptr;
}

void *
memRealloc(void *ptrold, size_t size, const char *file, const char *functionname, int line)
{
  void *ptr = NULL;

  memInit();

  if (size > 0)
  {
    intptr_t ptrold_ = (intptr_t) ptrold;
    ptr = realloc(ptrold, size);

    if (MEM_Info)
    {
      memAccess++;

      int item = MEM_UNDEFID;
      if (ptr)
      {
        item = memListChangeEntry(ptrold_, ptr, size, functionname, file, line);
        if (item == MEM_UNDEFID) item = memListNewEntry(REALLOC_FUNC, ptr, size, 1, functionname, file, line);
      }

      if (MEM_Debug) memListPrintEntry(REALLOC_FUNC, item, size, ptr, functionname, file, line);
    }

    if (ptr == NULL && dmemory_ExitOnError) memError(functionname, file, line, size);
  }
  else
    fprintf(stderr, "Warning (%s) : Allocation of 0 bytes! [ line %d file %s ]\n", functionname, line, get_filename(file));

  return ptr;
}

void
memFree(void *ptr, const char *file, const char *functionname, int line)
{
  memInit();

  if (MEM_Info)
  {
    size_t size = 0;
    int item = memListDeleteEntry(ptr, &size);
    if (item >= 0)
    {
      if (MEM_Debug) memListPrintEntry(FREE_FUNC, item, size, ptr, functionname, file, line);
    }
    else
    {
      if (ptr && MEM_Debug)
        fprintf(stderr, "%s info: memory entry at %p not found. [line %4d file %s (%s)]\n", __func__, ptr, line, get_filename(file),
                functionname);
    }
  }

  free(ptr);
}

size_t
memTotal(void)
{
  size_t memtotal = 0;
#ifdef HAVE_MALLINFO
  struct mallinfo meminfo = mallinfo();
  if (MEM_Debug)
  {
    fprintf(stderr, "arena      %8zu (non-mmapped space allocated from system)\n", (size_t) meminfo.arena);
    fprintf(stderr, "ordblks    %8zu (number of free chunks)\n", (size_t) meminfo.ordblks);
    fprintf(stderr, "smblks     %8zu (number of fastbin blocks)\n", (size_t) meminfo.smblks);
    fprintf(stderr, "hblks      %8zu (number of mmapped regions)\n", (size_t) meminfo.hblks);
    fprintf(stderr, "hblkhd     %8zu (space in mmapped regions)\n", (size_t) meminfo.hblkhd);
    fprintf(stderr, "usmblks    %8zu (maximum total allocated space)\n", (size_t) meminfo.usmblks);
    fprintf(stderr, "fsmblks    %8zu (maximum total allocated space)\n", (size_t) meminfo.fsmblks);
    fprintf(stderr, "uordblks   %8zu (total allocated space)\n", (size_t) meminfo.uordblks);
    fprintf(stderr, "fordblks   %8zu (total free space)\n", (size_t) meminfo.fordblks);
    fprintf(stderr, "Memory in use:   %8zu bytes\n", (size_t) meminfo.usmblks + (size_t) meminfo.uordblks);
    fprintf(stderr, "Total heap size: %8zu bytes\n", (size_t) meminfo.arena);

    // malloc_stats();
  }

  memtotal = (size_t) meminfo.arena;
#endif

  return memtotal;
}

void
memExitOnError(void)
{
  dmemory_ExitOnError = 1;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>

#if !defined(NAMESPACE_H)
#endif

int _ExitOnError = 1;  // If set to 1, exit on error
int _Verbose = 1;      // If set to 1, errors are reported
int _Debug = 0;        // If set to 1, debugging

/* If we're not using GNU C, elide __attribute__ */
#if !defined __GNUC__ && !defined __attribute__
#define __attribute__(x) /*NOTHING*/
#endif

void SysError_(const char *caller, const char *fmt, ...) __attribute__((noreturn));

void
SysError_(const char *caller, const char *fmt, ...)
{
  va_list args;
  int saved_errno = errno;

  va_start(args, fmt);

  printf("\n");
  fprintf(stderr, "%s  error (%s): ", PACKAGE_NAME, caller);
  vfprintf(stderr, fmt, args);
  fprintf(stderr, "\n");

  va_end(args);

  if (saved_errno)
  {
    errno = saved_errno;
    perror("System error message");
  }

  exit(EXIT_FAILURE);
}

void
Error_(const char *caller, const char *fmt, ...)
{
  va_list args;

  va_start(args, fmt);

  printf("\n");
  fprintf(stderr, "%s  error (%s): ", PACKAGE_NAME, caller);
  vfprintf(stderr, fmt, args);
  fprintf(stderr, "\n");

  va_end(args);

  if (_ExitOnError) exit(EXIT_FAILURE);
}

typedef void (*cdiAbortCFunc)(const char *caller, const char *filename, const char *functionname, int line, const char *errorString,
                              va_list ap)
#ifdef __GNUC__
    __attribute__((noreturn))
#endif
    ;

void
cdiAbortC(const char *caller, const char *filename, const char *functionname, int line, const char *errorString, ...)
{
  va_list ap;
  va_start(ap, errorString);
  cdiAbortCFunc cdiAbortC_p = (cdiAbortCFunc) namespaceSwitchGet(NSSWITCH_ABORT).func;
  cdiAbortC_p(caller, filename, functionname, line, errorString, ap);
  va_end(ap);
}

void
cdiAbortC_serial(const char *caller, const char *filename, const char *functionname, int line, const char *errorString, va_list ap)
{
  fprintf(stderr, "%s  error, %s, %s, line %d%s%s\nerrorString: \"", PACKAGE_NAME, functionname, filename, line,
          caller ? ", called from " : "", caller ? caller : "");
  vfprintf(stderr, errorString, ap);
  fputs("\"\n", stderr);
  exit(EXIT_FAILURE);
}

typedef void (*cdiWarningFunc)(const char *caller, const char *fmt, va_list ap);

void
Warning_(const char *caller, const char *fmt, ...)
{
  va_list args;

  va_start(args, fmt);

  if (_Verbose)
  {
    cdiWarningFunc cdiWarning_p = (cdiWarningFunc) namespaceSwitchGet(NSSWITCH_WARNING).func;
    cdiWarning_p(caller, fmt, args);
  }

  va_end(args);
}

void
cdiWarning(const char *caller, const char *fmt, va_list ap)
{
  fprintf(stderr, "%s  warning (%s): ", PACKAGE_NAME, caller);
  vfprintf(stderr, fmt, ap);
  fputc('\n', stderr);
}

void
Message_(const char *caller, const char *fmt, ...)
{
  va_list args;

  va_start(args, fmt);

  fprintf(stdout, "%s  %-18s: ", PACKAGE_NAME, caller);
  vfprintf(stdout, fmt, args);
  fprintf(stdout, "\n");

  va_end(args);
}

bool
cdiObsoleteInfo(const char *oldFunction, const char *newFunction)
{
  fprintf(stdout, "cdi info: Function %s() is deprecated and might be removed in the future versions of CDI.\n", oldFunction);
  fprintf(stdout, "cdi info:    Consider switching to the new function %s() as soon as possible.\n", newFunction);
  return false;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef EXSE_H
#define EXSE_H

enum
{
  EXSE_PREC_FP16 = 2,
  EXSE_PREC_FP32 = 4,
  EXSE_PREC_FP64 = 8,
};

#endif
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>
#include <string.h>
#include <ctype.h>


enum
{
  EXT_HEADER_LEN = 4,
};

union EXT_HEADER
{
  int32_t i32[EXT_HEADER_LEN];
  int64_t i64[EXT_HEADER_LEN];
};

static int initExtLib = 0;
static int extDefaultPrec = 0;
static int extDefaultNumber = EXT_REAL;

// A version string.
#undef LIBVERSION
#define LIBVERSION 2.0.0
#define XSTRING(x) #x
#define STRING(x) XSTRING(x)
static const char ext_libvers[] = STRING(LIBVERSION);

const char *
extLibraryVersion(void)
{
  return ext_libvers;
}

static int EXT_Debug = 0;  // If set to 1, debugging

void
extDebug(int debug)
{
  if (debug) Message("debug level %d", debug);
  EXT_Debug = debug;
}

static void
extLibInit(void)
{
  const char *envName = "EXT_PRECISION";

  char *envString = getenv(envName);
  if (envString)
  {
    if (strlen(envString) == 2)
    {
      switch (tolower((int) envString[0]))
      {
        case 'r':
        {
          extDefaultNumber = EXT_REAL;
          switch ((int) envString[1])
          {
            case '2': extDefaultPrec = EXSE_PREC_FP16; break;
            case '4': extDefaultPrec = EXSE_PREC_FP32; break;
            case '8': extDefaultPrec = EXSE_PREC_FP64; break;
            default: Warning("Invalid digit in %s: %s", envName, envString);
          }
          break;
        }
        case 'c':
        {
          extDefaultNumber = EXT_COMP;
          switch ((int) envString[1])
          {
            case '4': extDefaultPrec = EXSE_PREC_FP32; break;
            case '8': extDefaultPrec = EXSE_PREC_FP64; break;
            default: Warning("Invalid digit in %s: %s", envName, envString);
          }
          break;
        }
        default:
        {
          Warning("Invalid character in %s: %s", envName, envString);
          break;
        }
      }
    }
  }

  initExtLib = 1;
}

static void
extInit(extrec_t *extp)
{
  extp->checked = 0;
  extp->byteswap = 0;
  extp->prec = 0;
  extp->number = extDefaultNumber;
  extp->datasize = 0;
  extp->buffersize = 0;
  extp->buffer = NULL;
}

void *
extNew(void)
{
  if (!initExtLib) extLibInit();

  extrec_t *extp = (extrec_t *) Malloc(sizeof(extrec_t));

  extInit(extp);

  return (void *) extp;
}

void
extDelete(void *ext)
{
  extrec_t *extp = (extrec_t *) ext;

  if (extp)
  {
    if (extp->buffer) Free(extp->buffer);
    Free(extp);
  }
}

int
extCheckFiletype(int fileID, int *swap)
{
  size_t fact = 0;
  size_t data = 0;
  size_t dimxy = 0;
  unsigned char buffer[40], *pbuf;

  if (fileRead(fileID, buffer, 4) != 4) return 0;

  size_t blocklen = (size_t) get_uint32(buffer);
  size_t sblocklen = (size_t) get_swap_uint32(buffer);

  if (EXT_Debug) Message("blocklen = %d sblocklen = %d", blocklen, sblocklen);

  // clang-format off
  if (blocklen == 16)
    {
     *swap = 0;
      fact = blocklen / 4;
      if (fileRead(fileID, buffer, blocklen+8) != blocklen+8) return 0;
      pbuf = buffer+3*fact;      dimxy = (size_t) get_uint32(pbuf);
      pbuf = buffer+blocklen+4;  data  = (size_t) get_uint32(pbuf);
    }
  else if (blocklen == 32)
    {
     *swap = 0;
      fact = blocklen / 4;
      if (fileRead(fileID, buffer, blocklen+8) != blocklen+8) return 0;
      pbuf = buffer+3*fact;      dimxy = (size_t) get_uint64(pbuf);
      pbuf = buffer+blocklen+4;  data  = (size_t) get_uint32(pbuf);
    }
  else if (sblocklen == 16)
    {
     *swap = 1;
      fact = sblocklen / 4;
      if (fileRead(fileID, buffer, sblocklen+8) != sblocklen+8) return 0;
      pbuf = buffer+3*fact;       dimxy = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+sblocklen+4;  data  = (size_t) get_swap_uint32(pbuf);
    }
  else if (sblocklen == 32)
    {
     *swap = 1;
      fact = sblocklen / 4;
      if (fileRead(fileID, buffer, sblocklen+8) != sblocklen+8) return 0;
      pbuf = buffer+3*fact;       dimxy = (size_t) get_swap_uint64(pbuf);
      pbuf = buffer+sblocklen+4;  data  = (size_t) get_swap_uint32(pbuf);
    }
  // clang-format on

  fileRewind(fileID);

  if (EXT_Debug) Message("swap = %d fact = %d", *swap, fact);
  if (EXT_Debug) Message("dimxy = %lu data = %lu", dimxy, data);

  int found = data && (dimxy * fact == data || dimxy * fact * 2 == data || dimxy * fact / 2 == data);
  return found;
}

int
extInqHeader(void *ext, int *header)
{
  extrec_t *extp = (extrec_t *) ext;

  for (int i = 0; i < EXT_HEADER_LEN; i++) header[i] = extp->header[i];

  if (EXT_Debug) Message("datasize = %zu", extp->datasize);

  return 0;
}

int
extDefHeader(void *ext, const int *header)
{
  extrec_t *extp = (extrec_t *) ext;

  for (int i = 0; i < EXT_HEADER_LEN; i++) extp->header[i] = header[i];

  extp->datasize = (size_t) header[3];
  if (extp->number == EXT_COMP) extp->datasize *= 2;

  if (EXT_Debug) Message("datasize = %zu", extp->datasize);

  return 0;
}

static int
extInqData(extrec_t *extp, int prec, void *data)
{
  int ierr = 0;
  int byteswap = extp->byteswap;
  size_t datasize = extp->datasize, buffer_size = datasize * (size_t) prec;
  void *buffer = extp->buffer;
  int rprec = extp->prec;

  switch (rprec)
  {
    case EXSE_PREC_FP32:
    {
      if (byteswap) swap4byte(buffer, datasize);

      if (EXSE_PREC_FP32 == prec)
        memcpy(data, buffer, buffer_size);
      else if (EXSE_PREC_FP64 == prec)
        for (size_t i = 0; i < datasize; ++i) ((double *) data)[i] = (double) ((float *) buffer)[i];
#ifdef HAVE__FLOAT16
      else if (EXSE_PREC_FP16 == prec)
        for (size_t i = 0; i < datasize; ++i) ((_Float16 *) data)[i] = (_Float16) ((float *) buffer)[i];
#endif
      break;
    }
    case EXSE_PREC_FP64:
    {
      if (byteswap) swap8byte(buffer, datasize);

      if (EXSE_PREC_FP64 == prec)
        memcpy(data, buffer, buffer_size);
      else if (EXSE_PREC_FP32 == prec)
        for (size_t i = 0; i < datasize; ++i) ((float *) data)[i] = (float) ((double *) buffer)[i];
#ifdef HAVE__FLOAT16
      else if (EXSE_PREC_FP16 == prec)
        for (size_t i = 0; i < datasize; ++i) ((_Float16 *) data)[i] = (_Float16) ((double *) buffer)[i];
#endif
      break;
    }
#ifdef HAVE__FLOAT16
    case EXSE_PREC_FP16:
    {
      if (EXSE_PREC_FP16 == prec)
        memcpy(data, buffer, buffer_size);
      else if (EXSE_PREC_FP64 == prec)
        for (size_t i = 0; i < datasize; ++i) ((double *) data)[i] = (double) ((_Float16 *) buffer)[i];
      else if (EXSE_PREC_FP32 == prec)
        for (size_t i = 0; i < datasize; ++i) ((float *) data)[i] = (float) ((_Float16 *) buffer)[i];

      break;
    }
#endif
    default:
    {
      Error("unexpected data precision %d", rprec);
      break;
    }
  }

  return ierr;
}

int
extInqDataFP32(void *ext, float *data)
{
  return extInqData((extrec_t *) ext, EXSE_PREC_FP32, (void *) data);
}

int
extInqDataFP64(void *ext, double *data)
{
  return extInqData((extrec_t *) ext, EXSE_PREC_FP64, (void *) data);
}
#ifdef HAVE__FLOAT16
int
extInqDataFP16(void *ext, _Float16 *data)
{
  return extInqData((extrec_t *) ext, EXSE_PREC_FP16, (void *) data);
}
#endif

static int
extDefData(void *ext, int prec, const void *data)
{
  extrec_t *extp = (extrec_t *) ext;

  int rprec = extDefaultPrec ? extDefaultPrec : extp->prec;
  extp->prec = rprec ? rprec : prec;

  int *header = extp->header;

  size_t datasize = (size_t) header[3];
  if (extp->number == EXT_COMP) datasize *= 2;
  size_t blocklen = datasize * (size_t) rprec;

  extp->datasize = datasize;

  if (extp->buffersize != blocklen)
  {
    extp->buffersize = blocklen;
    extp->buffer = Realloc(extp->buffer, extp->buffersize);
  }

  switch (rprec)
  {
    case EXSE_PREC_FP32:
    {
      if (EXSE_PREC_FP32 == prec)
        memcpy(extp->buffer, data, blocklen);
      else if (EXSE_PREC_FP64 == prec)
        for (size_t i = 0; i < datasize; i++) ((float *) extp->buffer)[i] = (float) ((double *) data)[i];
#ifdef HAVE__FLOAT16
      else if (EXSE_PREC_FP16 == prec)
        for (size_t i = 0; i < datasize; i++) ((float *) extp->buffer)[i] = (float) ((_Float16 *) data)[i];
#endif
      break;
    }
    case EXSE_PREC_FP64:
    {
      if (EXSE_PREC_FP64 == prec)
        memcpy(extp->buffer, data, blocklen);
      else if (EXSE_PREC_FP32 == prec)
        for (size_t i = 0; i < datasize; i++) ((double *) extp->buffer)[i] = (double) ((float *) data)[i];
#ifdef HAVE__FLOAT16
      else if (EXSE_PREC_FP16 == prec)
        for (size_t i = 0; i < datasize; i++) ((double *) extp->buffer)[i] = (double) ((_Float16 *) data)[i];
#endif
      break;
    }
#ifdef HAVE__FLOAT16
    case EXSE_PREC_FP16:
    {
      if (EXSE_PREC_FP16 == prec)
        memcpy(extp->buffer, data, blocklen);
      else if (EXSE_PREC_FP32 == prec)
        for (size_t i = 0; i < datasize; i++) ((_Float16 *) extp->buffer)[i] = (_Float16) ((float *) data)[i];
      else if (EXSE_PREC_FP64 == prec)
        for (size_t i = 0; i < datasize; i++) ((_Float16 *) extp->buffer)[i] = (_Float16) ((double *) data)[i];

      break;
    }
#endif
    default:
    {
      Error("unexpected data precision %d", rprec);
      break;
    }
  }

  return 0;
}

int
extDefDataFP32(void *ext, const float *data)
{
  return extDefData(ext, EXSE_PREC_FP32, (void *) data);
}

int
extDefDataFP64(void *ext, const double *data)
{
  return extDefData(ext, EXSE_PREC_FP64, (void *) data);
}
#ifdef HAVE__FLOAT16
int
extDefDataFP16(void *ext, const _Float16 *data)
{
  return extDefData(ext, EXSE_PREC_FP16, (void *) data);
}
#endif

int
extRead(int fileID, void *ext)
{
  extrec_t *extp = (extrec_t *) ext;

  if (!extp->checked)
  {
    int status = extCheckFiletype(fileID, &extp->byteswap);
    if (status == 0) Error("Not a EXTRA file!");
    extp->checked = 1;
  }

  int byteswap = extp->byteswap;

  // read header record
  size_t blocklen = binReadF77Block(fileID, byteswap);

  if (fileEOF(fileID)) return -1;

  if (EXT_Debug) Message("blocklen = %lu", blocklen);

  size_t hprec = blocklen / EXT_HEADER_LEN;
  // extp->prec = (int) hprec;

  union EXT_HEADER tempheader;
  switch (hprec)
  {
    case EXSE_PREC_FP32:
    case EXSE_PREC_FP16:
    {
      binReadInt32(fileID, byteswap, EXT_HEADER_LEN, tempheader.i32);
      for (int i = 0; i < EXT_HEADER_LEN; i++) extp->header[i] = (int) tempheader.i32[i];
      break;
    }
    case EXSE_PREC_FP64:
    {
      binReadInt64(fileID, byteswap, EXT_HEADER_LEN, tempheader.i64);
      for (int i = 0; i < EXT_HEADER_LEN; i++) extp->header[i] = (int) tempheader.i64[i];
      break;
    }
    default:
    {
      Error("Unexpected header precision %d", hprec);
      break;
    }
  }

  size_t blocklen2 = binReadF77Block(fileID, byteswap);

  if (blocklen2 != blocklen)
  {
    Warning("Header blocklen differ (blocklen1=%d; blocklen2=%d)!", blocklen, blocklen2);
    if (blocklen2 != 0) return -1;
  }

  extp->datasize = (size_t) extp->header[3];

  if (EXT_Debug) Message("datasize = %zu", extp->datasize);

  blocklen = binReadF77Block(fileID, byteswap);

  if (extp->buffersize < blocklen)
  {
    extp->buffersize = blocklen;
    extp->buffer = Realloc(extp->buffer, extp->buffersize);
  }

  size_t dprec = blocklen / extp->datasize;
  extp->prec = (int) dprec;

  if (dprec == hprec || dprec == hprec / 2) { extp->number = EXT_REAL; }
  else if (dprec == hprec * 2)
  {
    dprec /= 2;
    extp->datasize *= 2;
    extp->number = EXT_COMP;
  }

  if (dprec != EXSE_PREC_FP32 && dprec != EXSE_PREC_FP64 && dprec != EXSE_PREC_FP16)
  {
    Warning("Unexpected data precision %d", dprec);
    return -1;
  }

  fileRead(fileID, extp->buffer, blocklen);

  blocklen2 = binReadF77Block(fileID, byteswap);

  if (blocklen2 != blocklen)
  {
    Warning("Data blocklen differ (blocklen1=%d; blocklen2=%d)!", blocklen, blocklen2);
    if (blocklen2 != 0) return -1;
  }

  return 0;
}

int
extWrite(int fileID, void *ext)
{
  extrec_t *extp = (extrec_t *) ext;
  union EXT_HEADER tempheader;
  int byteswap = extp->byteswap;
  int rprec = extp->prec;
  int number = extp->number;
  int *header = extp->header;

  // write header record
  size_t blocklen = EXT_HEADER_LEN * (size_t) ((rprec == EXSE_PREC_FP16) ? EXSE_PREC_FP32 : rprec);

  binWriteF77Block(fileID, byteswap, blocklen);

  switch (rprec)
  {
    case EXSE_PREC_FP16:
    case EXSE_PREC_FP32:
    {
      for (int i = 0; i < EXT_HEADER_LEN; i++) tempheader.i32[i] = (int32_t) header[i];
      binWriteInt32(fileID, byteswap, EXT_HEADER_LEN, tempheader.i32);
      break;
    }
    case EXSE_PREC_FP64:
    {
      for (int i = 0; i < EXT_HEADER_LEN; i++) tempheader.i64[i] = (int64_t) header[i];
      binWriteInt64(fileID, byteswap, EXT_HEADER_LEN, tempheader.i64);
      break;
    }
    default:
    {
      Error("unexpected header precision %d", rprec);
      break;
    }
  }

  binWriteF77Block(fileID, byteswap, blocklen);

  extp->datasize = (size_t) header[3];
  if (number == EXT_COMP) extp->datasize *= 2;
  blocklen = extp->datasize * (size_t) rprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  switch (rprec)
  {
#ifdef HAVE__FLOAT16
    case EXSE_PREC_FP16:
    {
      binWriteFlt16(fileID, extp->datasize, (_Float16 *) extp->buffer);
      break;
    }
#endif
    case EXSE_PREC_FP32:
    {
      binWriteFlt32(fileID, byteswap, extp->datasize, (float *) extp->buffer);
      break;
    }
    case EXSE_PREC_FP64:
    {
      binWriteFlt64(fileID, byteswap, extp->datasize, (double *) extp->buffer);
      break;
    }
    default:
    {
      Error("unexpected data precision %d", rprec);
      break;
    }
  }

  binWriteF77Block(fileID, byteswap, blocklen);

  return 0;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
// strdup() from string.h
#ifdef __STDC_ALLOC_LIB__
#define __STDC_WANT_LIB_EXT2__ 1
#else
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif

#ifdef HAVE_CONFIG_H
#endif

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

// On Windows, define ssize_t manually
#ifdef _WIN32
#define ssize_t __int64
#include <io.h>
#else
#include <unistd.h>
#endif

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>  // gettimeofday()
#endif


#ifdef CDI
#endif

#ifndef O_BINARY
#define O_BINARY 0
#endif

#ifdef HAVE_MMAP
#include <sys/mman.h>  // mmap() is defined in this header
#endif

#ifndef SSIZE_MAX
#define SSIZE_MAX LONG_MAX
#endif

enum
{
  MAX_FILES = 8192
};
static int _file_max = MAX_FILES;

static void file_initialize(void);

static bool _file_init = false;

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>

static pthread_once_t _file_init_thread = PTHREAD_ONCE_INIT;
static pthread_mutex_t _file_mutex = PTHREAD_MUTEX_INITIALIZER;

#define FILE_LOCK() pthread_mutex_lock(&_file_mutex)
#define FILE_UNLOCK() pthread_mutex_unlock(&_file_mutex)
#define FILE_INIT() \
  if (_file_init == false) pthread_once(&_file_init_thread, file_initialize)

#else

#define FILE_LOCK()
#define FILE_UNLOCK()
#define FILE_INIT() \
  if (_file_init == false) file_initialize()
#endif

typedef struct
{
  int self;
  int flag;            // access and error flag
  int eof;             // end of file flag
  int fd;              // file descriptor used for read
  FILE *fp;            // FILE pointer used for write
  char *name;          // file name
  off_t size;          // file size
  off_t position;      // file position
  long access;         // file access
  off_t byteTrans;     //
  size_t blockSize;    // file block size
  int mode;            // file access mode
  int type;            // file type [1:open  2:fopen]
  int bufferType;      // buffer type [1:std  2:mmap]
  size_t bufferSize;   // file buffer size
  size_t mappedSize;   // mmap buffer size
  char *buffer;        // file buffer
  long bufferNumFill;  // number of buffer fill
  char *bufferPtr;     // file buffer pointer
  off_t bufferPos;
  off_t bufferStart;
  off_t bufferEnd;
  size_t bufferCnt;
  double time_in_sec;
} bfile_t;

enum FILE_Flags
{
  FILE_EOF = 010,
  FILE_ERROR = 020
};

#ifndef MIN_BUF_SIZE
#define MIN_BUF_SIZE 131072L
#endif

static const char *fbtname[] = { "unknown", "standard", "mmap" };
static const char *ftname[] = { "unknown", "open", "fopen" };

static size_t FileBufferSizeMin = MIN_BUF_SIZE;
static long FileBufferSizeEnv = -1;
static int FileBufferTypeEnv = 0;

static int FileTypeRead = FILE_TYPE_OPEN;
static int FileTypeWrite = FILE_TYPE_FOPEN;
static int FileFlagWrite = 0;

static int FileDebug = 0;  // If set to 1, debugging
static bool FileInfo = false;

static void file_table_print(void);

// A version string.
#undef LIBVERSION
#define LIBVERSION 1.9.1
#define XSTRING(x) #x
#define STRING(x) XSTRING(x)
static const char file_libvers[] = STRING(LIBVERSION);

/*
  21/05/2004  1.3.2 set min I/O Buffersize to 128k
  31/05/2005  1.4.0 replace fileTable by _fileList
  26/08/2005  1.4.1 fileClose with return value
                    checks for all fileptr
  01/09/2005  1.5.0 thread safe version
  06/11/2005  1.5.1 add filePtrEOF, filePtr, filePtrGetc
  03/02/2006  1.5.2 ansi C: define getpagesize and strdupx
  27/12/2007  1.6.0 add FILE_TYPE_FOPEN
  24/03/2008  1.6.1 add O_BINARY if available
                    remove default HAVE_MMAP
                    use HAVE_STRUCT_STAT_ST_BLKSIZE
  22/08/2010  1.7.0 refactor
  11/11/2010  1.7.1 update for changed interface of error.h
  02/02/2012  1.8.0 cleanup
  16/11/2012  1.8.1 added support for unbuffered write
  27/06/2013  1.8.2 added env. var. FILE_TYPE_WRITE (1:open; 2:fopen)
  29/04/2020  1.9.0 fileSetPos(): refactored
  30/04/2020  1.9.1 fileSetPos(): not initialized correctly (bug fix)
 */

typedef struct _filePtrToIdx
{
  int idx;
  bfile_t *ptr;
  struct _filePtrToIdx *next;
} filePtrToIdx;

static filePtrToIdx *_fileList = NULL;
static filePtrToIdx *_fileAvail = NULL;

static void
file_list_new(void)
{
  assert(_fileList == NULL);

  _fileList = (filePtrToIdx *) malloc((size_t) _file_max * sizeof(filePtrToIdx));
}

static void
file_list_delete(void)
{
  if (_fileList)
  {
    free(_fileList);
    _fileList = NULL;
  }
}

static void
file_init_pointer(void)
{
  for (int i = 0; i < _file_max; i++)
  {
    _fileList[i].next = _fileList + i + 1;
    _fileList[i].idx = i;
    _fileList[i].ptr = 0;
  }

  _fileList[_file_max - 1].next = 0;

  _fileAvail = _fileList;
}

static bfile_t *
file_to_pointer(int idx)
{
  bfile_t *fileptr = NULL;

  FILE_INIT();

  if (idx >= 0 && idx < _file_max)
  {
    FILE_LOCK();
    fileptr = _fileList[idx].ptr;
    FILE_UNLOCK();
  }
  else
    Error("file index %d undefined!", idx);

  return fileptr;
}

// Create an index from a pointer
static int
file_from_pointer(bfile_t *ptr)
{
  int idx = -1;

  if (ptr)
  {
    FILE_LOCK();

    if (_fileAvail)
    {
      filePtrToIdx *newptr = _fileAvail;
      _fileAvail = _fileAvail->next;
      newptr->next = 0;
      idx = newptr->idx;
      newptr->ptr = ptr;

      if (FileDebug) Message("Pointer %p has idx %d from file list", ptr, idx);
    }
    else
    {
      Warning("Too many open files (limit is %d)!", _file_max);
      idx = -2;
    }

    FILE_UNLOCK();
  }
  else
    Error("Internal problem (pointer %p undefined)", ptr);

  return idx;
}

static void
file_init_entry(bfile_t *fileptr)
{
  fileptr->self = file_from_pointer(fileptr);

  fileptr->flag = 0;
  fileptr->fd = -1;
  fileptr->fp = NULL;
  fileptr->mode = 0;
  fileptr->size = 0;
  fileptr->name = NULL;
  fileptr->access = 0;
  fileptr->position = 0;
  fileptr->byteTrans = 0;
  fileptr->type = 0;
  fileptr->bufferType = 0;
  fileptr->bufferSize = 0;
  fileptr->mappedSize = 0;
  fileptr->buffer = NULL;
  fileptr->bufferNumFill = 0;
  fileptr->bufferStart = 0;
  fileptr->bufferEnd = -1;
  fileptr->bufferPos = 0;
  fileptr->bufferCnt = 0;
  fileptr->bufferPtr = NULL;
  fileptr->time_in_sec = 0.0;
}

static bfile_t *
file_new_entry(void)
{
  bfile_t *fileptr = (bfile_t *) malloc(sizeof(bfile_t));
  if (fileptr) file_init_entry(fileptr);
  return fileptr;
}

static void
file_delete_entry(bfile_t *fileptr)
{
  int idx = fileptr->self;

  FILE_LOCK();

  free(fileptr);

  _fileList[idx].next = _fileAvail;
  _fileList[idx].ptr = 0;
  _fileAvail = &_fileList[idx];

  FILE_UNLOCK();

  if (FileDebug) Message("Removed idx %d from file list", idx);
}

const char *
fileLibraryVersion(void)
{
  return file_libvers;
}

static int
file_pagesize(void)
{
#ifdef _SC_PAGESIZE
  return (int) sysconf(_SC_PAGESIZE);
#else
#ifndef POSIXIO_DEFAULT_PAGESIZE
#define POSIXIO_DEFAULT_PAGESIZE 4096
#endif
  return (int) POSIXIO_DEFAULT_PAGESIZE;
#endif
}

static double
file_time(void)
{
#ifdef HAVE_SYS_TIME_H
  struct timeval mytime;
  gettimeofday(&mytime, NULL);
  return (double) mytime.tv_sec + (double) mytime.tv_usec * 1.0e-6;
#else
  return 0;
#endif
}

void
fileDebug(int debug)
{
  FileDebug = debug;
  if (FileDebug) Message("Debug level %d", debug);
}

void *
filePtr(int fileID)
{
  return (void *) file_to_pointer(fileID);
}

static void
file_pointer_info(const char *caller, int fileID)
{
  if (FileDebug)
  {
    fprintf(stdout, "%-18s : ", caller);
    fprintf(stdout, "The fileID %d underlying pointer is not valid!", fileID);
    fprintf(stdout, "\n");
  }
}

int
fileSetBufferType(int fileID, int type)
{
  int ret = 0;

  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr)
  {
    switch (type)
    {
      case FILE_BUFTYPE_STD:
      case FILE_BUFTYPE_MMAP: fileptr->bufferType = type; break;
      default: Error("File type %d not implemented!", type);
    }
  }

#ifndef HAVE_MMAP
  if (type == FILE_BUFTYPE_MMAP) ret = 1;
#endif

  return ret;
}

int
fileFlush(int fileID)
{
  int retval = 0;
  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr)
  {
    FILE *fp = fileptr->fp;
    retval = fflush(fp);
    if (retval == 0) retval = fflush(fp);
    if (retval != 0) retval = errno;
  }

  return retval;
}

void
fileClearerr(int fileID)
{
  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr)
  {
    if (fileptr->mode != 'r') clearerr(fileptr->fp);
  }
}

int
filePtrEOF(void *vfileptr)
{
  bfile_t *fileptr = (bfile_t *) vfileptr;
  return fileptr ? (fileptr->flag & FILE_EOF) != 0 : 0;
}

int
fileEOF(int fileID)
{
  bfile_t *fileptr = file_to_pointer(fileID);
  return fileptr ? (fileptr->flag & FILE_EOF) != 0 : 0;
}

void
fileRewind(int fileID)
{
  fileSetPos(fileID, (off_t) 0, SEEK_SET);
  fileClearerr(fileID);
}

off_t
fileGetPos(int fileID)
{
  off_t filepos = 0;

  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr) { filepos = (fileptr->mode == 'r' && fileptr->type == FILE_TYPE_OPEN) ? fileptr->position : ftell(fileptr->fp); }

  if (FileDebug) Message("Position %ld", filepos);

  return filepos;
}

static int
file_set_buffer_pos(bfile_t *fileptr)
{
  off_t position = fileptr->position;
  if (position < fileptr->bufferStart || position > fileptr->bufferEnd)
  {
    fileptr->bufferPos = (fileptr->bufferType == FILE_BUFTYPE_STD) ? position : position - position % file_pagesize();
    fileptr->bufferCnt = 0;
    fileptr->bufferPtr = NULL;

    return 1;
  }

  return 0;
}

static void
file_check_buffer_pos(bfile_t *fileptr)
{
  if (fileptr->bufferPos != fileptr->bufferEnd + 1)
  {
    if (FileDebug) Message("Reset buffer pos from %ld to %ld", fileptr->bufferPos, fileptr->bufferEnd + 1);

    fileptr->bufferPos = fileptr->bufferEnd + 1;
  }
}

static void
file_seek_buffer(bfile_t *fileptr, off_t offset, int whence)
{
  if (whence == SEEK_SET)
  {
    fileptr->position = offset;
    if (!file_set_buffer_pos(fileptr))
    {
      file_check_buffer_pos(fileptr);
      fileptr->bufferCnt = (size_t) (fileptr->bufferEnd - fileptr->position) + 1;
      fileptr->bufferPtr = fileptr->buffer + fileptr->position - fileptr->bufferStart;
    }
  }
  else if (whence == SEEK_CUR)
  {
    fileptr->position += offset;
    if (!file_set_buffer_pos(fileptr))
    {
      file_check_buffer_pos(fileptr);
      fileptr->bufferCnt -= (size_t) offset;
      fileptr->bufferPtr += offset;
    }
  }
}

int
fileSetPos(int fileID, off_t offset, int whence)
{
  int status = 0;

  if (FileDebug) Message("Offset %8ld  Whence %3d", (long) offset, whence);

  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr == 0)
  {
    file_pointer_info(__func__, fileID);
    return 1;
  }

  if (whence != SEEK_SET && whence != SEEK_CUR) Error("Whence = %d not implemented", whence);

  if (fileptr->mode == 'r' && fileptr->type == FILE_TYPE_OPEN)
    file_seek_buffer(fileptr, offset, whence);
  else
    status = fseek(fileptr->fp, offset, whence);

  if ((fileptr->position < fileptr->size) && ((fileptr->flag & FILE_EOF) != 0)) fileptr->flag -= FILE_EOF;

  return status;
}

static void
file_table_print(void)
{
  int lprintHeader = 1;

  for (int fileID = 0; fileID < _file_max; fileID++)
  {
    bfile_t *fileptr = file_to_pointer(fileID);
    if (fileptr)
    {
      if (lprintHeader)
      {
        fprintf(stderr, "\nFile table:\n");
        fprintf(stderr, "+-----+---------+");
        fprintf(stderr, "----------------------------------------------------+\n");
        fprintf(stderr, "|  ID |  Mode   |");
        fprintf(stderr, "  Name                                              |\n");
        fprintf(stderr, "+-----+---------+");
        fprintf(stderr, "----------------------------------------------------+\n");
        lprintHeader = 0;
      }

      fprintf(stderr, "| %3d | ", fileID);

      switch (fileptr->mode)
      {
        case 'r': fprintf(stderr, "read   "); break;
        case 'w': fprintf(stderr, "write  "); break;
        case 'a': fprintf(stderr, "append "); break;
        default: fprintf(stderr, "unknown");
      }

      fprintf(stderr, " | %-51s|\n", fileptr->name);
    }
  }

  if (lprintHeader == 0)
  {
    fprintf(stderr, "+-----+---------+");
    fprintf(stderr, "----------------------------------------------------+\n");
  }
}

char *
fileInqName(int fileID)
{
  bfile_t *fileptr = file_to_pointer(fileID);
  return fileptr ? fileptr->name : NULL;
}

int
fileInqMode(int fileID)
{
  bfile_t *fileptr = file_to_pointer(fileID);
  return fileptr ? fileptr->mode : 0;
}

static long
file_getenv(const char *envName)
{
  long envValue = -1;
  char *envString = getenv(envName);
  if (envString)
  {
    long fact = 1;
    for (int i = 0; i < (int) strlen(envString); i++)
    {
      if (!isdigit((int) envString[i]))
      {
        switch (tolower((int) envString[i]))
        {
          case 'k': fact = 1024; break;
          case 'm': fact = 1048576; break;
          case 'g': fact = 1073741824; break;
          default:
            fact = 0;
            Warning("Invalid number string in %s: %s", envName, envString);
            Warning("%s must comprise only digits [0-9].", envName);
        }
        break;
      }
    }

    if (fact) envValue = fact * atol(envString);

    if (FileDebug) Message("Set %s to %ld", envName, envValue);
  }

  return envValue;
}

static void
file_initialize(void)
{
  long value;

  FileInfo = (file_getenv("FILE_INFO") > 0);

  value = file_getenv("FILE_DEBUG");
  if (value >= 0) FileDebug = (int) value;

  value = file_getenv("FILE_MAX");
  if (value >= 0) _file_max = (int) value;

  if (FileInfo) fprintf(stderr, "FILE_MAX = %d\n", _file_max);

  value = file_getenv("FILE_BUFSIZE");
  if (value >= 0)
    FileBufferSizeEnv = value;
  else
  {
    value = file_getenv("GRIB_API_IO_BUFFER_SIZE");
    if (value >= 0) FileBufferSizeEnv = value;
  }

  if (FileInfo) fprintf(stderr, "FILE_BUFSIZE = %ld\n", FileBufferSizeEnv);

  value = file_getenv("FILE_TYPE_READ");
  if (value > 0)
  {
    switch (value)
    {
      case FILE_TYPE_OPEN:
      case FILE_TYPE_FOPEN: FileTypeRead = (int) value; break;
      default: Warning("File type %ld not implemented!", value);
    }
  }

  if (FileInfo)
    fprintf(stderr, "FILE_TYPE_READ = %d [%d:%s  %d:%s]\n", FileTypeRead, FILE_TYPE_OPEN, ftname[FILE_TYPE_OPEN], FILE_TYPE_FOPEN,
            ftname[FILE_TYPE_FOPEN]);

  value = file_getenv("FILE_TYPE_WRITE");
  if (value > 0)
  {
    switch (value)
    {
      case FILE_TYPE_OPEN:
      case FILE_TYPE_FOPEN: FileTypeWrite = (int) value; break;
      default: Warning("File type %ld not implemented!", value);
    }
  }

  if (FileInfo)
    fprintf(stderr, "FILE_TYPE_WRITE = %d [%d:%s  %d:%s]\n", FileTypeWrite, FILE_TYPE_OPEN, ftname[FILE_TYPE_OPEN], FILE_TYPE_FOPEN,
            ftname[FILE_TYPE_FOPEN]);

#ifdef O_NONBLOCK
  FileFlagWrite = O_NONBLOCK;
#endif
  char *envString = getenv("FILE_FLAG_WRITE");
  if (envString)
  {
#ifdef O_NONBLOCK
    if (strcmp(envString, "NONBLOCK") == 0) FileFlagWrite = O_NONBLOCK;
#endif
  }

  value = file_getenv("FILE_BUFTYPE");
#ifndef HAVE_MMAP
  if (value == FILE_BUFTYPE_MMAP)
  {
    Warning("MMAP not available!");
    value = 0;
  }
#endif
  if (value > 0)
  {
    switch (value)
    {
      case FILE_BUFTYPE_STD:
      case FILE_BUFTYPE_MMAP: FileBufferTypeEnv = (int) value; break;
      default: Warning("File buffer type %ld not implemented!", value);
    }
  }

  if (FileInfo)
    fprintf(stderr, "FILE_BUFTYPE = %d [%d:%s  %d:%s]\n", FileBufferTypeEnv, FILE_BUFTYPE_STD, fbtname[FILE_BUFTYPE_STD],
            FILE_BUFTYPE_MMAP, fbtname[FILE_BUFTYPE_MMAP]);

  file_list_new();
  atexit(file_list_delete);

  FILE_LOCK();
  file_init_pointer();
  FILE_UNLOCK();

  if (FileDebug) atexit(file_table_print);

  _file_init = true;
}

static size_t
file_get_buffersize(bfile_t *fileptr)
{
  size_t buffersize = 0;

  if (FileBufferSizeEnv >= 0)
    buffersize = (size_t) FileBufferSizeEnv;
  else if (fileptr->bufferSize > 0)
    buffersize = fileptr->bufferSize;
  else
  {
    buffersize = fileptr->blockSize * 4;
    if (buffersize < FileBufferSizeMin) buffersize = FileBufferSizeMin;
  }

  return buffersize;
}

static void
file_set_buffer(bfile_t *fileptr)
{
  size_t buffersize = 0;

  if (fileptr->mode == 'r')
  {
    if (FileBufferTypeEnv)
      fileptr->bufferType = FileBufferTypeEnv;
    else if (fileptr->bufferType == 0)
      fileptr->bufferType = FILE_BUFTYPE_STD;

    buffersize = file_get_buffersize(fileptr);

    if ((size_t) fileptr->size < buffersize) buffersize = (size_t) fileptr->size;

    if (fileptr->bufferType == FILE_BUFTYPE_MMAP)
    {
      size_t blocksize = (size_t) file_pagesize();
      size_t minblocksize = 4 * blocksize;
      buffersize = buffersize - buffersize % minblocksize;

      if (buffersize < (size_t) fileptr->size && buffersize < minblocksize) buffersize = minblocksize;
    }

    if (buffersize == 0) buffersize = 1;
  }
  else
  {
    fileptr->bufferType = FILE_BUFTYPE_STD;
    buffersize = file_get_buffersize(fileptr);
  }

  if (fileptr->bufferType == FILE_BUFTYPE_STD || fileptr->type == FILE_TYPE_FOPEN)
  {
    if (buffersize > 0)
    {
      fileptr->buffer = (char *) malloc(buffersize);
      if (fileptr->buffer == NULL) SysError("Allocation of file buffer failed!");
    }
  }

  if (fileptr->type == FILE_TYPE_FOPEN)
    if (setvbuf(fileptr->fp, fileptr->buffer, fileptr->buffer ? _IOFBF : _IONBF, buffersize)) SysError("setvbuf failed!");

  fileptr->bufferSize = buffersize;
}

static int
file_fill_buffer(bfile_t *fileptr)
{
  ssize_t nread;
  long offset = 0;

  if (FileDebug) Message("file ptr = %p  Cnt = %ld", fileptr, fileptr->bufferCnt);

  if ((fileptr->flag & FILE_EOF) != 0) return EOF;

  if (fileptr->buffer == NULL) file_set_buffer(fileptr);

  if (fileptr->bufferSize == 0) return EOF;

  int fd = fileptr->fd;

#ifdef HAVE_MMAP
  if (fileptr->bufferType == FILE_BUFTYPE_MMAP)
  {
    if (fileptr->bufferPos >= fileptr->size) { nread = 0; }
    else
    {
#ifdef CDI
      xassert(fileptr->bufferSize <= SSIZE_MAX);
#endif
      nread = (ssize_t) fileptr->bufferSize;
      if ((nread + fileptr->bufferPos) > fileptr->size) nread = fileptr->size - fileptr->bufferPos;

      if (fileptr->buffer)
      {
        int ret = munmap(fileptr->buffer, fileptr->mappedSize);
        if (ret == -1) SysError("munmap error for read %s", fileptr->name);
        fileptr->buffer = NULL;
      }

      fileptr->mappedSize = (size_t) nread;

      fileptr->buffer = (char *) mmap(NULL, (size_t) nread, PROT_READ, MAP_PRIVATE, fd, fileptr->bufferPos);

      if (fileptr->buffer == MAP_FAILED) SysError("mmap error for read %s", fileptr->name);

      offset = fileptr->position - fileptr->bufferPos;
    }
  }
  else
#endif
  {
    off_t retseek = lseek(fileptr->fd, fileptr->bufferPos, SEEK_SET);
    if (retseek == (off_t) -1) SysError("lseek error at pos %ld file %s", (long) fileptr->bufferPos, fileptr->name);

    nread = read(fd, fileptr->buffer, fileptr->bufferSize);
    if (nread > 0) offset = fileptr->position - fileptr->bufferPos;
  }

  if (nread <= 0)
  {
    fileptr->flag |= (nread == 0) ? FILE_EOF : FILE_ERROR;
    fileptr->bufferCnt = 0;
    return EOF;
  }

  fileptr->bufferPtr = fileptr->buffer;
  fileptr->bufferCnt = (size_t) nread;

  fileptr->bufferStart = fileptr->bufferPos;
  fileptr->bufferPos += nread;
  fileptr->bufferEnd = fileptr->bufferPos - 1;

  if (FileDebug)
  {
    Message("fileID = %d  Val     = %d", fileptr->self, (int) fileptr->buffer[0]);
    Message("fileID = %d  Start   = %ld", fileptr->self, fileptr->bufferStart);
    Message("fileID = %d  End     = %ld", fileptr->self, fileptr->bufferEnd);
    Message("fileID = %d  nread   = %ld", fileptr->self, nread);
    Message("fileID = %d  offset  = %ld", fileptr->self, offset);
    Message("fileID = %d  Pos     = %ld", fileptr->self, fileptr->bufferPos);
    Message("fileID = %d  postion = %ld", fileptr->self, fileptr->position);
  }

  if (offset > 0)
  {
    if (offset > nread) Error("Internal problem with buffer handling. nread = %d offset = %d", nread, offset);

    fileptr->bufferPtr += offset;
    fileptr->bufferCnt -= (size_t) offset;
  }

  fileptr->bufferNumFill++;

  return (unsigned char) *fileptr->bufferPtr;
}

static void
file_copy_from_buffer(bfile_t *fileptr, void *ptr, size_t size)
{
  if (FileDebug) Message("size = %ld  Cnt = %ld", size, fileptr->bufferCnt);

  if (fileptr->bufferCnt < size) Error("Buffer too small. bufferCnt = %d", fileptr->bufferCnt);

  if (size == 1)
  {
    ((char *) ptr)[0] = fileptr->bufferPtr[0];

    fileptr->bufferPtr++;
    fileptr->bufferCnt--;
  }
  else
  {
    memcpy(ptr, fileptr->bufferPtr, size);

    fileptr->bufferPtr += size;
    fileptr->bufferCnt -= size;
  }
}

static size_t
file_read_from_buffer(bfile_t *fileptr, void *ptr, size_t size)
{
  size_t nread;
  size_t offset = 0;

  if (FileDebug) Message("size = %ld  Cnt = %ld", size, (long) fileptr->bufferCnt);

  if (((long) fileptr->bufferCnt) < 0L) Error("Internal problem. bufferCnt = %ld", (long) fileptr->bufferCnt);

  size_t rsize = size;

  while (fileptr->bufferCnt < rsize)
  {
    nread = fileptr->bufferCnt;
    // fprintf(stderr, "rsize = %d nread = %d\n", (int) rsize, (int) nread);
    if (nread > (size_t) 0) file_copy_from_buffer(fileptr, (char *) ptr + offset, nread);
    offset += nread;
    rsize = (nread < rsize) ? rsize - nread : 0;

    if (file_fill_buffer(fileptr) == EOF) break;
  }

  nread = size - offset;

  if (fileptr->bufferCnt < nread) nread = fileptr->bufferCnt;

  if (nread > (unsigned) 0) file_copy_from_buffer(fileptr, (char *) ptr + offset, nread);

  return (nread + offset);
}

void
fileSetBufferSize(int fileID, long buffersize)
{
#ifdef CDI
  xassert(buffersize >= 0);
#endif
  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr) fileptr->bufferSize = (size_t) buffersize;
}

/*
 *   Open a file. Returns file ID, or -1 on error
 */
int
fileOpen(const char *filename, const char *mode)
#ifdef CDI
{
  int (*myFileOpen)(const char *filename, const char *mode)
      = (int (*)(const char *, const char *)) namespaceSwitchGet(NSSWITCH_FILE_OPEN).func;
  return myFileOpen(filename, mode);
}

int
fileOpen_serial(const char *filename, const char *mode)
#endif
{
  FILE *fp = NULL;  // file pointer    (used for write)
  int fd = -1;      // file descriptor (used for read)
  int fileID = FILE_UNDEFID;
  struct stat filestat;
  bfile_t *fileptr = NULL;

  FILE_INIT();

  int fmode = tolower((int) mode[0]);

  switch (fmode)
  {
    case 'r':
      if (FileTypeRead == FILE_TYPE_FOPEN)
        fp = fopen(filename, "rb");
      else
        fd = open(filename, O_RDONLY | O_BINARY);
      break;
    case 'x': fp = fopen(filename, "rb"); break;
    case 'w':
      if (FileTypeWrite == FILE_TYPE_FOPEN)
        fp = fopen(filename, "wb");
      else
        fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY | FileFlagWrite, 0666);
      break;
    case 'a': fp = fopen(filename, "ab"); break;
    default: Error("Mode %c unexpected!", fmode);
  }

  if (FileDebug && fp == NULL && fd == -1) Message("Open failed on %s mode %c errno %d", filename, fmode, errno);

  if (fp)
  {
    if (stat(filename, &filestat) != 0) return fileID;

    fileptr = file_new_entry();
    if (fileptr)
    {
      fileID = fileptr->self;
      fileptr->fp = fp;
    }
  }
  else if (fd >= 0)
  {
    if (fstat(fd, &filestat) != 0) return fileID;

    fileptr = file_new_entry();
    if (fileptr)
    {
      fileID = fileptr->self;
      fileptr->fd = fd;
    }
  }

  if (fileID >= 0)
  {
    fileptr->mode = fmode;
    fileptr->name = strdup(filename);

#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
    fileptr->blockSize = (size_t) filestat.st_blksize;
#else
    fileptr->blockSize = (size_t) 4096;
#endif

    // clang-format off
    if      (fmode == 'r') { fileptr->type = FileTypeRead; }
    else if (fmode == 'w') { fileptr->type = FileTypeWrite; }
    else                   { fileptr->type = FILE_TYPE_FOPEN; }
    // clang-format on

    if (fmode == 'r') { fileptr->size = filestat.st_size; }

    // if (fileptr->type == FILE_TYPE_FOPEN) file_set_buffer(fileptr);
    file_set_buffer(fileptr);

    if (FileDebug) Message("File %s opened with ID %d", filename, fileID);
  }

  return fileID;
}

/*
 *   Close a file.
 */
int
fileClose(int fileID)
#ifdef CDI
{
  int (*myFileClose)(int fileID) = (int (*)(int)) namespaceSwitchGet(NSSWITCH_FILE_CLOSE).func;
  return myFileClose(fileID);
}

int
fileClose_serial(int fileID)
#endif
{
  double rout = 0;

  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr == NULL)
  {
    file_pointer_info(__func__, fileID);
    return 1;
  }

  const char *name = fileptr->name;

  if (FileDebug) Message("fileID = %d  filename = %s", fileID, name);

  if (FileInfo)
  {
    fprintf(stderr, "____________________________________________\n");
    fprintf(stderr, " file ID          : %d\n", fileID);
    fprintf(stderr, " file name        : %s\n", fileptr->name);
    fprintf(stderr, " file type        : %d (%s)\n", fileptr->type, ftname[fileptr->type]);

    if (fileptr->type == FILE_TYPE_FOPEN)
      fprintf(stderr, " file pointer     : %p\n", (void *) fileptr->fp);
    else
    {
      fprintf(stderr, " file descriptor  : %d\n", fileptr->fd);
      fprintf(stderr, " file flag        : %d\n", FileFlagWrite);
    }
    fprintf(stderr, " file mode        : %c\n", fileptr->mode);

    if (sizeof(off_t) > sizeof(long))
    {
#ifdef _WIN32
      fprintf(stderr, " file size        : %I64d\n", (long long) fileptr->size);
      if (fileptr->type == FILE_TYPE_OPEN) fprintf(stderr, " file position    : %I64d\n", (long long) fileptr->position);
      fprintf(stderr, " bytes transfered : %I64d\n", (long long) fileptr->byteTrans);
#else
      fprintf(stderr, " file size        : %lld\n", (long long) fileptr->size);
      if (fileptr->type == FILE_TYPE_OPEN) fprintf(stderr, " file position    : %lld\n", (long long) fileptr->position);
      fprintf(stderr, " bytes transfered : %lld\n", (long long) fileptr->byteTrans);
#endif
    }
    else
    {
      fprintf(stderr, " file size        : %ld\n", (long) fileptr->size);
      if (fileptr->type == FILE_TYPE_OPEN) fprintf(stderr, " file position    : %ld\n", (long) fileptr->position);
      fprintf(stderr, " bytes transfered : %ld\n", (long) fileptr->byteTrans);
    }

    if (fileptr->time_in_sec > 0) rout = (double) fileptr->byteTrans / (1024. * 1024. * fileptr->time_in_sec);

    fprintf(stderr, " wall time [s]    : %.2f\n", fileptr->time_in_sec);
    fprintf(stderr, " data rate [MB/s] : %.1f\n", rout);

    fprintf(stderr, " file access      : %ld\n", fileptr->access);
    if (fileptr->mode == 'r' && fileptr->type == FILE_TYPE_OPEN)
    {
      fprintf(stderr, " buffer type      : %d (%s)\n", fileptr->bufferType, fbtname[fileptr->bufferType]);
      fprintf(stderr, " num buffer fill  : %ld\n", fileptr->bufferNumFill);
    }
    fprintf(stderr, " buffer size      : %lu\n", (unsigned long) fileptr->bufferSize);
    fprintf(stderr, " block size       : %lu\n", (unsigned long) fileptr->blockSize);
    fprintf(stderr, " page size        : %d\n", file_pagesize());
    fprintf(stderr, "--------------------------------------------\n");
  }

  if (fileptr->type == FILE_TYPE_FOPEN)
  {
    if (fclose(fileptr->fp) == EOF) SysError("EOF returned for close of %s!", name);
  }
  else
  {
#ifdef HAVE_MMAP
    if (fileptr->buffer && fileptr->mappedSize)
    {
      if (munmap(fileptr->buffer, fileptr->mappedSize) == -1) SysError("munmap error for close %s", fileptr->name);
      fileptr->buffer = NULL;
    }
#endif
    if (close(fileptr->fd) == -1) SysError("EOF returned for close of %s!", name);
  }

  if (fileptr->name) free((void *) fileptr->name);
  if (fileptr->buffer) free((void *) fileptr->buffer);

  file_delete_entry(fileptr);

  return 0;
}

int
filePtrGetc(void *vfileptr)
{
  int ivalue = EOF;

  bfile_t *fileptr = (bfile_t *) vfileptr;
  if (fileptr)
  {
    if (fileptr->mode == 'r' && fileptr->type == FILE_TYPE_OPEN)
    {
      int fillret = (fileptr->bufferCnt == 0) ? file_fill_buffer(fileptr) : 0;
      if (fillret >= 0)
      {
        ivalue = (unsigned char) *fileptr->bufferPtr++;
        fileptr->bufferCnt--;
        fileptr->position++;

        fileptr->byteTrans++;
        fileptr->access++;
      }
    }
    else
    {
      ivalue = fgetc(fileptr->fp);
      if (ivalue >= 0)
      {
        fileptr->byteTrans++;
        fileptr->access++;
      }
      else
        fileptr->flag |= FILE_EOF;
    }
  }

  return ivalue;
}

int
fileGetc(int fileID)
{
  bfile_t *fileptr = file_to_pointer(fileID);
  return filePtrGetc((void *) fileptr);
}

size_t
filePtrRead(void *vfileptr, void *restrict ptr, size_t size)
{
  size_t nread = 0;

  bfile_t *fileptr = (bfile_t *) vfileptr;
  if (fileptr)
  {
    if (fileptr->mode == 'r' && fileptr->type == FILE_TYPE_OPEN) { nread = file_read_from_buffer(fileptr, ptr, size); }
    else
    {
      nread = fread(ptr, 1, size, fileptr->fp);
      if (nread != size) fileptr->flag |= (nread == 0) ? FILE_EOF : FILE_ERROR;
    }

    fileptr->position += (off_t) nread;
    fileptr->byteTrans += (off_t) nread;
    fileptr->access++;
  }

  if (FileDebug) Message("size %ld  nread %ld", size, nread);

  return nread;
}

size_t
fileRead(int fileID, void *restrict ptr, size_t size)
{
  size_t nread = 0;

  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr)
  {
    double t_begin = 0.0;

    if (FileInfo) t_begin = file_time();

    if (fileptr->type == FILE_TYPE_OPEN) { nread = file_read_from_buffer(fileptr, ptr, size); }
    else
    {
      nread = fread(ptr, 1, size, fileptr->fp);
      if (nread != size) fileptr->flag |= (nread == 0) ? FILE_EOF : FILE_ERROR;
    }

    if (FileInfo) fileptr->time_in_sec += file_time() - t_begin;

    fileptr->position += (off_t) nread;
    fileptr->byteTrans += (off_t) nread;
    fileptr->access++;
  }

  if (FileDebug) Message("size %ld  nread %ld", size, nread);

  return nread;
}

size_t
fileWrite(int fileID, const void *restrict ptr, size_t size)
{
  size_t nwrite = 0;

  bfile_t *fileptr = file_to_pointer(fileID);
  if (fileptr)
  {
    double t_begin = 0.0;

    if (FileInfo) t_begin = file_time();

    if (fileptr->type == FILE_TYPE_FOPEN) { nwrite = fwrite(ptr, 1, size, fileptr->fp); }
    else
    {
      ssize_t temp = write(fileptr->fd, ptr, size);
      if (temp == -1) perror("error writing to file");
      nwrite = (temp == -1) ? 0 : (size_t) temp;
    }

    if (FileInfo) fileptr->time_in_sec += file_time() - t_begin;

    fileptr->position += (off_t) nwrite;
    fileptr->byteTrans += (off_t) nwrite;
    fileptr->access++;
  }

  return nwrite;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <float.h>

// Required on windows to be able to use M_PI
#define _USE_MATH_DEFINES
#include <math.h>

#ifndef M_SQRT2
#define M_SQRT2 1.41421356237309504880168872420969808
#endif

static void
cpledn(size_t kn, size_t kodd, const double *pfn, double pdx, int kflag, double *pw, double *pdxn, double *pxmod)
{
  // 1.0 Newton iteration step

  double zdlx = pdx;
  double zdlldn = 0.0;

  size_t ik = 1;

  if (kflag == 0)
  {
    double zdlk = 0.5 * pfn[0];
    for (size_t jn = 2 - kodd; jn <= kn; jn += 2)
    {
      // normalised ordinary Legendre polynomial == \overbar{p_n}^0
      zdlk = zdlk + pfn[ik] * cos((double) (jn) *zdlx);
      // normalised derivative == d/d\theta(\overbar{p_n}^0)
      zdlldn = zdlldn - pfn[ik] * (double) (jn) *sin((double) (jn) *zdlx);
      ik++;
    }
    // Newton method
    double zdlmod = -(zdlk / zdlldn);
    double zdlxn = zdlx + zdlmod;
    *pdxn = zdlxn;
    *pxmod = zdlmod;
  }

  // 2.0 Compute weights

  if (kflag == 1)
  {
    for (size_t jn = 2 - kodd; jn <= kn; jn += 2)
    {
      // normalised derivative
      zdlldn = zdlldn - pfn[ik] * (double) (jn) *sin((double) (jn) *zdlx);
      ik++;
    }
    *pw = (double) (2 * kn + 1) / (zdlldn * zdlldn);
  }
}

static void
gawl(double *pfn, double *pl, double *pw, size_t kn)
{
  double pmod = 0.0;
  double zw = 0.0;
  double zdlxn = 0.0;

  // 1.0 Initizialization

  int iflag = 0;
  int itemax = 20;

  size_t iodd = (kn % 2);

  double zdlx = *pl;

  // 2.0 Newton iteration

  for (int jter = 1; jter <= itemax + 1; ++jter)
  {
    cpledn(kn, iodd, pfn, zdlx, iflag, &zw, &zdlxn, &pmod);
    zdlx = zdlxn;
    if (iflag == 1) break;
    if (fabs(pmod) <= DBL_EPSILON * 1000.0) iflag = 1;
  }

  *pl = zdlxn;
  *pw = zw;
}

static void
gauaw(size_t kn, double *restrict pl, double *restrict pw)
{
  /*
   * 1.0 Initialize Fourier coefficients for ordinary Legendre polynomials
   *
   * Belousov, Swarztrauber, and ECHAM use zfn(0,0) = sqrt(2)
   * IFS normalisation chosen to be 0.5*Integral(Pnm**2) = 1 (zfn(0,0) = 2.0)
   */
  double *zfn = (double *) malloc((kn + 1) * (kn + 1) * sizeof(double));
  double *zfnlat = (double *) malloc((kn / 2 + 1 + 1) * sizeof(double));

  zfn[0] = M_SQRT2;
  for (size_t jn = 1; jn <= kn; ++jn)
  {
    double zfnn = zfn[0];
    for (size_t jgl = 1; jgl <= jn; ++jgl) { zfnn *= sqrt(1.0 - 0.25 / ((double) (jgl * jgl))); }

    zfn[jn * (kn + 1) + jn] = zfnn;

    size_t iodd = jn % 2;
    for (size_t jgl = 2; jgl <= jn - iodd; jgl += 2)
    {
      zfn[jn * (kn + 1) + jn - jgl]
          = zfn[jn * (kn + 1) + jn - jgl + 2] * ((double) ((jgl - 1) * (2 * jn - jgl + 2))) / ((double) (jgl * (2 * jn - jgl + 1)));
    }
  }

  // 2.0 Gaussian latitudes and weights

  size_t iodd = kn % 2;
  size_t ik = iodd;
  for (size_t jgl = iodd; jgl <= kn; jgl += 2)
  {
    zfnlat[ik] = zfn[kn * (kn + 1) + jgl];
    ik++;
  }

  // 2.1 Find first approximation of the roots of the Legendre polynomial of degree kn

  size_t ins2 = kn / 2 + (kn % 2);

  for (size_t jgl = 1; jgl <= ins2; ++jgl)
  {
    double z = ((double) (4 * jgl - 1)) * M_PI / ((double) (4 * kn + 2));
    pl[jgl - 1] = z + 1.0 / (tan(z) * ((double) (8 * kn * kn)));
  }

  // 2.2 Computes roots and weights for transformed theta

  for (size_t jgl = ins2; jgl >= 1; --jgl)
  {
    size_t jglm1 = jgl - 1;
    gawl(zfnlat, &(pl[jglm1]), &(pw[jglm1]), kn);
  }

  // convert to physical latitude

  for (size_t jgl = 0; jgl < ins2; ++jgl) pl[jgl] = cos(pl[jgl]);

  for (size_t jgl = 1; jgl <= kn / 2; ++jgl)
  {
    size_t jglm1 = jgl - 1;
    size_t isym = kn - jgl;
    pl[isym] = -pl[jglm1];
    pw[isym] = pw[jglm1];
  }

  free(zfnlat);
  free(zfn);
}

void
gaussianLatitudes(size_t nlats, double *latitudes, double *weights)
{
  gauaw(nlats, latitudes, weights);
}

bool
isGaussianLatitudes(size_t nlats, const double *latitudes)
{
  bool is_gauss_lats = false;

  if (nlats > 2)  // check if gaussian
  {
    size_t i;
    double *yv = (double *) malloc(nlats * sizeof(double));
    double *yw = (double *) malloc(nlats * sizeof(double));
    gaussianLatitudes(nlats, yv, yw);
    free(yw);

    for (i = 0; i < nlats; i++) yv[i] = asin(yv[i]) / M_PI * 180.0;

    for (i = 0; i < nlats; i++)
      if (fabs(yv[i] - latitudes[i]) > ((yv[0] - yv[1]) / 500.0)) break;

    if (i == nlats) is_gauss_lats = true;

    // check S->N
    if (is_gauss_lats == false)
    {
      for (i = 0; i < nlats; i++)
        if (fabs(yv[i] - latitudes[nlats - i - 1]) > ((yv[0] - yv[1]) / 500.0)) break;

      if (i == nlats) is_gauss_lats = true;
    }

    free(yv);
  }

  return is_gauss_lats;
}
#ifndef GET_NUM_MISSVALS_H
#define GET_NUM_MISSVALS_H

#include <stddef.h>

size_t get_num_missvalsSP(size_t size, float *data, float missval);
size_t get_num_missvalsDP(size_t size, double *data, double missval);
size_t get_cplx_num_missvalsSP(size_t size, float *data, float missval);
size_t get_cplx_num_missvalsDP(size_t size, double *data, double missval);

#endif
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */


size_t
get_num_missvalsSP(size_t size, float *data, float missval)
{
  size_t numMissVals = 0;

  if (DBL_IS_NAN(missval))
  {
    for (size_t i = 0; i < size; i++)
      if (DBL_IS_EQUAL(data[i], missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }
  else
  {
    for (size_t i = 0; i < size; i++)
      if (IS_EQUAL(data[i], missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }

  return numMissVals;
}

size_t
get_num_missvalsDP(size_t size, double *data, double missval)
{
  size_t numMissVals = 0;

  if (DBL_IS_NAN(missval))
  {
    for (size_t i = 0; i < size; i++)
      if (DBL_IS_EQUAL(data[i], missval) || DBL_IS_EQUAL(data[i], (float) missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }
  else
  {
    for (size_t i = 0; i < size; i++)
      if (IS_EQUAL(data[i], missval) || IS_EQUAL(data[i], (float) missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }

  return numMissVals;
}

size_t
get_cplx_num_missvalsSP(size_t size, float *data, float missval)
{
  size_t numMissVals = 0;

  if (DBL_IS_NAN(missval))
  {
    for (size_t i = 0; i < 2 * size; i += 2)
      if (DBL_IS_EQUAL(data[i], missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }
  else
  {
    for (size_t i = 0; i < 2 * size; i += 2)
      if (IS_EQUAL(data[i], missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }

  return numMissVals;
}

size_t
get_cplx_num_missvalsDP(size_t size, double *data, double missval)
{
  size_t numMissVals = 0;

  if (DBL_IS_NAN(missval))
  {
    for (size_t i = 0; i < 2 * size; i += 2)
      if (DBL_IS_EQUAL(data[i], missval) || DBL_IS_EQUAL(data[i], (float) missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }
  else
  {
    for (size_t i = 0; i < 2 * size; i += 2)
      if (IS_EQUAL(data[i], missval) || IS_EQUAL(data[i], (float) missval))
      {
        data[i] = missval;
        numMissVals++;
      }
  }

  return numMissVals;
}
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBGRIB_API
#include <grib_api.h>
#endif

#include <stdio.h>


static char gribapi_libvers[64] = "";
#ifdef HAVE_LIBGRIB_API
static bool gribapi_libvers_init;
#endif

void
gribapiLibraryVersion(int *major_version, int *minor_version, int *revision_version)
{
#ifdef HAVE_LIBGRIB_API
  long version = grib_get_api_version();
  (*major_version) = (int) (version / 10000);
  (*minor_version) = (int) ((version - (*major_version) * 10000) / 100);
  (*revision_version) = (int) (version - (*major_version) * 10000 - (*minor_version) * 100);
#else
  (*major_version) = 0;
  (*minor_version) = 0;
  (*revision_version) = 0;
#endif
}

const char *
gribapiLibraryVersionString(void)
{
#ifdef HAVE_LIBGRIB_API
  if (!gribapi_libvers_init)
  {
    int major_version, minor_version, revision_version;
    gribapiLibraryVersion(&major_version, &minor_version, &revision_version);

    snprintf(gribapi_libvers, sizeof(gribapi_libvers), "%d.%d.%d", major_version, minor_version, revision_version);
    gribapi_libvers_init = true;
  }
#endif

  return gribapi_libvers;
}

void *
gribHandleNew(int editionNumber)
{
#ifdef HAVE_LIBGRIB_API
  grib_handle *gh = NULL;
  const char *fname = (editionNumber == 1) ? CDI_GRIB1_Template : CDI_GRIB2_Template;
  if (fname)
  {
    FILE *fp = fopen(fname, "r");
    if (fp)
    {
      int error;
      gh = grib_handle_new_from_file(NULL, fp, &error);
      fclose(fp);
      if (gh == NULL) Error("grib_handle_new_from_file failed!");
    }
    else { Error("Open failed on >%s<!", fname); }
  }

  if (gh == NULL)
  {
    gh = grib_handle_new_from_samples(NULL, (editionNumber == 1) ? "GRIB1" : "GRIB2");
    if (gh == NULL) Error("grib_handle_new_from_samples failed!");

    if (editionNumber == 1) GRIB_CHECK(my_grib_set_long(gh, "deleteLocalDefinition", 1L), 0);
    if (editionNumber == 2) GRIB_CHECK(my_grib_set_long(gh, "grib2LocalSectionPresent", 0L), 0);
    if (editionNumber == 2) GRIB_CHECK(my_grib_set_long(gh, "numberOfValues", 0L), 0);
  }

  return gh;
#else
  (void) editionNumber;
  return NULL;
#endif
}

void
gribHandleDelete(void *gh)
{
#ifdef HAVE_LIBGRIB_API
  grib_handle_delete((struct grib_handle *) gh);
#else
  (void) gh;
#endif
}

void
gribContainersNew(stream_t *streamptr)
{
  const int editionNumber = (streamptr->filetype == CDI_FILETYPE_GRB) ? 1 : 2;

#ifdef HAVE_LIBCGRIBEX
  if (editionNumber == 1 && !CDI_gribapi_grib1) {}
  else
#endif
  {
    const int nvars = streamptr->nvars;

#ifdef GRIBCONTAINER2D
    gribContainer_t **gribContainers;
    gribContainers = (gribContainer_t **) Malloc(nvars * sizeof(gribContainer_t *));

    for (int varID = 0; varID < nvars; ++varID)
    {
      const int nlevs = streamptr->vars[varID].nlevs;
      gribContainers[varID] = (gribContainer_t *) Malloc(nlevs * sizeof(gribContainer_t));

      for (int levelID = 0; levelID < nlevs; ++levelID)
      {
        gribContainers[varID][levelID].gribHandle = gribHandleNew(editionNumber);
        gribContainers[varID][levelID].init = false;
      }
    }

    streamptr->gribContainers = (void *) gribContainers;
#else
    gribContainer_t *gribContainers = (gribContainer_t *) Malloc((size_t) nvars * sizeof(gribContainer_t));

    for (int varID = 0; varID < nvars; ++varID)
    {
      gribContainers[varID].gribHandle = gribHandleNew(editionNumber);
      gribContainers[varID].init = false;
    }

    streamptr->gribContainers = (void *) gribContainers;
#endif
  }
}

void
gribContainersDelete(stream_t *streamptr)
{
  if (streamptr->gribContainers)
  {
    const int nvars = streamptr->nvars;

#ifdef GRIBCONTAINER2D
    gribContainer_t **gribContainers = (gribContainer_t **) streamptr->gribContainers;

    for (int varID = 0; varID < nvars; ++varID)
    {
      const int nlevs = streamptr->vars[varID].nlevs;
      for (int levelID = 0; levelID < nlevs; ++levelID) { gribHandleDelete(gribContainers[varID][levelID].gribHandle); }
      Free(gribContainers[varID]);
    }
#else
    gribContainer_t *gribContainers = (gribContainer_t *) streamptr->gribContainers;

    for (int varID = 0; varID < nvars; ++varID) { gribHandleDelete(gribContainers[varID].gribHandle); }
#endif

    Free(gribContainers);

    streamptr->gribContainers = NULL;
  }
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef INCLUDE_GUARD_CDI_GRIBAPI_UTILITIES_H
#define INCLUDE_GUARD_CDI_GRIBAPI_UTILITIES_H

#ifdef HAVE_LIBGRIB_API


#include <grib_api.h>

#include <stdbool.h>

char *gribCopyString(grib_handle *gribHandle, const char *key);
bool gribCheckString(grib_handle *gribHandle, const char *key, const char *expectedValue);

bool gribCheckLong(grib_handle *gribHandle, const char *key, long expectedValue);
long gribGetLong(grib_handle *gh, const char *key);
long gribGetLongDefault(grib_handle *gribHandle, const char *key, long defaultValue);

double gribGetDouble(grib_handle *gh, const char *key);
double gribGetDoubleDefault(grib_handle *gribHandle, const char *key, double defaultValue);

size_t gribGetArraySize(grib_handle *gribHandle, const char *key);
void gribGetDoubleArray(grib_handle *gribHandle, const char *key,
                        double *array);  // The caller is responsible to ensure a sufficiently large buffer.
void gribGetLongArray(grib_handle *gribHandle, const char *key,
                      long *array);  // The caller is responsible to ensure a sufficiently large buffer.

long gribEditionNumber(grib_handle *gh);
char *gribMakeTimeString(grib_handle *gh, CdiTimeType timeType);  // Returns NULL if timeType is kCdiTimeType_endTime and the field
                                                                  // does not have an integration period (statistical data).
int gribapiTimeIsFC(grib_handle *gh);
int gribapiGetTsteptype(grib_handle *gh);
int gribGetDatatype(grib_handle *gribHandle);
int gribapiGetParam(grib_handle *gh);
int gribapiGetGridType(grib_handle *gh);
bool gribapiGetGrid(grib_handle *gh, grid_t *grid);
size_t gribapiGetGridsize(grib_handle *gh);

#ifdef HIRLAM_EXTENSIONS
void gribapiSetDataTimeRangeIndicator(grib_handle *gh, int timeRangeIndicator);
void gribapiGetDataTimeRangeIndicator(grib_handle *gh, int *timeRangeIndicator);
#endif  // #ifdef HIRLAM_EXTENSIONS

extern struct cdiGribAPI_ts_str_map_elem
{
  long productionTemplate;
  const char sname[8];
} cdiGribAPI_ts_str_map[];

#endif

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBGRIB_API



#include <assert.h>
#include <time.h>

#define FAIL_ON_GRIB_ERROR(function, gribHandle, key, ...)                                                                       \
  do {                                                                                                                           \
    int errorCode = (int) function(gribHandle, key, __VA_ARGS__);                                                                \
    if (errorCode)                                                                                                               \
    {                                                                                                                            \
      fprintf(stderr, "%s:%d: Error in function `%s`: `%s` returned error code %d for key \"%s\"", __FILE__, __LINE__, __func__, \
              #function, errorCode, key);                                                                                        \
      exit(errorCode);                                                                                                           \
    }                                                                                                                            \
  } while (0)

// A simple wrapper for grib_get_string() that returns a newly allocated string.
char *
gribCopyString(grib_handle *gribHandle, const char *key)
{
  size_t length;
#ifdef HAVE_GRIB_GET_LENGTH
  if (!grib_get_length(gribHandle, key, &length))
  {
    char *result = (char *) Malloc(length);
    if (!grib_get_string(gribHandle, key, result, &length))
      result = (char *) Realloc(result, length);
    else
    {
      Free(result);
      result = NULL;
    }
    return result;
  }
  else
    return NULL;
#else
  length = 1024; /* there's an implementation limit
                  * that makes strings longer than
                  * this unlikely in grib_api versions
                  * not providing grib_get_length */
  int rc;
  char *result = (char *) Malloc(length);
  while ((rc = grib_get_string(gribHandle, key, result, &length)) == GRIB_BUFFER_TOO_SMALL || rc == GRIB_ARRAY_TOO_SMALL)
  {
    if (length <= 1024UL * 1024UL)
    {
      length *= 2;
      result = Realloc(result, length);
    }
    else
      break;
  }
  if (!rc)
    result = Realloc(result, length);
  else
  {
    Free(result);
    result = NULL;
  }
  return result;
#endif
}

// A simple wrapper for grib_get_string() for the usecase that the result is only compared to a given constant string.
// Returns true if the key exists and the value is equal to the given string.
bool
gribCheckString(grib_handle *gribHandle, const char *key, const char *expectedValue)
{
  size_t expectedLength = strlen(expectedValue) + 1;
#ifdef HAVE_GRIB_GET_LENGTH
  size_t length;
  if (grib_get_length(gribHandle, key, &length)) return false;
  if (length != expectedLength) return false;
  char *value = (char *) Malloc(length);
  if (grib_get_string(gribHandle, key, value, &length)) return false;
  int rc = str_is_equal(value, expectedValue);
  Free(value);
#else
  char *value = gribCopyString(gribHandle, key);
  int rc = value ? (strlen(value) + 1 == expectedLength ? str_is_equal(value, expectedValue) : false) : false;
  Free(value);
#endif
  return rc;
}

// A simple wrapper for grib_get_long() for the usecase that the result is only compared to a given constant value.
// Returns true if the key exists and the value is equal to the given one.
bool
gribCheckLong(grib_handle *gribHandle, const char *key, long expectedValue)
{
  long value;
  if (grib_get_long(gribHandle, key, &value)) return false;
  return value == expectedValue;
}

// A simple wrapper for grib_get_long() for the usecase that failure to fetch the value is fatal.
long
gribGetLong(grib_handle *gh, const char *key)
{
  long result;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, key, &result);
  return result;
}

// A simple wrapper for grib_get_long() for the usecase that a default value is used in the case that the operation fails.
long
gribGetLongDefault(grib_handle *gribHandle, const char *key, long defaultValue)
{
  long result;
  if (grib_get_long(gribHandle, key, &result) || result == GRIB_MISSING_LONG) result = defaultValue;
  return result;
}

// A simple wrapper for grib_get_double() for the usecase that failure to fetch the value is fatal.
double
gribGetDouble(grib_handle *gh, const char *key)
{
  double result;
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, key, &result);
  return result;
}

// A sample wrapper for grib_get_double() for the usecase that a default value is used in the case that the operation fails.
double
gribGetDoubleDefault(grib_handle *gribHandle, const char *key, double defaultValue)
{
  double result;
  if (grib_get_double(gribHandle, key, &result) || IS_EQUAL(result, GRIB_MISSING_DOUBLE)) result = defaultValue;
  return result;
}

// A simple wrapper for grib_get_size() for the usecase that failure to fetch the value is fatal.
size_t
gribGetArraySize(grib_handle *gribHandle, const char *key)
{
  size_t result;
  FAIL_ON_GRIB_ERROR(grib_get_size, gribHandle, key, &result);
  return result;
}

// A simple wrapper for grib_get_double_array() for the usecase that failure to fetch the data is fatal.
void
gribGetDoubleArray(grib_handle *gribHandle, const char *key, double *array)
{
  size_t valueCount = gribGetArraySize(gribHandle, key);
  FAIL_ON_GRIB_ERROR(grib_get_double_array, gribHandle, key, array, &valueCount);
}

// A simple wrapper for grib_get_long_array() for the usecase that failure to fetch the data is fatal.
void
gribGetLongArray(grib_handle *gribHandle, const char *key, long *array)
{
  size_t valueCount = gribGetArraySize(gribHandle, key);
  FAIL_ON_GRIB_ERROR(grib_get_long_array, gribHandle, key, array, &valueCount);
}

// We need the edition number so frequently, that it's convenient to give it its own function.
long
gribEditionNumber(grib_handle *gh)
{
  return gribGetLong(gh, "editionNumber");
}

// This return value of this should be passed to a call to resetTz(), it is a malloc'ed string with the content of the TZ
// environment variable before the call (or NULL if that was not set).
static char *
setUtc(void)
{
  char *temp = getenv("TZ"), *result = NULL;
  if (temp) result = strdup(temp);
  setenv("TZ", "UTC", 1);
  return result;
}

// Undoes the effect of setUtc(), pass to it the return value of the corresponding setUtc() call, it will free the string.
static void
resetTz(char *savedTz)
{
  if (savedTz)
  {
    setenv("TZ", savedTz, 1);
    Free(savedTz);
  }
  else { unsetenv("TZ"); }
}

// This function uses the system functions to normalize the date representation according to the gregorian calendar.
// Returns zero on success.
static int
normalizeDays(struct tm *me)
{
  char *savedTz = setUtc();  // Ensure that mktime() does not interprete the date according to our local time zone.

  int result = (mktime(me) == (time_t) -1);  // This does all the heavy lifting.

  resetTz(savedTz);
  return result;
}

// Returns zero on success.
static int
addSecondsToDate(struct tm *me, long long amount)
{
  // It is irrelevant here whether days are zero or one based, the correction would have be undone again so that it is effectless.
  long long seconds = ((me->tm_mday * 24ll + me->tm_hour) * 60 + me->tm_min) * 60
                      + me->tm_sec;  // The portion of the date that uses fixed increments.
  seconds += amount;
  me->tm_mday = (int) (seconds / 24 / 60 / 60);
  seconds -= (long long) me->tm_mday * 24 * 60 * 60;
  me->tm_hour = (int) (seconds / 60 / 60);
  seconds -= (long long) me->tm_hour * 60 * 60;
  me->tm_min = (int) (seconds / 60);
  seconds -= (long long) (me->tm_min * 60);
  me->tm_sec = (int) seconds;
  return normalizeDays(me);
}

static void
addMonthsToDate(struct tm *me, long long amount)
{
  long long months = me->tm_year * 12ll + me->tm_mon;
  months += amount;
  me->tm_year = (int) (months / 12);
  months -= (long long) me->tm_year * 12;
  me->tm_mon = (int) months;
}

// unit is a value according to code table 4.4 of the GRIB2 specification, returns non-zero on error
static int
addToDate(struct tm *me, long long amount, long unit)
{
  switch (unit)
  {
    case 0: return addSecondsToDate(me, 60 * amount);            // minute
    case 1: return addSecondsToDate(me, 60 * 60 * amount);       // hour
    case 2: return addSecondsToDate(me, 24 * 60 * 60 * amount);  // day

    case 3: addMonthsToDate(me, amount); return 0;             // month
    case 4: addMonthsToDate(me, 12 * amount); return 0;        // year
    case 5: addMonthsToDate(me, 10 * 12 * amount); return 0;   // decade
    case 6: addMonthsToDate(me, 30 * 12 * amount); return 0;   // normal
    case 7: addMonthsToDate(me, 100 * 12 * amount); return 0;  // century

    case 10: return addSecondsToDate(me, 3 * 60 * 60 * amount);   // eighth of a day
    case 11: return addSecondsToDate(me, 6 * 60 * 60 * amount);   // quarter day
    case 12: return addSecondsToDate(me, 12 * 60 * 60 * amount);  // half day
    case 13: return addSecondsToDate(me, amount);                 // second

    default: return 1;  // reserved, unknown, or missing
  }
}

static char *
makeDateString(struct tm *me)
{
  enum
  {
    size = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 4 + 1
  };
  char *result = (char *) Malloc(size);
  assert(me->tm_year + 1900 < 10000 && me->tm_year + 1900 > -1000 && me->tm_mon >= 0 && me->tm_mon < 12 && me->tm_mday >= 1
         && me->tm_mday <= 31 && me->tm_hour >= 0 && me->tm_hour <= 24 && me->tm_min >= 0 && me->tm_min < 60 && me->tm_sec >= 0
         && me->tm_sec <= 60);
  int year = me->tm_year + 1900;
  if (year > 10000)
    year = 9999;
  else if (year < -999)
    year = -999;
  snprintf(result, size, "%04d-%02d-%02dT%02d:%02d:%02d.000", year, (me->tm_mon + 1) & 31, me->tm_mday & 31, me->tm_hour & 31,
           me->tm_min & 63, me->tm_sec & 63);
  return result;
}

// FIXME: This ignores any calendar definition that might be present.
// XXX: Identification templates are not implemented in grib_api-1.12.3, so even if I implemented the other calendars now, it
// wouldn't be possible to use them.
static int
getAvailabilityOfRelativeTimes(grib_handle *gh, bool *outHaveForecastTime, bool *outHaveTimeRange)
{
  switch (gribGetLong(gh, "productDefinitionTemplateNumber"))
  {
    case 20:
    case 30:
    case 31:
    case 254:
    case 311:
    case 2000: *outHaveForecastTime = false, *outHaveTimeRange = false; return 0;

    // case 55 and case 40455 are the same: 55 is the proposed standard value, 40455 is the value in the local use range that is
    // used by the dwd until the standard is updated.
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 15:
    case 32:
    case 33:
    case 40:
    case 41:
    case 44:
    case 45:
    case 48:
    case 51:
    case 53:
    case 54:
    case 55:
    case 56:
    case 57:
    case 58:
    case 60:
    case 1000:
    case 1002:
    case 1100:
    case 40033:
    case 40455:
    case 40456: *outHaveForecastTime = true, *outHaveTimeRange = false; return 0;

    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
    case 13:
    case 14:
    case 34:
    case 42:
    case 43:
    case 46:
    case 47:
    case 61:
    case 67:
    case 68:
    case 91:
    case 1001:
    case 1101:
    case 40034: *outHaveForecastTime = true, *outHaveTimeRange = true; return 0;

    default: return 1;
  }
}

char *
gribMakeTimeString(grib_handle *gh, CdiTimeType timeType)
{
  // Get the parts of the reference date.
  struct tm date;
  date.tm_mon = (int) gribGetLong(gh, "month") - 1;  // months are zero based in struct tm and one based in GRIB
  date.tm_mday = (int) gribGetLong(gh, "day");
  date.tm_hour = (int) gribGetLong(gh, "hour");
  date.tm_min = (int) gribGetLong(gh, "minute");
  date.tm_isdst = 0;

  if (gribEditionNumber(gh) == 1)
  {
    date.tm_year = (int) gribGetLong(gh, "yearOfCentury");  // years are -1900 based both in struct tm and GRIB1
  }
  else
  {
    date.tm_year = (int) gribGetLong(gh, "year") - 1900;  // years are -1900 based in struct tm and zero based in GRIB2
    date.tm_sec = (int) gribGetLong(gh, "second");

    // If the start or end time are requested, we need to take the relative times into account.
    if (timeType != kCdiTimeType_referenceTime)
    {
      // Determine whether we have a forecast time and a time range.
      bool haveForecastTime, haveTimeRange;
      if (getAvailabilityOfRelativeTimes(gh, &haveForecastTime, &haveTimeRange)) return NULL;
      if (timeType == kCdiTimeType_endTime && !haveTimeRange)
        return NULL;  // tell the caller that the requested time does not exist

      // If we have relative times, apply the relative times to the date
      if (haveForecastTime)
      {
        long offset = gribGetLongDefault(gh, "forecastTime", 0);
        // if (stepUnits == indicatorOfUnitOfTimeRange) assert(startStep == forecastTime)
        long offsetUnit = gribGetLongDefault(gh, "indicatorOfUnitOfTimeRange", 255);
        if (addToDate(&date, offset, offsetUnit)) return NULL;
        if (timeType == kCdiTimeType_endTime)
        {
          assert(haveTimeRange);
          long range = gribGetLongDefault(gh, "lengthOfTimeRange", 0);
          // if (stepUnits == indicatorOfUnitForTimeRange) assert(endStep == startStep + lengthOfTimeRange)
          long rangeUnit = gribGetLongDefault(gh, "indicatorOfUnitForTimeRange", 255);
          if (addToDate(&date, range, rangeUnit)) return NULL;
        }
      }
    }
  }

  // Bake the date into a string.
  return makeDateString(&date);
}

int
gribapiTimeIsFC(grib_handle *gh)
{
  if (gribEditionNumber(gh) <= 1) return true;

  long sigofrtime;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "significanceOfReferenceTime", &sigofrtime);
  return sigofrtime != 3;
}

struct cdiGribAPI_ts_str_map_elem cdiGribAPI_ts_str_map[] = {
  // clang-format off
  [0]             = {  0, "" },
  [TSTEP_INSTANT] = {  0, "instant" },
  [TSTEP_AVG]     = {  8, "avg" },
  [TSTEP_ACCUM]   = {  8, "accum" },
  [TSTEP_MAX]     = {  8, "max" },
  [TSTEP_MIN]     = {  8, "min" },
  [TSTEP_DIFF]    = {  8, "diff" },
  [TSTEP_RMS]     = {  8, "rms" },
  [TSTEP_SD]      = {  8, "sd" },
  [TSTEP_COV]     = {  8, "cov" },
  [TSTEP_RATIO]   = {  8, "ratio" },
  [TSTEP_SUM]     = {  8, "sum" }
  // clang-format on
};

// Fetches the value of the "stepType" key and converts it into a constant in the TSTEP_* range.
int
gribapiGetTsteptype(grib_handle *gh)
{
  size_t len = 256;
  char stepType[256];
  int tsteptype = TSTEP_INSTANT;
  static bool lprint = true;

  if (gribapiTimeIsFC(gh))
  {
    int status = grib_get_string(gh, "stepType", stepType, &len);
    if (status == 0 && len > 1 && len < 256)
    {
      for (int i = TSTEP_INSTANT; i <= TSTEP_SUM; ++i)
        if (strncmp(cdiGribAPI_ts_str_map[i].sname, stepType, len) == 0)
        {
          tsteptype = i;
          goto tsteptypeFound;
        }

      if (lprint)
      {
        Message("Time stepType %s unsupported, set to instant!", stepType);
        lprint = false;
      }
      // printf("stepType: %s %ld %d\n", stepType, len, tsteptype);
    }

    long typeOfStat;
    status = grib_get_long(gh, "typeOfStatisticalProcessing", &typeOfStat);
    if (status == 0)
    {
      switch (typeOfStat)
      {
        case 0: return TSTEP_AVG;
        case 1: return TSTEP_ACCUM;
        case 2: return TSTEP_MAX;
        case 3: return TSTEP_MIN;
        case 4: return TSTEP_DIFF;
        case 5: return TSTEP_RMS;
        case 6: return TSTEP_SD;
        case 7: return TSTEP_COV;
        case 9: return TSTEP_RATIO;
        case 11: return TSTEP_SUM;
      }
    }

#ifdef HIRLAM_EXTENSIONS
    {
      // Normaly cdo looks in grib for attribute called "stepType", see above.
      // BUT NWP models such as Hirlam and Harmonie 37h1.2, use "timeRangeIndicator" instead!
      // Where for example:       0: for instanteneous fields; 4: for accumulated fields
      //  0:   Forecast product valid at reference time + P1
      //  2:   Product with a valid time ranging between reference time + P1 and reference time + P2
      //  4:   Accumulation (reference time + P1 to reference time + P2)
      //  5:   Difference(reference time + P2 minus reference time + P1) product considered valid at reference time + P2
      // More details on WMO standards:
      //               http://www.wmo.int/pages/prog/www/WDM/Guides/Guide-binary-2.html
      // tsteptype = TSTEP_INSTANT;  // default value for any case
      long timeRangeIND = 0;  // typically 0: for instanteneous fields; 4: for accumulated fields
      int rc = grib_get_long(gh, "timeRangeIndicator", &timeRangeIND);
      if (rc != 0)
      {
        // if ( lprint )
        Warning("Could not get 'stepType' either 'timeRangeIndicator'. Using default!");
        return tsteptype;
      }
      extern int cdiGribUseTimeRangeIndicator;
      cdiGribUseTimeRangeIndicator = 1;
      switch (timeRangeIND)
      {
        case 0: tsteptype = TSTEP_INSTANT; break;
        case 2:
          tsteptype = TSTEP_INSTANT2;
          strcpy(stepType, "instant2");
          break;  // was incorrectly set before into accum
        case 4: tsteptype = TSTEP_ACCUM; break;
        case 5: tsteptype = TSTEP_DIFF; break;
        default:
          if (lprint)
          {
            if (CDI_Debug)
              Warning("timeRangeIND = %d;  stepType= %s; tsteptype=%d unsupported timeRangeIND at the moment, set to instant!",
                      timeRangeIND, stepType, tsteptype);
            lprint = false;
          }
          break;
      }
      if (CDI_Debug) Warning("timeRangeIND = %d;  stepType= %s; tsteptype=%d", timeRangeIND, stepType, tsteptype);
    }
#endif  // HIRLAM_EXTENSIONS
  }

tsteptypeFound:
  return tsteptype;
}

int
gribGetDatatype(grib_handle *gribHandle)
{
  int datatype;
  if (gribEditionNumber(gribHandle) > 1 && gribCheckString(gribHandle, "packingType", "grid_ieee"))
  {
    datatype = gribCheckLong(gribHandle, "precision", 1) ? CDI_DATATYPE_FLT32 : CDI_DATATYPE_FLT64;
  }
  else
  {
    long bitsPerValue;
    datatype = (!grib_get_long(gribHandle, "bitsPerValue", &bitsPerValue) && bitsPerValue > 0 && bitsPerValue <= 32)
                   ? (int) bitsPerValue
                   : CDI_DATATYPE_PACK;
  }
  return datatype;
}

int
gribapiGetParam(grib_handle *gh)
{
  long pdis, pcat, pnum;
  if (gribEditionNumber(gh) <= 1)
  {
    pdis = 255;
    FAIL_ON_GRIB_ERROR(grib_get_long, gh, "table2Version", &pcat);
    FAIL_ON_GRIB_ERROR(grib_get_long, gh, "indicatorOfParameter", &pnum);
  }
  else
  {
    FAIL_ON_GRIB_ERROR(grib_get_long, gh, "discipline", &pdis);
    if (grib_get_long(gh, "parameterCategory", &pcat)) pcat = 0;
    if (grib_get_long(gh, "parameterNumber", &pnum)) pnum = 0;
  }
  return cdiEncodeParam((int) pnum, (int) pcat, (int) pdis);
}

static bool
has_ni(grib_handle *gh)
{
  return (gribGetLong(gh, "Ni") != (long) GRIB_MISSING_LONG);
}

int
gribapiGetGridType(grib_handle *gh)
{
  long gridDefinitionTemplateNumber = gribGetLongDefault(gh, "gridDefinitionTemplateNumber", -1);
  switch (gridDefinitionTemplateNumber)
  {
    case GRIB2_GTYPE_LATLON: return has_ni(gh) ? GRID_LONLAT : GRID_GENERIC;
    case GRIB2_GTYPE_GAUSSIAN: return has_ni(gh) ? GRID_GAUSSIAN : GRID_GAUSSIAN_REDUCED;
    case GRIB2_GTYPE_LATLON_ROT: return GRID_PROJECTION;
    case GRIB2_GTYPE_LCC: return CDI_PROJ_LCC;
    case GRIB2_GTYPE_LLAM: return CDI_PROJ_LCC;  // Handle LLAM as LCC
    case GRIB2_GTYPE_STERE: return CDI_PROJ_STERE;
    case GRIB2_GTYPE_SPECTRAL: return GRID_SPECTRAL;
    case GRIB2_GTYPE_GME: return GRID_GME;
    case GRIB2_GTYPE_UNSTRUCTURED: return GRID_UNSTRUCTURED;
    case GRIB2_GTYPE_HEALPIX: return CDI_PROJ_HEALPIX;
    // case GRIB2_GTYPE_HEALPIX: return GRID_HEALPIX;
    default:
    {
      static bool lwarn = true;
      if (lwarn)
      {
        lwarn = false;
        char mesg[256];
        size_t len = sizeof(mesg);
        if (grib_get_string(gh, "gridType", mesg, &len) != 0) mesg[0] = 0;
        Warning("gridDefinitionTemplateNumber %d unsupported (gridType=%s)!", gridDefinitionTemplateNumber, mesg);
      }
    }
  }

  return GRID_GENERIC;
}

static int
gribapiGetIsRotated(grib_handle *gh)
{
  return gribGetLongDefault(gh, "gridDefinitionTemplateNumber", -1) == GRIB2_GTYPE_LATLON_ROT;
}

size_t
gribapiGetGridsize(grib_handle *gh)
{
  size_t gridsize;
  FAIL_ON_GRIB_ERROR(grib_get_size, gh, "values", &gridsize);
  return gridsize;
}

static void
gribapiGetGridGaussianReduced(grib_handle *gh, grid_t *grid, long editionNumber, size_t numberOfPoints)
{
  long lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "numberOfParallelsBetweenAPoleAndTheEquator", &lpar);
  grid->np = (int) lpar;

  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Nj", &lpar);
  size_t nlat = (size_t) lpar;

  grid->size = numberOfPoints;

  grid->reducedPointsSize = (int) nlat;
  grid->reducedPoints = (int *) Malloc(nlat * sizeof(int));
  long *pl = (long *) Malloc(nlat * sizeof(long));
  size_t dummy = nlat;
  FAIL_ON_GRIB_ERROR(grib_get_long_array, gh, "pl", pl, &dummy);
  for (size_t i = 0; i < nlat; ++i) grid->reducedPoints[i] = (int) pl[i];
  Free(pl);

  grid->y.size = nlat;
  grid->x.inc = 0;
  grid->y.inc = 0;
  grid->x.flag = 0;
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "longitudeOfFirstGridPointInDegrees", &grid->x.first);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "longitudeOfLastGridPointInDegrees", &grid->x.last);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "latitudeOfFirstGridPointInDegrees", &grid->y.first);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "latitudeOfLastGridPointInDegrees", &grid->y.last);

  // FAIL_ON_GRIB_ERROR(grib_get_double, gh, "iDirectionIncrementInDegrees", &grid->x.inc);
  // if ( IS_EQUAL(grid->x.inc, GRIB_MISSING_DOUBLE) ) grid->x.inc = 0;

  if (grid->x.last < grid->x.first)
  {
    if (grid->x.first >= 180.0)
      grid->x.first -= 360.0;
    else
      grid->x.last += 360.0;
  }

  grid->x.flag = 2;

  grid->y.flag = 0;
  // if (IS_NOT_EQUAL(grid->y.first, 0) || IS_NOT_EQUAL(grid->y.last, 0))
  {
    if (grid->y.size > 1)
    {
      if (editionNumber <= 1) {}
    }
    grid->y.flag = 2;
  }
}

static void
gribapiGetGridRegular(grib_handle *gh, grid_t *grid, long editionNumber, int gridtype, size_t numberOfPoints)
{
  long lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Ni", &lpar);
  size_t nlon = (size_t) lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Nj", &lpar);
  size_t nlat = (size_t) lpar;

  if (gridtype == GRID_GAUSSIAN)
  {
    FAIL_ON_GRIB_ERROR(grib_get_long, gh, "numberOfParallelsBetweenAPoleAndTheEquator", &lpar);
    grid->np = (int) lpar;
  }

  if (numberOfPoints != nlon * nlat) Error("numberOfPoints (%zu) and gridSize (%zu) differ!", numberOfPoints, nlon * nlat);

  grid->size = numberOfPoints;
  grid->x.size = nlon;
  grid->y.size = nlat;
  grid->x.inc = 0;
  grid->y.inc = 0;
  grid->x.flag = 0;
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "longitudeOfFirstGridPointInDegrees", &grid->x.first);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "longitudeOfLastGridPointInDegrees", &grid->x.last);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "latitudeOfFirstGridPointInDegrees", &grid->y.first);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "latitudeOfLastGridPointInDegrees", &grid->y.last);
  if (nlon > 1) FAIL_ON_GRIB_ERROR(grib_get_double, gh, "iDirectionIncrementInDegrees", &grid->x.inc);
  if (gridtype == GRID_LONLAT && nlat > 1) FAIL_ON_GRIB_ERROR(grib_get_double, gh, "jDirectionIncrementInDegrees", &grid->y.inc);

  long iscan = 0, jscan = 0;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "iScansNegatively", &iscan);
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "jScansPositively", &jscan);
  if (iscan) grid->x.inc = -grid->x.inc;
  if (!jscan) grid->y.inc = -grid->y.inc;

  if (grid->x.inc < -999 || grid->x.inc > 999) grid->x.inc = 0;
  if (grid->y.inc < -999 || grid->y.inc > 999) grid->y.inc = 0;

  // if ( IS_NOT_EQUAL(grid->x.first, 0) || IS_NOT_EQUAL(grid->x.last, 0) )
  {
    if (grid->x.size > 1)
    {
      // if ( editionNumber <= 1 )
      {
        if (grid->x.last < grid->x.first)
        {
          if (grid->x.first >= 180)
            grid->x.first -= 360;
          else
            grid->x.last += 360;
        }

        // correct xinc if necessary
        if (IS_EQUAL(grid->x.first, 0) && grid->x.last > 354 && grid->x.last < 360)
        {
          double xinc = 360. / (double) grid->x.size;
          if (fabs(grid->x.inc - xinc) > 0.0)
          {
            grid->x.inc = xinc;
            if (CDI_Debug) Message("set xinc to %g", grid->x.inc);
          }
        }
      }
    }
    grid->x.flag = 2;
  }

  grid->y.flag = 0;
  // if ( IS_NOT_EQUAL(grid->y.first, 0) || IS_NOT_EQUAL(grid->y.last, 0) )
  {
    if (grid->y.size > 1)
    {
      if (editionNumber <= 1) {}
    }
    grid->y.flag = 2;
  }
}

static void
gribapiGetGridProj(grib_handle *gh, grid_t *grid, size_t numberOfPoints)
{
  long lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Nx", &lpar);
  size_t nlon = (size_t) lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Ny", &lpar);
  size_t nlat = (size_t) lpar;

  if (numberOfPoints != nlon * nlat) Error("numberOfPoints (%zu) and gridSize (%zu) differ!", numberOfPoints, nlon * nlat);

  grid->size = numberOfPoints;
  grid->x.size = nlon;
  grid->y.size = nlat;

  double xinc, yinc;
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "DxInMetres", &xinc);
  FAIL_ON_GRIB_ERROR(grib_get_double, gh, "DyInMetres", &yinc);

  grid->x.first = 0;
  grid->x.last = 0;
  grid->x.inc = xinc;
  grid->y.first = 0;
  grid->y.last = 0;
  grid->y.inc = yinc;
  grid->x.flag = 2;
  grid->y.flag = 2;
}

static void
gribapiGetProjHealpix(grib_handle *gh, grid_t *grid, size_t numberOfPoints)
{
  (void) gh;
  grid->size = numberOfPoints;
}

static void
gribapiGetGridHealpix(grib_handle *gh, grid_t *grid, size_t numberOfPoints)
{
  (void) gh;
  grid->size = numberOfPoints;
}

static void
gribapiGetGridSpectral(grib_handle *gh, grid_t *grid, size_t datasize)
{
  size_t len = 256;
  char typeOfPacking[256];
  FAIL_ON_GRIB_ERROR(grib_get_string, gh, "packingType", typeOfPacking, &len);
  grid->lcomplex = 0;
  if (strncmp(typeOfPacking, "spectral_complex", len) == 0) grid->lcomplex = 1;

  grid->size = datasize;

  long lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "J", &lpar);
  grid->trunc = (int) lpar;
}

static void
gribapiGetGridGME(grib_handle *gh, grid_t *grid, size_t numberOfPoints)
{
  grid->size = numberOfPoints;

  long lpar;
  if (grib_get_long(gh, "nd", &lpar) == 0) grid->gme.nd = (int) lpar;
  if (grib_get_long(gh, "Ni", &lpar) == 0) grid->gme.ni = (int) lpar;
  if (grib_get_long(gh, "n2", &lpar) == 0) grid->gme.ni2 = (int) lpar;
  if (grib_get_long(gh, "n3", &lpar) == 0) grid->gme.ni3 = (int) lpar;
}

static void
gribapiGetGridUnstructured(grib_handle *gh, grid_t *grid, size_t numberOfPoints)
{
  unsigned char uuid[CDI_UUID_SIZE];
  /*
    char reference_link[8192];
    size_t len = sizeof(reference_link);
    reference_link[0] = 0;
  */
  grid->size = numberOfPoints;

  long lpar;
  if (grib_get_long(gh, "numberOfGridUsed", &lpar) == 0)
  {
    cdiDefVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDUSED, (int) lpar);
    if (grib_get_long(gh, "numberOfGridInReference", &lpar) == 0)
      cdiDefVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE, (int) lpar);
    /*
      if ( grib_get_string(gh, "gridDescriptionFile", reference_link, &len) == 0 )
      {
      if ( strncmp(reference_link, "file://", 7) == 0 )
      grid->reference = strdup(reference_link);
      }
    */
    size_t len = (size_t) CDI_UUID_SIZE;
    if (grib_get_bytes(gh, "uuidOfHGrid", uuid, &len) == 0) cdiDefVarKeyBytes(&grid->keys, CDI_KEY_UUID, uuid, CDI_UUID_SIZE);
  }
}

static void
gribapiGetGridGeneric(grib_handle *gh, grid_t *grid, size_t numberOfPoints)
{
  long lpar;
  size_t nlon = (grib_get_long(gh, "Ni", &lpar) == 0) ? (size_t) lpar : 0;
  size_t nlat = (grib_get_long(gh, "Nj", &lpar) == 0) ? (size_t) lpar : 0;

  grid->size = numberOfPoints;

  bool lgeneric = (nlon > 0 && nlat > 0 && nlon * nlat == numberOfPoints);
  grid->x.size = lgeneric ? nlon : 0;
  grid->y.size = lgeneric ? nlat : 0;
}

// TODO: Simplify by use of the convenience functions (gribGetLong(), gribGetLongDefault(), etc.).
bool
gribapiGetGrid(grib_handle *gh, grid_t *grid)
{
  bool uvRelativeToGrid = false;
  long editionNumber = gribEditionNumber(gh);
  int gridtype = gribapiGetGridType(gh);
  int projtype = (gridtype == GRID_PROJECTION && gribapiGetIsRotated(gh)) ? CDI_PROJ_RLL : CDI_UNDEFID;
  if (gridtype == CDI_PROJ_LCC || gridtype == CDI_PROJ_STERE || gridtype == CDI_PROJ_HEALPIX)
  {
    projtype = gridtype;
    gridtype = GRID_PROJECTION;
  }
  /*
  if ( streamptr->unreduced && gridtype == GRID_GAUSSIAN_REDUCED )
    {
      gridtype = GRID_GAUSSIAN;
      ISEC2_NumLon = 2*ISEC2_NumLat;
      ISEC4_NumValues = ISEC2_NumLon*ISEC2_NumLat;
    }
  */
  grid_init(grid);
  cdiGridTypeInit(grid, gridtype, 0);

  size_t datasize;
  FAIL_ON_GRIB_ERROR(grib_get_size, gh, "values", &datasize);
  long lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "numberOfPoints", &lpar);
  size_t numberOfPoints = (size_t) lpar;

  if (gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || projtype == CDI_PROJ_RLL)
  {
    gribapiGetGridRegular(gh, grid, editionNumber, gridtype, numberOfPoints);
  }
  else if (gridtype == GRID_GAUSSIAN_REDUCED) { gribapiGetGridGaussianReduced(gh, grid, editionNumber, numberOfPoints); }
  else if (projtype == CDI_PROJ_LCC) { gribapiGetGridProj(gh, grid, numberOfPoints); }
  else if (projtype == CDI_PROJ_STERE) { gribapiGetGridProj(gh, grid, numberOfPoints); }
  else if (projtype == CDI_PROJ_HEALPIX) { gribapiGetProjHealpix(gh, grid, numberOfPoints); }
  else if (gridtype == GRID_HEALPIX) { gribapiGetGridHealpix(gh, grid, numberOfPoints); }
  else if (gridtype == GRID_SPECTRAL) { gribapiGetGridSpectral(gh, grid, datasize); }
  else if (gridtype == GRID_GME) { gribapiGetGridGME(gh, grid, numberOfPoints); }
  else if (gridtype == GRID_UNSTRUCTURED) { gribapiGetGridUnstructured(gh, grid, numberOfPoints); }
  else if (gridtype == GRID_GENERIC) { gribapiGetGridGeneric(gh, grid, numberOfPoints); }
  else { Error("Unsupported grid type: %s", gridNamePtr(gridtype)); }

  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_LONLAT || projtype == CDI_PROJ_RLL || projtype == CDI_PROJ_LCC
      || projtype == CDI_PROJ_HEALPIX)
  {
    long temp = 0;
    GRIB_CHECK(grib_get_long(gh, "uvRelativeToGrid", &temp), 0);
    assert(temp == 0 || temp == 1);
    uvRelativeToGrid = (bool) temp;
  }

  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_LONLAT || (gridtype == GRID_PROJECTION && projtype != CDI_PROJ_HEALPIX))
  {
    long iScansNegatively, jScansPositively, jPointsAreConsecutive;
    GRIB_CHECK(grib_get_long(gh, "iScansNegatively", &iScansNegatively), 0);
    GRIB_CHECK(grib_get_long(gh, "jScansPositively", &jScansPositively), 0);
    GRIB_CHECK(grib_get_long(gh, "jPointsAreConsecutive", &jPointsAreConsecutive), 0);

    int scanningMode = (int) (128 * iScansNegatively + 64 * jScansPositively + 32 * jPointsAreConsecutive);
    cdiDefVarKeyInt(&grid->keys, CDI_KEY_SCANNINGMODE, scanningMode);
    /* scanningMode  = 128 * iScansNegatively + 64 * jScansPositively + 32 * jPointsAreConsecutive;
                 64  = 128 * 0                + 64 *        1         + 32 * 0
                 00  = 128 * 0                + 64 *        0         + 32 * 0
                 96  = 128 * 0                + 64 *        1         + 32 * 1
       Default / implicit scanning mode is 64:
                          i and j scan positively, i points are consecutive (row-major)        */
#ifdef HIRLAM_EXTENSIONS
    if (cdiDebugExt >= 30 && editionNumber <= 1)
    {
      //  indicatorOfParameter=33,indicatorOfTypeOfLevel=105,level
      long paramId, levelTypeId, levelId;
      GRIB_CHECK(grib_get_long(gh, "indicatorOfParameter", &paramId), 0);
      GRIB_CHECK(grib_get_long(gh, "indicatorOfTypeOfLevel", &levelTypeId), 0);
      GRIB_CHECK(grib_get_long(gh, "level", &levelId), 0);
      Message("(param,ltype,level) = (%3d,%3d,%4d); Scanning mode = %02d -> bits:(%1d.%1d.%1d)*32;  uvRelativeToGrid = %02d",
              (int) paramId, (int) levelTypeId, (int) levelId, scanningMode, jPointsAreConsecutive, jScansPositively,
              iScansNegatively, uvRelativeToGrid);
    }
#endif  // HIRLAM_EXTENSIONS
  }

  grid->type = gridtype;
  grid->projtype = projtype;

  return uvRelativeToGrid;
}
#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDI_UUID_H
#define CDI_UUID_H

#ifdef HAVE_CONFIG_H
#endif


// clang-format off
#ifdef __cplusplus
extern "C" {
#endif

enum {
  uuidNumHexChars = 36,
};

static inline
int cdiUUIDIsNull(const unsigned char uuid[])
{
  int isNull = 1;
  for (size_t i = 0; i < CDI_UUID_SIZE; ++i) isNull &= (uuid[i] == 0);
  return isNull;
}

#ifndef _WIN32
void cdiCreateUUID(unsigned char uuid[CDI_UUID_SIZE]);
#endif

int cdiUUID2Str(const unsigned char uuid[], char uuidstr[]);
int cdiStr2UUID(const char *uuidstr, unsigned char uuid[]);

#ifdef __cplusplus
}
#endif
// clang-format on

#endif
#ifndef RESOURCE_UNPACK_H
#define RESOURCE_UNPACK_H

#ifdef HAVE_CONFIG_H
#endif

enum
{
  GRID = 1,
  ZAXIS = 2,
  TAXIS = 3,
  INSTITUTE = 4,
  MODEL = 5,
  STREAM = 6,
  VLIST = 7,
  DIST_GRID = 8,
  RESH_DELETE,
  START = 55555555,
  END = 99999999
};

typedef void (*cdiPostResUpdateHook)(int resH, int resType);

int reshUnpackResources(char *unpackBuffer, int unpackBufferSize, void *context, cdiPostResUpdateHook postHook);

extern int (*reshDistGridUnpack)(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context,
                                 int force_id);
#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef VLIST_H
#define VLIST_H

#ifdef HAVE_CONFIG_H
#endif

#ifndef ERROR_H
#endif

#include <stdbool.h>
#include <stddef.h> /* size_t */

#ifndef CDI_LIMITS_H
#endif

#define VALIDMISS 1.e+303


typedef struct
{
  bool flag;
  int index;
  int mlevelID;
  int flevelID;
} levinfo_t;

#define DEFAULT_LEVINFO(levID) \
  (levinfo_t) { 0, -1, levID, levID }
/*
#define DEFAULT_LEVINFO(levID) \
  (levinfo_t){ .flag = 0, .index = -1, .flevelID = levID, .mlevelID = levID}
*/
typedef struct
{
  int ens_index;
  int ens_count;
  int forecast_init_type;
} ensinfo_t;

typedef struct
{
  bool isUsed;
  bool flag;
  bool lvalidrange;
  signed char xyz;   /* order of spatial dimensions,
                      * a permutation of 123 */
  bool missvalused;  // true if missval is defined
  int mvarID;
  int fvarID;
  int param;
  int gridID;
  int zaxisID;
  int timetype;   // TIME_*
  int tsteptype;  // TSTEP_*
  int datatype;   // CDI_DATATYPE_PACKX for GRIB data, else CDI_DATATYPE_FLT32 or CDI_DATATYPE_FLT64
  int instID;
  int modelID;
  int tableID;
  int timave;
  int nsb;  // Number of significant bits
  double missval;
  double validrange[2];
  levinfo_t *levinfo;
  int comptype;   // compression type
  int complevel;  // compression level
  cdi_keys_t keys;
  cdi_atts_t atts;
  int subtypeID;  // subtype ID for tile-related meta-data, currently for GRIB-API only.

  int opt_grib_nentries;                // current no. key-value pairs
  int opt_grib_kvpair_size;             // current allocated size
  opt_key_val_pair_t *opt_grib_kvpair;  // (optional) list of keyword/value pairs
} var_t;

typedef struct
{
  // set when a vlist is passed to streamDefVlist() to safeguard against modifications of the wrong vlist object
  bool immutable;
  // set if this vlist has been created by CDI itself, and must not be destroyed by the user, consequently
  bool internal;
  int self;
  int nvars;  // number of variables
  int ngrids;
  int nzaxis;
  int nsubtypes;  // no. of variable subtypes (e.g. sets of tiles)
  long ntsteps;
  int taxisID;
  int tableID;
  int instID;
  int modelID;
  int varsAllocated;
  int gridIDs[MAX_GRIDS_PS];
  int zaxisIDs[MAX_ZAXES_PS];
  int subtypeIDs[MAX_SUBTYPES_PS];
  var_t *vars;
  cdi_keys_t keys;
  cdi_atts_t atts;
} vlist_t;

vlist_t *vlist_to_pointer(int vlistID);
void cdiVlistMakeInternal(int vlistID);
void cdiVlistMakeImmutable(int vlistID);
void cdiVlistDestroy_(int vlistID, bool assertInternal);
int vlistInqVarMissvalUsed(int vlistID, int varID);
int vlistHasTime(int vlistID);

int vlistUnpack(char *buffer, int bufferSize, int *pos, int originNamespace, void *context, int force_id);

/*      vlistDefVarValidrange: Define the valid range of a Variable */
void vlistDefVarValidrange(int vlistID, int varID, const double *validrange);

/*      vlistInqVarValidrange: Get the valid range of a Variable */
int vlistInqVarValidrange(int vlistID, int varID, double *validrange);

void vlistInqVarDimorder(int vlistID, int varID, int outDimorder[3]);

void resize_opt_grib_entries(var_t *var, int nentries);

static inline void
vlistAdd2GridIDs(vlist_t *vlistptr, int gridID)
{
  int index, ngrids = vlistptr->ngrids;
  for (index = 0; index < ngrids; index++)
  {
    if (vlistptr->gridIDs[index] == gridID) break;
    //      if ( gridIsEqual(vlistptr->gridIDs[index], gridID) ) break;
  }

  if (index == ngrids)
  {
    if (ngrids >= MAX_GRIDS_PS) Error("Internal limit exceeded: more than %d grids.", MAX_GRIDS_PS);
    vlistptr->gridIDs[ngrids] = gridID;
    ++(vlistptr->ngrids);
  }
}

static inline void
vlistAdd2ZaxisIDs(vlist_t *vlistptr, int zaxisID)
{
  int index, nzaxis = vlistptr->nzaxis;
  for (index = 0; index < nzaxis; index++)
    if (zaxisID == vlistptr->zaxisIDs[index]) break;

  if (index == nzaxis)
  {
    if (nzaxis >= MAX_ZAXES_PS) Error("Internal limit exceeded: more than %d zaxis.", MAX_ZAXES_PS);
    vlistptr->zaxisIDs[nzaxis] = zaxisID;
    ++(vlistptr->nzaxis);
  }
}

static inline void
vlistAdd2SubtypeIDs(vlist_t *vlistptr, int subtypeID)
{
  if (subtypeID == CDI_UNDEFID) return;

  int index, nsubs = vlistptr->nsubtypes;
  for (index = 0; index < nsubs; index++)
    if (vlistptr->subtypeIDs[index] == subtypeID) break;

  if (index == nsubs)
  {
    if (nsubs >= MAX_SUBTYPES_PS) Error("Internal limit exceeded: more than %d subs.", MAX_SUBTYPES_PS);
    vlistptr->subtypeIDs[nsubs] = subtypeID;
    ++(vlistptr->nsubtypes);
  }
}

#ifdef HAVE_LIBGRIB_API
extern int cdiNAdditionalGRIBKeys;
extern char *cdiAdditionalGRIBKeys[];
#endif

extern const resOps vlistOps;

#endif /* VLIST_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <assert.h>
#include <string.h>


int (*proj_lonlat_to_lcc_func)(struct CDI_GridProjParams gpp, size_t, double *, double *) = NULL;
int (*proj_lcc_to_lonlat_func)(struct CDI_GridProjParams gpp, double, double, size_t, double *, double *) = NULL;
int (*proj_lonlat_to_stere_func)(struct CDI_GridProjParams gpp, size_t, double *, double *) = NULL;
int (*proj_stere_to_lonlat_func)(struct CDI_GridProjParams gpp, double, double, size_t, double *, double *) = NULL;

// the value in the second pair of brackets must match the length of the longest string (including terminating NUL)
static const char Grids[][17] = {
  /*  0 */ "undefined",
  /*  1 */ "generic",
  /*  2 */ "gaussian",
  /*  3 */ "gaussian_reduced",
  /*  4 */ "lonlat",
  /*  5 */ "spectral",
  /*  6 */ "fourier",
  /*  7 */ "gme",
  /*  8 */ "trajectory",
  /*  9 */ "unstructured",
  /* 10 */ "curvilinear",
  /* 11 */ "healpix",
  /* 12 */ "projection",
  /* 13 */ "characterXY",
};

// must match table below
enum xystdname_idx
{
  grid_xystdname_grid_latlon,
  grid_xystdname_latlon,
  grid_xystdname_projection,
  grid_xystdname_char,
};
static const char xystdname_tab[][2][24] = {
  [grid_xystdname_grid_latlon] = { "grid_longitude", "grid_latitude" },
  [grid_xystdname_latlon] = { "longitude", "latitude" },
  [grid_xystdname_projection] = { "projection_x_coordinate", "projection_y_coordinate" },
  [grid_xystdname_char] = { "region", "region" },
};

static int gridCompareP(void *gridptr1, void *gridptr2);
static void gridDestroyP(void *gridptr);
static void gridPrintP(void *gridptr, FILE *fp);
static int gridGetPackSize(void *gridptr, void *context);
static void gridPack(void *gridptr, void *buff, int size, int *position, void *context);
static int gridTxCode(void *gridptr);

static const resOps gridOps = { gridCompareP, gridDestroyP, gridPrintP, gridGetPackSize, gridPack, gridTxCode };

grid_t *
grid_to_pointer(int gridID)
{
  return (grid_t *) reshGetVal(gridID, &gridOps);
}

#define gridMark4Update(gridID) reshSetStatus(gridID, &gridOps, RESH_DESYNC_IN_USE)

static inline bool
grid_is_irregular(int gridType)
{
  return (gridType == GRID_UNSTRUCTURED || gridType == GRID_CURVILINEAR);
}

static bool
cdiInqAttConvertedToFloat(int gridID, int atttype, const char *attname, int attlen, double *attflt)
{
  bool status = true;

  if (atttype == CDI_DATATYPE_INT32)
  {
    int attint = 0;
    int *pattint = attlen > 1 ? (int *) Malloc((size_t) attlen * sizeof(int)) : &attint;
    cdiInqAttInt(gridID, CDI_GLOBAL, attname, attlen, pattint);
    for (int i = 0; i < attlen; ++i) attflt[i] = (double) pattint[i];
    if (attlen > 1) Free(pattint);
  }
  else if (atttype == CDI_DATATYPE_FLT32 || atttype == CDI_DATATYPE_FLT64)
  {
    cdiInqAttFlt(gridID, CDI_GLOBAL, attname, attlen, attflt);
  }
  else { status = false; }

  return status;
}

static void
grid_axis_init(struct gridaxis_t *axisptr)
{
  axisptr->size = 0;
  axisptr->vals = NULL;
  axisptr->bounds = NULL;
  axisptr->flag = 0;
  axisptr->first = 0.0;
  axisptr->last = 0.0;
  axisptr->inc = 0.0;
#ifndef USE_MPI
  axisptr->clength = 0;
  axisptr->cvals = NULL;
#endif
  cdiInitKeys(&axisptr->keys);
}

enum cdiApplyRet
cdiGridApply(enum cdiApplyRet (*func)(int id, void *res, void *data), void *data)
{
  return cdiResHFilterApply(&gridOps, func, data);
}

void
grid_init(grid_t *gridptr)
{
  gridptr->self = CDI_UNDEFID;
  gridptr->type = CDI_UNDEFID;
  gridptr->datatype = CDI_UNDEFID;
  gridptr->proj = CDI_UNDEFID;
  gridptr->projtype = CDI_UNDEFID;
  gridptr->mask = NULL;
  gridptr->mask_gme = NULL;
  gridptr->size = 0;

  grid_axis_init(&gridptr->x);
  grid_axis_init(&gridptr->y);

  gridptr->indices = NULL;
  gridptr->area = NULL;
  gridptr->reducedPoints = NULL;
  gridptr->reducedPointsSize = 0;

  gridptr->gme.nd = 0;
  gridptr->gme.ni = 0;
  gridptr->gme.ni2 = 0;
  gridptr->gme.ni3 = 0;

  gridptr->trunc = 0;
  gridptr->nvertex = 0;
  gridptr->np = 0;
  gridptr->isCyclic = CDI_UNDEFID;

  gridptr->lcomplex = false;
  gridptr->hasdims = true;
  gridptr->name = NULL;
  gridptr->vtable = &cdiGridVtable;

  cdiInitKeys(&gridptr->keys);
  gridptr->atts.nalloc = MAX_ATTRIBUTES;
  gridptr->atts.nelems = 0;

  cdiDefVarKeyInt(&gridptr->keys, CDI_KEY_DATATYPE, CDI_DATATYPE_FLT64);
#ifdef HIRLAM_EXTENSIONS
  cdiDefVarKeyInt(&gridptr->keys, CDI_KEY_SCANNINGMODE, 64);
#endif

  gridptr->extraData = NULL;
}

static void
grid_free_components(grid_t *gridptr)
{
  void *p2free[] = { gridptr->indices,  gridptr->mask,     gridptr->mask_gme,      gridptr->x.vals, gridptr->y.vals,
#ifndef USE_MPI
                     gridptr->x.cvals,  gridptr->y.cvals,
#endif
                     gridptr->x.bounds, gridptr->y.bounds, gridptr->reducedPoints, gridptr->area,   gridptr->name };

  for (size_t i = 0; i < sizeof(p2free) / sizeof(p2free[0]); ++i)
    if (p2free[i]) Free(p2free[i]);

  cdiDeleteVarKeys(&(gridptr->x.keys));
  cdiDeleteVarKeys(&(gridptr->y.keys));
  cdiDeleteVarKeys(&(gridptr->keys));
  /* 12 pio tests fail
  int gridID = gridptr->self;
  if (gridID != CDI_UNDEFID) cdiDeleteAtts(gridID, CDI_GLOBAL);
  */
}

void
grid_free(grid_t *gridptr)
{
  if (gridptr)
  {
    grid_free_components(gridptr);
    grid_init(gridptr);
  }
}

static grid_t *
gridNewEntry(cdiResH resH)
{
  grid_t *gridptr = (grid_t *) Malloc(sizeof(grid_t));
  grid_init(gridptr);

  if (resH == CDI_UNDEFID)
    gridptr->self = reshPut(gridptr, &gridOps);
  else
  {
    gridptr->self = resH;
    reshReplace(resH, gridptr, &gridOps);
  }

  return gridptr;
}

static void
gridInit(void)
{
  static bool gridInitialized = false;
  if (gridInitialized) return;
  gridInitialized = true;
}

static void
grid_copy_base_scalar_fields(grid_t *gridptrOrig, grid_t *gridptrDup)
{
  memcpy(gridptrDup, gridptrOrig, sizeof(grid_t));
  gridptrDup->self = CDI_UNDEFID;
  cdiInitKeys(&gridptrDup->keys);
  cdiCopyVarKeys(&gridptrOrig->keys, &gridptrDup->keys);
  cdiInitKeys(&gridptrDup->x.keys);
  cdiCopyVarKeys(&gridptrOrig->x.keys, &gridptrDup->x.keys);
  cdiInitKeys(&gridptrDup->y.keys);
  cdiCopyVarKeys(&gridptrOrig->y.keys, &gridptrDup->y.keys);
}

static grid_t *
grid_copy_base(grid_t *gridptrOrig)
{
  grid_t *gridptrDup = (grid_t *) Malloc(sizeof(*gridptrDup));
  gridptrOrig->vtable->copyScalarFields(gridptrOrig, gridptrDup);
  gridptrOrig->vtable->copyArrayFields(gridptrOrig, gridptrDup);
  return gridptrDup;
}

unsigned
cdiGridCount(void)
{
  return reshCountType(&gridOps);
}

static inline void
gridaxisSetKey(struct gridaxis_t *axisptr, int key, const char *name)
{
  if (find_key(&axisptr->keys, key) == NULL)
    cdiDefVarKeyBytes(&axisptr->keys, key, (const unsigned char *) name, (int) strlen(name) + 1);
}

void
cdiGridTypeInit(grid_t *gridptr, int gridtype, size_t size)
{
  gridptr->type = gridtype;
  gridptr->size = size;

  // clang-format off
  if      (gridtype == GRID_LONLAT)           gridptr->nvertex = 2;
  else if (gridtype == GRID_GAUSSIAN)         gridptr->nvertex = 2;
  else if (gridtype == GRID_GAUSSIAN_REDUCED) gridptr->nvertex = 2;
  else if (gridtype == GRID_CURVILINEAR)      gridptr->nvertex = 4;
  else if (gridtype == GRID_UNSTRUCTURED)     gridptr->x.size = size;
  // clang-format on

  switch (gridtype)
  {
    case GRID_LONLAT:
    case GRID_GAUSSIAN:
    case GRID_GAUSSIAN_REDUCED:
    case GRID_TRAJECTORY:
    case GRID_CURVILINEAR:
    case GRID_UNSTRUCTURED:
    case GRID_GME:
    {
      if (gridtype == GRID_TRAJECTORY)
      {
        gridaxisSetKey(&gridptr->x, CDI_KEY_NAME, "tlon");
        gridaxisSetKey(&gridptr->y, CDI_KEY_NAME, "tlat");
      }
      else
      {
        gridaxisSetKey(&gridptr->x, CDI_KEY_NAME, "lon");
        gridaxisSetKey(&gridptr->y, CDI_KEY_NAME, "lat");
      }

      gridaxisSetKey(&gridptr->x, CDI_KEY_LONGNAME, "longitude");
      gridaxisSetKey(&gridptr->y, CDI_KEY_LONGNAME, "latitude");

      gridaxisSetKey(&gridptr->x, CDI_KEY_UNITS, "degrees_east");
      gridaxisSetKey(&gridptr->y, CDI_KEY_UNITS, "degrees_north");

      gridaxisSetKey(&gridptr->x, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_latlon][0]);
      gridaxisSetKey(&gridptr->y, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_latlon][1]);

      break;
    }
#ifndef USE_MPI
    case GRID_CHARXY:
    {
      if (gridptr->x.cvals) gridaxisSetKey(&gridptr->x, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_char][0]);
      if (gridptr->y.cvals) gridaxisSetKey(&gridptr->y, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_char][1]);

      break;
    }
#endif
    case GRID_GENERIC:
    case GRID_PROJECTION:
    {
      gridaxisSetKey(&gridptr->x, CDI_KEY_NAME, "x");
      gridaxisSetKey(&gridptr->y, CDI_KEY_NAME, "y");
      if (gridtype == GRID_PROJECTION)
      {
        gridaxisSetKey(&gridptr->x, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_projection][0]);
        gridaxisSetKey(&gridptr->y, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_projection][1]);
        gridaxisSetKey(&gridptr->x, CDI_KEY_UNITS, "m");
        gridaxisSetKey(&gridptr->y, CDI_KEY_UNITS, "m");
      }
      break;
    }
  }
}

// used also in CDO
void
gridGenXvals(size_t xsize, double xfirst, double xlast, double xinc, double *restrict xvals)
{
  if (fabs(xinc) <= 0 && xsize > 1)
  {
    if (xfirst >= xlast)
    {
      while (xfirst >= xlast) xlast += 360;
      xinc = (xlast - xfirst) / (double) xsize;
    }
    else { xinc = (xlast - xfirst) / (double) (xsize - 1); }
  }

  for (size_t i = 0; i < xsize; ++i) xvals[i] = xfirst + (double) i * xinc;
}

static void
calc_gaussgrid(double *restrict yvals, size_t ysize, double yfirst, double ylast)
{
  double *restrict yw = (double *) Malloc(ysize * sizeof(double));
  gaussianLatitudes(ysize, yvals, yw);
  Free(yw);
  for (size_t i = 0; i < ysize; i++) yvals[i] = asin(yvals[i]) / M_PI * 180.0;

  if (yfirst < ylast && yfirst > -90.0 && ylast < 90.0)
  {
    size_t yhsize = ysize / 2;
    for (size_t i = 0; i < yhsize; i++)
    {
      const double ytmp = yvals[i];
      yvals[i] = yvals[ysize - i - 1];
      yvals[ysize - i - 1] = ytmp;
    }
  }
}

static void
gridGenYvalsGaussian(size_t ysize, double yfirst, double ylast, double *restrict yvals)
{
  const double deleps = 0.002;

  calc_gaussgrid(yvals, ysize, yfirst, ylast);

  if (!(IS_EQUAL(yfirst, 0) && IS_EQUAL(ylast, 0)))
    if (fabs(yvals[0] - yfirst) > deleps || fabs(yvals[ysize - 1] - ylast) > deleps)
    {
      bool lfound = false;
      long ny = (long) (180. / (fabs(ylast - yfirst) / (double) (ysize - 1)) + 0.5);
      ny -= ny % 2;
      if (ny > 0 && (size_t) ny > ysize && ny < 4096)
      {
        double *ytmp = (double *) Malloc((size_t) ny * sizeof(double));
        calc_gaussgrid(ytmp, (size_t) ny, yfirst, ylast);

        size_t nstart = (size_t) ny - ysize;
        for (size_t i = 0; i < ((size_t) ny - ysize); i++)
          if (fabs(ytmp[i] - yfirst) < deleps)
          {
            nstart = i;
            break;
          }

        lfound = (nstart + ysize - 1) < (size_t) ny && fabs(ytmp[nstart + ysize - 1] - ylast) < deleps;
        if (lfound)
        {
          for (size_t i = 0; i < ysize; i++) yvals[i] = ytmp[i + nstart];
        }

        if (ytmp) Free(ytmp);
      }

      if (!lfound)
      {
        Warning("Cannot calculate gaussian latitudes for lat1 = %g latn = %g!", yfirst, ylast);
        for (size_t i = 0; i < ysize; i++) yvals[i] = 0;
        yvals[0] = yfirst;
        yvals[ysize - 1] = ylast;
      }
    }
}

static void
gridGenYvalsRegular(size_t ysize, double yfirst, double ylast, double yinc, double *restrict yvals)
{
  if (fabs(yinc) <= 0 && ysize > 1)
  {
    if (IS_EQUAL(yfirst, ylast) && IS_NOT_EQUAL(yfirst, 0)) ylast *= -1;

    if (yfirst > ylast)
      yinc = (yfirst - ylast) / (double) (ysize - 1);
    else if (yfirst < ylast)
      yinc = (ylast - yfirst) / (double) (ysize - 1);
    else
    {
      if (ysize % 2 != 0)
      {
        yinc = 180.0 / (double) (ysize - 1);
        yfirst = -90;
      }
      else
      {
        yinc = 180.0 / (double) ysize;
        yfirst = -90 + yinc / 2;
      }
    }
  }

  if (yfirst > ylast && yinc > 0) yinc = -yinc;

  for (size_t i = 0; i < ysize; i++) yvals[i] = yfirst + (double) i * yinc;
}

// used also in CDO
void
gridGenYvals(int gridtype, size_t ysize, double yfirst, double ylast, double yinc, double *restrict yvals)
{
  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED)
  {
    if (ysize > 2) { gridGenYvalsGaussian(ysize, yfirst, ylast, yvals); }
    else
    {
      yvals[0] = yfirst;
      yvals[ysize - 1] = ylast;
    }
  }
  // else if (gridtype == GRID_LONLAT || gridtype == GRID_GENERIC)
  else { gridGenYvalsRegular(ysize, yfirst, ylast, yinc, yvals); }
  /*
    else
    Error("unable to calculate values for %s grid!", gridNamePtr(gridtype));
  */
}

/*
@Function  gridCreate
@Title     Create a horizontal Grid

@Prototype int gridCreate(int gridtype, SizeType size)
@Parameter
    @Item  gridtype  The type of the grid, one of the set of predefined CDI grid types.
                     The valid CDI grid types are @func{GRID_GENERIC}, @func{GRID_LONLAT},
                     @func{GRID_GAUSSIAN}, @func{GRID_PROJECTION}, @func{GRID_SPECTRAL},
                     @func{GRID_GME}, @func{GRID_CURVILINEAR} and @func{GRID_UNSTRUCTURED}.
    @Item  size      Number of gridpoints.

@Description
The function @func{gridCreate} creates a horizontal Grid.

@Result
@func{gridCreate} returns an identifier to the Grid.

@Example
Here is an example using @func{gridCreate} to create a regular lon/lat Grid:

@Source
   ...
#define  nlon  12
#define  nlat   6
   ...
double lons[nlon] = {0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330};
double lats[nlat] = {-75, -45, -15, 15, 45, 75};
int gridID;
   ...
gridID = gridCreate(GRID_LONLAT, nlon*nlat);
gridDefXsize(gridID, nlon);
gridDefYsize(gridID, nlat);
gridDefXvals(gridID, lons);
gridDefYvals(gridID, lats);
   ...
@EndSource
@EndFunction
*/
int
gridCreate(int gridtype, SizeType size)
{
  if (CDI_Debug) Message("gridtype=%s  size=%zu", gridNamePtr(gridtype), (size_t) size);

  xassert(size);
  gridInit();

  grid_t *gridptr = gridNewEntry(CDI_UNDEFID);
  if (!gridptr) Error("No memory");

  int gridID = gridptr->self;
  if (CDI_Debug) Message("gridID: %d", gridID);

  cdiGridTypeInit(gridptr, gridtype, (size_t) size);

  return gridID;
}

static void
gridDestroyKernel(grid_t *gridptr)
{
  xassert(gridptr);

  grid_free_components(gridptr);
  Free(gridptr);
}

/*
@Function  gridDestroy
@Title     Destroy a horizontal Grid

@Prototype void gridDestroy(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.

@EndFunction
*/
void
gridDestroy(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->destroy(gridptr);
  reshRemove(gridID, &gridOps);
}

static void
gridDestroyP(void *gridptr)
{
  ((grid_t *) gridptr)->vtable->destroy((grid_t *) gridptr);
}

const char *
gridNamePtr(int gridtype)
{
  int size = (int) (sizeof(Grids) / sizeof(Grids[0]));
  return (gridtype >= 0 && gridtype < size) ? Grids[gridtype] : Grids[GRID_GENERIC];
}

void
gridName(int gridtype, char *gridname)
{
  strcpy(gridname, gridNamePtr(gridtype));
}

/*
@Function  gridDefXname
@Title     Define the name of a X-axis

@Prototype void gridDefXname(int gridID, const char *name)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  name     Name of the X-axis.

@Description
The function @func{gridDefXname} defines the name of a X-axis.

@EndFunction
*/
void
gridDefXname(int gridID, const char *name)
{
  (void) cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_NAME, name);
}

/*
@Function  gridDefXlongname
@Title     Define the longname of a X-axis

@Prototype void gridDefXlongname(int gridID, const char *longname)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  longname Longname of the X-axis.

@Description
The function @func{gridDefXlongname} defines the longname of a X-axis.

@EndFunction
*/
void
gridDefXlongname(int gridID, const char *longname)
{
  (void) cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_LONGNAME, longname);
}

/*
@Function  gridDefXunits
@Title     Define the units of a X-axis

@Prototype void gridDefXunits(int gridID, const char *units)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  units    Units of the X-axis.

@Description
The function @func{gridDefXunits} defines the units of a X-axis.

@EndFunction
*/
void
gridDefXunits(int gridID, const char *units)
{
  (void) cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_UNITS, units);
}

/*
@Function  gridDefYname
@Title     Define the name of a Y-axis

@Prototype void gridDefYname(int gridID, const char *name)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  name     Name of the Y-axis.

@Description
The function @func{gridDefYname} defines the name of a Y-axis.

@EndFunction
*/
void
gridDefYname(int gridID, const char *name)
{
  (void) cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_NAME, name);
}

/*
@Function  gridDefYlongname
@Title     Define the longname of a Y-axis

@Prototype void gridDefYlongname(int gridID, const char *longname)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  longname Longname of the Y-axis.

@Description
The function @func{gridDefYlongname} defines the longname of a Y-axis.

@EndFunction
*/
void
gridDefYlongname(int gridID, const char *longname)
{
  (void) cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_LONGNAME, longname);
}

/*
@Function  gridDefYunits
@Title     Define the units of a Y-axis

@Prototype void gridDefYunits(int gridID, const char *units)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  units    Units of the Y-axis.

@Description
The function @func{gridDefYunits} defines the units of a Y-axis.

@EndFunction
*/
void
gridDefYunits(int gridID, const char *units)
{
  (void) cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_UNITS, units);
}

/*
@Function  gridInqXname
@Title     Get the name of a X-axis

@Prototype void gridInqXname(int gridID, char *name)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  name     Name of the X-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{gridInqXname} returns the name of a X-axis.

@Result
@func{gridInqXname} returns the name of the X-axis to the parameter name.

@EndFunction
*/
void
gridInqXname(int gridID, char *name)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_NAME, name, &length);
}

/*
@Function  gridInqXlongname
@Title     Get the longname of a X-axis

@Prototype void gridInqXlongname(int gridID, char *longname)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  longname Longname of the X-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{gridInqXlongname} returns the longname of a X-axis.

@Result
@func{gridInqXlongname} returns the longname of the X-axis to the parameter longname.

@EndFunction
*/
void
gridInqXlongname(int gridID, char *longname)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_LONGNAME, longname, &length);
}

/*
@Function  gridInqXunits
@Title     Get the units of a X-axis

@Prototype void gridInqXunits(int gridID, char *units)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  units    Units of the X-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{gridInqXunits} returns the units of a X-axis.

@Result
@func{gridInqXunits} returns the units of the X-axis to the parameter units.

@EndFunction
*/
void
gridInqXunits(int gridID, char *units)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_UNITS, units, &length);
}

/*
@Function  gridInqYname
@Title     Get the name of a Y-axis

@Prototype void gridInqYname(int gridID, char *name)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  name     Name of the Y-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{gridInqYname} returns the name of a Y-axis.

@Result
@func{gridInqYname} returns the name of the Y-axis to the parameter name.

@EndFunction
*/
void
gridInqYname(int gridID, char *name)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_NAME, name, &length);
}

/*
@Function  gridInqYlongname
@Title     Get the longname of a Y-axis

@Prototype void gridInqYlongname(int gridID, char *longname)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  longname Longname of the Y-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{gridInqYlongname} returns the longname of a Y-axis.

@Result
@func{gridInqYlongname} returns the longname of the Y-axis to the parameter longname.

@EndFunction
*/
void
gridInqYlongname(int gridID, char *longname)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_LONGNAME, longname, &length);
}

/*
@Function  gridInqYunits
@Title     Get the units of a Y-axis

@Prototype void gridInqYunits(int gridID, char *units)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  units    Units of the Y-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{gridInqYunits} returns the units of a Y-axis.

@Result
@func{gridInqYunits} returns the units of the Y-axis to the parameter units.

@EndFunction
*/
void
gridInqYunits(int gridID, char *units)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_UNITS, units, &length);
}

static void
gridDefIndicesSerial(grid_t *gridptr, const int64_t *indices)
{
  size_t size = gridptr->size;
  if (size == 0) Error("Size undefined for gridID = %d", gridptr->self);

  if (gridptr->indices && CDI_Debug) Warning("values already defined!");
  gridptr->indices = (int64_t *) Realloc(gridptr->indices, size * sizeof(int64_t));
  memcpy(gridptr->indices, indices, size * sizeof(int64_t));
}

void
gridDefIndices(int gridID, const int64_t *indices)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defIndices(gridptr, indices);
  gridMark4Update(gridID);
}

static const int64_t *
gridInqIndicesPtrSerial(grid_t *gridptr)
{
  return gridptr->indices;
}

static SizeType
gridInqIndicesSerial(grid_t *gridptr, int64_t *indices)
{
  size_t size = gridptr->size;
  if (CDI_Debug && size == 0) Warning("size undefined for gridID = %d", gridptr->self);

  if (gridptr->indices)
  {
    if (size && indices) memcpy(indices, gridptr->vtable->inqIndicesPtr(gridptr), size * sizeof(int64_t));
  }
  else
    size = 0;

  return (SizeType) size;
}

SizeType
gridInqIndices(int gridID, int64_t *indices)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqIndices(gridptr, indices);
}

void
gridDefProj(int gridID, int projID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->proj = projID;

  if (gridptr->type == GRID_CURVILINEAR)
  {
    grid_t *projptr = grid_to_pointer(projID);
    const char *xdimname = cdiInqVarKeyStringPtr(&gridptr->x.keys, CDI_KEY_DIMNAME);
    const char *ydimname = cdiInqVarKeyStringPtr(&gridptr->y.keys, CDI_KEY_DIMNAME);
    if (xdimname && find_key(&projptr->x.keys, CDI_KEY_NAME)) cdiDefKeyString(projID, CDI_XAXIS, CDI_KEY_NAME, xdimname);
    if (ydimname && find_key(&projptr->y.keys, CDI_KEY_NAME)) cdiDefKeyString(projID, CDI_YAXIS, CDI_KEY_NAME, ydimname);
  }
}

int
gridInqProj(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->proj;
}

int
gridInqProjType(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  int projtype = gridptr->projtype;
  if (projtype == -1)
  {
    char gmapname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
    if (gmapname[0])
    {
      // clang-format off
      if      (str_is_equal(gmapname, "rotated_latitude_longitude"))   projtype = CDI_PROJ_RLL;
      else if (str_is_equal(gmapname, "lambert_azimuthal_equal_area")) projtype = CDI_PROJ_LAEA;
      else if (str_is_equal(gmapname, "lambert_conformal_conic"))      projtype = CDI_PROJ_LCC;
      else if (str_is_equal(gmapname, "sinusoidal"))                   projtype = CDI_PROJ_SINU;
      else if (str_is_equal(gmapname, "polar_stereographic"))          projtype = CDI_PROJ_STERE;
      else if (str_is_equal(gmapname, "healpix"))                      projtype = CDI_PROJ_HEALPIX;
      // clang-format on
      gridptr->projtype = projtype;
    }
  }

  return projtype;
}

void
gridVerifyProj(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  int projtype = gridInqProjType(gridID);
  if (projtype == CDI_PROJ_RLL)
  {
    gridaxisSetKey(&gridptr->x, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_grid_latlon][0]);
    gridaxisSetKey(&gridptr->y, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_grid_latlon][1]);
    gridaxisSetKey(&gridptr->x, CDI_KEY_UNITS, "degrees");
    gridaxisSetKey(&gridptr->y, CDI_KEY_UNITS, "degrees");
  }
  else if (projtype == CDI_PROJ_LCC)
  {
    gridaxisSetKey(&gridptr->x, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_projection][0]);
    gridaxisSetKey(&gridptr->y, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_projection][1]);
    gridaxisSetKey(&gridptr->x, CDI_KEY_UNITS, "m");
    gridaxisSetKey(&gridptr->y, CDI_KEY_UNITS, "m");
  }
}

/*
@Function  gridInqType
@Title     Get the type of a Grid

@Prototype int gridInqType(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqType} returns the type of a Grid.

@Result
@func{gridInqType} returns the type of the grid,
one of the set of predefined CDI grid types.
The valid CDI grid types are @func{GRID_GENERIC}, @func{GRID_LONLAT},
@func{GRID_GAUSSIAN}, @func{GRID_PROJECTION}, @func{GRID_SPECTRAL}, @func{GRID_GME},
@func{GRID_CURVILINEAR} and @func{GRID_UNSTRUCTURED}.

@EndFunction
*/
int
gridInqType(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->type;
}

/*
@Function  gridInqSize
@Title     Get the size of a Grid

@Prototype SizeType gridInqSize(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqSize} returns the size of a Grid.

@Result
@func{gridInqSize} returns the number of grid points of a Grid.

@EndFunction
*/
SizeType
gridInqSize(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  size_t size = gridptr->size;
  if (size == 0)
  {
    size_t xsize = gridptr->x.size;
    size_t ysize = gridptr->y.size;
    size = ysize ? xsize * ysize : xsize;
    gridptr->size = size;
  }

  return (SizeType) size;
}

static int
nsp2trunc(size_t nsp)
{
  /*  nsp = (trunc+1)*(trunc+1)              */
  /*      => trunc^2 + 3*trunc - (x-2) = 0   */
  /*                                         */
  /*  with:  y^2 + p*y + q = 0               */
  /*         y = -p/2 +- sqrt((p/2)^2 - q)   */
  /*         p = 3 and q = - (x-2)           */
  int trunc = (int) (sqrt((double) nsp * 4 + 1.) - 3) / 2;
  return trunc;
}

int
gridInqTrunc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->trunc == 0)
  {
    if (gridptr->type == GRID_SPECTRAL) gridptr->trunc = nsp2trunc(gridptr->size);
    /*
    else if      ( gridptr->type == GRID_GAUSSIAN )
      gridptr->trunc = nlat2trunc(gridptr->y.size);
    */
  }

  return gridptr->trunc;
}

void
gridDefTrunc(int gridID, int trunc)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->trunc != trunc)
  {
    gridMark4Update(gridID);
    gridptr->trunc = trunc;
  }
}

/*
@Function  gridDefXsize
@Title     Define the number of values of a X-axis

@Prototype void gridDefXsize(int gridID, SizeType xsize)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  xsize    Number of values of a X-axis.

@Description
The function @func{gridDefXsize} defines the number of values of a X-axis.

@EndFunction
*/
void
gridDefXsize(int gridID, SizeType xsize)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  SizeType gridSize = gridInqSize(gridID);
  if (xsize > gridSize) Error("xsize %lld is greater then gridsize %lld", (long long) xsize, (long long) gridSize);

  int gridType = gridInqType(gridID);
  if (gridType == GRID_UNSTRUCTURED && xsize != gridSize)
    Error("xsize %lld must be equal to gridsize %lld for gridtype: %s", (long long) xsize, (long long) gridSize,
          gridNamePtr(gridType));
  if (gridType == GRID_GAUSSIAN_REDUCED && xsize != 2 && xsize != gridSize)
    Error("xsize %lld must be equal to gridsize %lld for gridtype: %s", (long long) xsize, (long long) gridSize,
          gridNamePtr(gridType));

  if (gridptr->x.size != (size_t) xsize)
  {
    gridMark4Update(gridID);
    gridptr->x.size = (size_t) xsize;
  }

  if (gridType != GRID_UNSTRUCTURED && gridType != GRID_GAUSSIAN_REDUCED && gridType != GRID_PROJECTION)
  {
    size_t axisproduct = gridptr->x.size * gridptr->y.size;
    if (axisproduct > 0 && axisproduct != (size_t) gridSize)
      Error("Inconsistent grid declaration! (xsize=%zu ysize=%zu gridsize=%zu)", gridptr->x.size, gridptr->y.size,
            (size_t) gridSize);
  }
}

void
gridDefDatatype(int gridID, int datatype)
{
  cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, datatype);
}

int
gridInqDatatype(int gridID)
{
  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  return datatype;
}

/*
@Function  gridInqXsize
@Title     Get the number of values of a X-axis

@Prototype SizeType gridInqXsize(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqXsize} returns the number of values of a X-axis.

@Result
@func{gridInqXsize} returns the number of values of a X-axis.

@EndFunction
*/
SizeType
gridInqXsize(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return (SizeType) gridptr->x.size;
}

/*
@Function  gridDefYsize
@Title     Define the number of values of a Y-axis

@Prototype void gridDefYsize(int gridID, SizeType ysize)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  ysize    Number of values of a Y-axis.

@Description
The function @func{gridDefYsize} defines the number of values of a Y-axis.

@EndFunction
*/
void
gridDefYsize(int gridID, SizeType ysize)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  SizeType gridSize = gridInqSize(gridID);

  if (ysize > gridSize) Error("ysize %lld is greater then gridsize %lld", (long long) ysize, (long long) gridSize);

  int gridType = gridInqType(gridID);
  if (gridType == GRID_UNSTRUCTURED && ysize != gridSize)
    Error("ysize %lld must be equal gridsize %lld for gridtype: %s", (long long) ysize, (long long) gridSize,
          gridNamePtr(gridType));

  if (gridptr->y.size != (size_t) ysize)
  {
    gridMark4Update(gridID);
    gridptr->y.size = (size_t) ysize;
  }

  if (gridType != GRID_UNSTRUCTURED && gridType != GRID_GAUSSIAN_REDUCED && gridType != GRID_PROJECTION)
  {
    size_t axisproduct = gridptr->x.size * gridptr->y.size;
    if (axisproduct > 0 && axisproduct != (size_t) gridSize)
      Error("Inconsistent grid declaration! (xsize=%zu ysize=%zu gridsize=%zu)", gridptr->x.size, gridptr->y.size,
            (size_t) gridSize);
  }
}

/*
@Function  gridInqYsize
@Title     Get the number of values of a Y-axis

@Prototype SizeType gridInqYsize(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqYsize} returns the number of values of a Y-axis.

@Result
@func{gridInqYsize} returns the number of values of a Y-axis.

@EndFunction
*/
SizeType
gridInqYsize(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return (SizeType) gridptr->y.size;
}

/*
@Function  gridDefNP
@Title     Define the number of parallels between a pole and the equator

@Prototype void gridDefNP(int gridID, int np)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  np       Number of parallels between a pole and the equator.

@Description
The function @func{gridDefNP} defines the number of parallels between a pole and the equator
of a Gaussian grid.

@EndFunction
*/
void
gridDefNP(int gridID, int np)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->np != np)
  {
    gridMark4Update(gridID);
    gridptr->np = np;
  }
}

/*
@Function  gridInqNP
@Title     Get the number of parallels between a pole and the equator

@Prototype int gridInqNP(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqNP} returns the number of parallels between a pole and the equator
of a Gaussian grid.

@Result
@func{gridInqNP} returns the number of parallels between a pole and the equator.

@EndFunction
*/
int
gridInqNP(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->np;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridDefReducedPoints(int gridID, int reducedPointsSize, const int reducedPoints[])
{
  grid_t *gridptr = grid_to_pointer(gridID);

  gridptr->reducedPoints = (int *) Malloc((size_t) reducedPointsSize * sizeof(int));
  gridptr->reducedPointsSize = reducedPointsSize;
  memcpy(gridptr->reducedPoints, reducedPoints, (size_t) reducedPointsSize * sizeof(int));
  gridMark4Update(gridID);
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridInqReducedPoints(int gridID, int *reducedPoints)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->reducedPoints == 0) Error("undefined pointer!");

  memcpy(reducedPoints, gridptr->reducedPoints, (size_t) gridptr->reducedPointsSize * sizeof(int));
}

static SizeType
gridInqMaskSerialGeneric(grid_t *gridptr, mask_t **internalMask, int *restrict mask)
{
  size_t size = gridptr->size;

  if (CDI_Debug && size == 0) Warning("Size undefined for gridID = %d", gridptr->self);

  const mask_t *restrict mask_src = *internalMask;
  if (mask_src)
  {
    if (mask && size > 0)
      for (size_t i = 0; i < size; ++i) mask[i] = (int) mask_src[i];
  }
  else
    size = 0;

  return (SizeType) size;
}

static SizeType
gridInqMaskSerial(grid_t *gridptr, int *mask)
{
  return gridInqMaskSerialGeneric(gridptr, &gridptr->mask, mask);
}

int
gridInqMask(int gridID, int *mask)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqMask(gridptr, mask);
}

static void
gridDefMaskSerial(grid_t *gridptr, const int *mask)
{
  size_t size = gridptr->size;
  if (size == 0) Error("Size undefined for gridID = %d", gridptr->self);

  if (mask == NULL)
  {
    if (gridptr->mask)
    {
      Free(gridptr->mask);
      gridptr->mask = NULL;
    }
  }
  else
  {
    if (gridptr->mask == NULL)
      gridptr->mask = (mask_t *) Malloc(size * sizeof(mask_t));
    else if (CDI_Debug)
      Warning("grid mask already defined!");

    for (size_t i = 0; i < size; ++i) gridptr->mask[i] = (mask_t) (mask[i] != 0);
  }
}

void
gridDefMask(int gridID, const int *mask)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defMask(gridptr, mask);
  gridMark4Update(gridID);
}

static int
gridInqMaskGMESerial(grid_t *gridptr, int *mask_gme)
{
  return (int) gridInqMaskSerialGeneric(gridptr, &gridptr->mask_gme, mask_gme);
}

int
gridInqMaskGME(int gridID, int *mask)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqMaskGME(gridptr, mask);
}

static void
gridDefMaskGMESerial(grid_t *gridptr, const int *mask)
{
  size_t size = gridptr->size;
  if (size == 0) Error("Size undefined for gridID = %d", gridptr->self);

  if (gridptr->mask_gme == NULL)
    gridptr->mask_gme = (mask_t *) Malloc(size * sizeof(mask_t));
  else if (CDI_Debug)
    Warning("mask already defined!");

  for (size_t i = 0; i < size; ++i) gridptr->mask_gme[i] = (mask_t) (mask[i] != 0);
}

void
gridDefMaskGME(int gridID, const int *mask)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defMaskGME(gridptr, mask);
  gridMark4Update(gridID);
}

static void
copy_darray(size_t n, const double *restrict in, double *restrict out)
{
#ifdef _OPENMP
#pragma omp parallel for if (n > 99999) default(shared) schedule(static)
#endif
  for (size_t i = 0; i < n; ++i) out[i] = in[i];
}

static SizeType
gridInqXValsSerial(grid_t *gridptr, double *xvals)
{
  size_t size = grid_is_irregular(gridptr->type) ? gridptr->size : gridptr->x.size;
  if (CDI_Debug && size == 0) Warning("size undefined for gridID = %d", gridptr->self);

  if (gridptr->x.vals)
  {
    if (size && xvals) copy_darray(size, gridptr->vtable->inqXValsPtr(gridptr), xvals);
  }
  else
    size = 0;

  return (SizeType) size;
}

static SizeType
gridInqXValsPartSerial(grid_t *gridptr, int start, SizeType length, double *xvals)
{
  size_t size = grid_is_irregular(gridptr->type) ? gridptr->size : gridptr->x.size;
  if (CDI_Debug && size == 0) Warning("size undefined for gridID = %d", gridptr->self);

  if (gridptr->x.vals)
  {
    if (size && xvals && (size_t) length <= size)
    {
      const double *gridptr_xvals = gridptr->vtable->inqXValsPtr(gridptr);
      memcpy(xvals, gridptr_xvals + start, (size_t) length * sizeof(double));
    }
  }
  else
    size = 0;

  return (SizeType) size;
}

#ifndef USE_MPI
static SizeType
gridInqXCvalsSerial(grid_t *gridptr, char **xcvals)
{
  if (gridptr->type != GRID_CHARXY) Error("Function only valid for grid type 'GRID_CHARXY'.");

  size_t size = gridptr->x.size;
  size_t maxclength = 0;

  const char **gridptr_xcvals = gridptr->vtable->inqXCvalsPtr(gridptr);
  if (gridptr_xcvals && size && xcvals)
  {
    maxclength = (size_t) gridptr->x.clength;
    for (size_t i = 0; i < size; i++) memcpy(xcvals[i], gridptr_xcvals[i], maxclength * sizeof(char));
  }

  return (SizeType) maxclength;
}

static int
gridInqXIscSerial(grid_t *gridptr)
{
  /*
  if ( gridptr->type != GRID_CHARXY )
    Error("Axis type is 'char' but grid is not type 'GRID_CHARXY'.");
  */
  return gridptr->x.clength;
}
#endif

/*
@Function  gridInqXvals
@Title     Get all values of a X-axis

@Prototype SizeType gridInqXvals(int gridID, double *xvals)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  xvals    Pointer to the location into which the X-values are read.
                    The caller must allocate space for the returned values.

@Description
The function @func{gridInqXvals} returns all values of the X-axis.

@Result
Upon successful completion @func{gridInqXvals} returns the number of values and
the values are stored in @func{xvals}.
Otherwise, 0 is returned and @func{xvals} is empty.

@EndFunction
*/
SizeType
gridInqXvals(int gridID, double *xvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXVals(gridptr, xvals);
}

SizeType
gridInqXvalsPart(int gridID, int start, SizeType length, double *xvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXValsPart(gridptr, start, length, xvals);
}

SizeType
gridInqXCvals(int gridID, char **xcvals)
{
#ifndef USE_MPI
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXCvals(gridptr, xcvals);
#else
  (void) gridID;
  (void) xcvals;
  return 0;
#endif
}

int
gridInqXIsc(int gridID)
{
#ifndef USE_MPI
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXIsc(gridptr);
#else
  (void) gridID;
  return 0;
#endif
}

static void
gridDefXValsSerial(grid_t *gridptr, const double *xvals)
{
  size_t size = grid_is_irregular(gridptr->type) ? gridptr->size : gridptr->x.size;
  if (size == 0) Error("Size undefined for gridID = %d", gridptr->self);

  if (gridptr->x.vals && CDI_Debug) Warning("values already defined!");
  gridptr->x.vals = (double *) Realloc(gridptr->x.vals, size * sizeof(double));
  copy_darray(size, xvals, gridptr->x.vals);
}

#ifndef USE_MPI
static SizeType
gridInqYCvalsSerial(grid_t *gridptr, char **ycvals)
{
  if (gridptr->type != GRID_CHARXY) Error("Function only valid for grid type 'GRID_CHARXY'.");

  size_t size = gridptr->y.size;
  size_t maxclength = 0;

  const char **gridptr_ycvals = gridptr->vtable->inqYCvalsPtr(gridptr);
  if (gridptr_ycvals && size && ycvals)
  {
    maxclength = (size_t) gridptr->y.clength;
    for (size_t i = 0; i < size; i++) memcpy(ycvals[i], gridptr_ycvals[i], maxclength);
  }

  return (SizeType) maxclength;
}

static int
gridInqYIscSerial(grid_t *gridptr)
{
  // if ( gridptr->type != GRID_CHARXY ) Error("Axis type is 'char' but grid is not type 'GRID_CHARXY'.");
  return gridptr->y.clength;
}
#endif

/*
@Function  gridDefXvals
@Title     Define the values of a X-axis

@Prototype void gridDefXvals(int gridID, const double *xvals)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  xvals    X-values of the grid.

@Description
The function @func{gridDefXvals} defines all values of the X-axis.

@EndFunction
*/
void
gridDefXvals(int gridID, const double *xvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defXVals(gridptr, xvals);
  gridMark4Update(gridID);
}

static SizeType
gridInqYValsSerial(grid_t *gridptr, double *yvals)
{
  size_t size = grid_is_irregular(gridptr->type) ? gridptr->size : gridptr->y.size;
  if (CDI_Debug && size == 0) Warning("size undefined for gridID = %d!", gridptr->self);

  if (gridptr->y.vals)
  {
    if (size && yvals) copy_darray(size, gridptr->vtable->inqYValsPtr(gridptr), yvals);
  }
  else
    size = 0;

  return (SizeType) size;
}

static SizeType
gridInqYValsPartSerial(grid_t *gridptr, int start, SizeType length, double *yvals)
{
  size_t size = grid_is_irregular(gridptr->type) ? gridptr->size : gridptr->y.size;
  if (CDI_Debug && size == 0) Warning("size undefined for gridID = %d!", gridptr->self);

  if (gridptr->y.vals)
  {
    if (size && yvals && (size_t) length <= size)
    {
      const double *gridptr_yvals = gridptr->vtable->inqYValsPtr(gridptr);
      memcpy(yvals, gridptr_yvals + start, (size_t) length * sizeof(double));
    }
  }
  else
    size = 0;

  return (SizeType) size;
}

/*
@Function  gridInqYvals
@Title     Get all values of a Y-axis

@Prototype SizeType gridInqYvals(int gridID, double *yvals)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  yvals    Pointer to the location into which the Y-values are read.
                    The caller must allocate space for the returned values.

@Description
The function @func{gridInqYvals} returns all values of the Y-axis.

@Result
Upon successful completion @func{gridInqYvals} returns the number of values and
the values are stored in @func{yvals}.
Otherwise, 0 is returned and @func{yvals} is empty.

@EndFunction
*/
SizeType
gridInqYvals(int gridID, double *yvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYVals(gridptr, yvals);
}

SizeType
gridInqYvalsPart(int gridID, int start, SizeType size, double *yvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYValsPart(gridptr, start, size, yvals);
}

SizeType
gridInqYCvals(int gridID, char **ycvals)
{
#ifndef USE_MPI
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYCvals(gridptr, ycvals);
#else
  (void) gridID;
  (void) ycvals;
  return 0;
#endif
}

int
gridInqYIsc(int gridID)
{
#ifndef USE_MPI
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYIsc(gridptr);
#else
  (void) gridID;
  return 0;
#endif
}

static void
gridDefYValsSerial(grid_t *gridptr, const double *yvals)
{
  int gridtype = gridptr->type;
  size_t size = grid_is_irregular(gridtype) ? gridptr->size : gridptr->y.size;

  if (size == 0) Error("Size undefined for gridID = %d!", gridptr->self);

  if (gridptr->y.vals && CDI_Debug) Warning("Values already defined!");

  gridptr->y.vals = (double *) Realloc(gridptr->y.vals, size * sizeof(double));
  copy_darray(size, yvals, gridptr->y.vals);
}

/*
@Function  gridDefYvals
@Title     Define the values of a Y-axis

@Prototype void gridDefYvals(int gridID, const double *yvals)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  yvals    Y-values of the grid.

@Description
The function @func{gridDefYvals} defines all values of the Y-axis.

@EndFunction
*/
void
gridDefYvals(int gridID, const double *yvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defYVals(gridptr, yvals);
  gridMark4Update(gridID);
}

static double
gridInqXValSerial(grid_t *gridptr, SizeType index)
{
  return gridptr->x.vals ? gridptr->x.vals[index] : 0;
}

double
gridInqXval(int gridID, SizeType index)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXVal(gridptr, index);
}

static double
gridInqYValSerial(grid_t *gridptr, SizeType index)
{
  return gridptr->y.vals ? gridptr->y.vals[index] : 0;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
double
gridInqYval(int gridID, SizeType index)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYVal(gridptr, index);
}

static double
grid_calc_increment(size_t size, const double *vals)
{
  if (size > 1)
  {
    double inc = (vals[size - 1] - vals[0]) / (double) (size - 1);
    double abs_inc = fabs(inc);
    for (size_t i = 1; i < size; ++i)
      if (fabs(fabs(vals[i - 1] - vals[i]) - abs_inc) > 0.01 * abs_inc)
      {
        inc = 0.0;
        break;
      }

    return inc;
  }

  return 0.0;
}

static double
grid_calc_increment_in_meter(size_t size, const double *vals)
{
  if (size > 1)
  {
    double inc = (vals[size - 1] - vals[0]) / (double) (size - 1);
    return round(fabs(inc));
  }

  return 0.0;
}

static double
gridInqXIncBase(grid_t *gridptr)
{
  if (fabs(gridptr->x.inc) <= 0 && gridptr->x.vals)
  {
    size_t xsize = gridptr->x.size;
    if (xsize > 1)
    {
      const double *xvals = gridptr->vtable->inqXValsPtr(gridptr);
      gridptr->x.inc = grid_calc_increment(xsize, xvals);
    }
  }

  return gridptr->x.inc;
}

double
gridInqXincInMeter(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *xvals = gridptr->vtable->inqXValsPtr(gridptr);

  if (fabs(gridptr->x.inc) <= 0 && xvals)
  {
    size_t xsize = gridptr->x.size;
    if (xsize > 1) gridptr->x.inc = grid_calc_increment_in_meter(xsize, xvals);
  }

  return gridptr->x.inc;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
double
gridInqXinc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXInc(gridptr);
}

static double
gridInqYIncBase(grid_t *gridptr)
{
  if (fabs(gridptr->y.inc) <= 0 && gridptr->y.vals)
  {
    size_t ysize = gridptr->y.size;
    if (ysize > 1)
    {
      const double *yvals = gridptr->vtable->inqYValsPtr(gridptr);
      gridptr->y.inc = grid_calc_increment(ysize, yvals);
    }
  }

  return gridptr->y.inc;
}

double
gridInqYincInMeter(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *yvals = gridptr->vtable->inqYValsPtr(gridptr);

  if (fabs(gridptr->y.inc) <= 0 && yvals)
  {
    size_t ysize = gridptr->y.size;
    if (ysize > 1) gridptr->y.inc = grid_calc_increment_in_meter(ysize, yvals);
  }

  return gridptr->y.inc;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
double
gridInqYinc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYInc(gridptr);
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridInqParamRLL(int gridID, double *xpole, double *ypole, double *angle)
{
  *xpole = 0;
  *ypole = 0;
  *angle = 0;

  static const char projection[] = "rotated_latitude_longitude";
  char name[CDI_MAX_NAME + 1];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, name, &length);
  if (name[0] && str_is_equal(name, projection))
  {
    int atttype, attlen;

    int natts, nfound = 0;
    cdiInqNatts(gridID, CDI_GLOBAL, &natts);

    for (int iatt = 0; iatt < natts; ++iatt)
    {
      cdiInqAtt(gridID, CDI_GLOBAL, iatt, name, &atttype, &attlen);
      if (attlen == 1)
      {
        double *attflt;
        // clang-format off
        if      (str_is_equal(name, "grid_north_pole_longitude")) attflt = xpole;
        else if (str_is_equal(name, "grid_north_pole_latitude") ) attflt = ypole;
        else if (str_is_equal(name, "north_pole_grid_longitude")) attflt = angle;
        else continue;
        // clang-format on
        bool valid = cdiInqAttConvertedToFloat(gridID, atttype, name, attlen, attflt);
        if ((nfound += valid) == 3) return;
      }
    }
  }
  else
    Warning("%s mapping parameter missing!", projection);
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridDefParamRLL(int gridID, double xpole, double ypole, double angle)
{
  cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_UNITS, "degrees");
  cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_UNITS, "degrees");

  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, "rotated_pole");

  const char *gmapname = "rotated_latitude_longitude";
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname);
  cdiDefAttTxt(gridID, CDI_GLOBAL, "grid_mapping_name", (int) (strlen(gmapname)), gmapname);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "grid_north_pole_longitude", CDI_DATATYPE_FLT64, 1, &xpole);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "grid_north_pole_latitude", CDI_DATATYPE_FLT64, 1, &ypole);
  if (IS_NOT_EQUAL(angle, 0)) cdiDefAttFlt(gridID, CDI_GLOBAL, "north_pole_grid_longitude", CDI_DATATYPE_FLT64, 1, &angle);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->projtype = CDI_PROJ_RLL;

  gridVerifyProj(gridID);
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridInqParamGME(int gridID, int *nd, int *ni, int *ni2, int *ni3)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  *nd = gridptr->gme.nd;
  *ni = gridptr->gme.ni;
  *ni2 = gridptr->gme.ni2;
  *ni3 = gridptr->gme.ni3;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridDefParamGME(int gridID, int nd, int ni, int ni2, int ni3)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->gme.nd != nd)
  {
    gridptr->gme.nd = nd;
    gridptr->gme.ni = ni;
    gridptr->gme.ni2 = ni2;
    gridptr->gme.ni3 = ni3;
    gridMark4Update(gridID);
  }
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void
gridChangeType(int gridID, int gridtype)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (CDI_Debug) Message("Changed grid type from %s to %s", gridNamePtr(gridptr->type), gridNamePtr(gridtype));

  if (gridptr->type != gridtype)
  {
    gridptr->type = gridtype;
    gridMark4Update(gridID);
  }
}

static void
grid_check_cyclic(grid_t *gridptr)
{
  gridptr->isCyclic = 0;
  enum
  {
    numVertices = 4
  };
  size_t xsize = gridptr->x.size, ysize = gridptr->y.size;
  const double *xvals = gridptr->vtable->inqXValsPtr(gridptr), *yvals = gridptr->vtable->inqYValsPtr(gridptr),
               (*xbounds)[numVertices] = (const double(*)[numVertices]) gridptr->vtable->inqXBoundsPtr(gridptr);

  if (gridptr->type == GRID_GAUSSIAN || gridptr->type == GRID_LONLAT)
  {
    if (xvals && xsize > 1)
    {
      double xval1 = xvals[0];
      double xval2 = xvals[1];
      double xvaln = xvals[xsize - 1];
      if (xval2 < xval1) xval2 += 360;
      if (xvaln < xval1) xvaln += 360;

      if (IS_NOT_EQUAL(xval1, xvaln))
      {
        double xinc = xval2 - xval1;
        if (IS_EQUAL(xinc, 0)) xinc = (xvaln - xval1) / (double) (xsize - 1);

        const double x0 = xvaln + xinc - 360;

        if (fabs(x0 - xval1) < 0.01 * xinc) gridptr->isCyclic = 1;
      }
    }
  }
  else if (gridptr->type == GRID_CURVILINEAR)
  {
    bool lcheck = true;
    if (yvals && xvals)
    {
      if ((fabs(yvals[0] - yvals[xsize - 1]) > fabs(yvals[0] - yvals[xsize * ysize - xsize]))
          && (fabs(yvals[xsize * ysize - xsize] - yvals[xsize * ysize - 1]) > fabs(yvals[xsize - 1] - yvals[xsize * ysize - 1])))
        lcheck = false;
    }
    else
      lcheck = false;

    if (lcheck && xvals && xsize > 1)
    {
      size_t nc = 0;
      for (size_t j = 0; j < ysize; ++j)
      {
        size_t i1 = j * xsize, i2 = j * xsize + 1, in = j * xsize + (xsize - 1);
        double val1 = xvals[i1], val2 = xvals[i2], valn = xvals[in];
        double xinc = fabs(val2 - val1);

        if (val1 < 1 && valn > 300) val1 += 360;
        if (valn < 1 && val1 > 300) valn += 360;
        if (val1 < -179 && valn > 120) val1 += 360;
        if (valn < -179 && val1 > 120) valn += 360;
        if (fabs(valn - val1) > 180) val1 += 360;

        double x0 = valn + copysign(xinc, val1 - valn);

        nc += fabs(x0 - val1) < 0.5 * xinc;
      }
      gridptr->isCyclic = nc > ysize / 2;
    }

    if (lcheck && xbounds && xsize > 1)
    {
      bool isCyclic = true;
      for (size_t j = 0; j < ysize; ++j)
      {
        size_t i1 = j * xsize, i2 = j * xsize + (xsize - 1);
        for (size_t k1 = 0; k1 < numVertices; ++k1)
        {
          double val1 = xbounds[i1][k1];
          for (size_t k2 = 0; k2 < numVertices; ++k2)
          {
            double val2 = xbounds[i2][k2];

            if (val1 < 1 && val2 > 300) val1 += 360;
            if (val2 < 1 && val1 > 300) val2 += 360;
            if (val1 < -179 && val2 > 120) val1 += 360;
            if (val2 < -179 && val1 > 120) val2 += 360;
            if (fabs(val2 - val1) > 180) val1 += 360;

            if (fabs(val1 - val2) < 0.001) goto foundCloseVertices;
          }
        }
        // all vertices more than 0.001 degrees apart
        isCyclic = false;
        break;
      foundCloseVertices:;
      }
      gridptr->isCyclic = isCyclic;
    }
  }
}

int
gridIsCyclic(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  if (gridptr->isCyclic == CDI_UNDEFID) grid_check_cyclic(gridptr);
  return gridptr->isCyclic;
}

static bool
compareXYvals(grid_t *gridRef, grid_t *gridTest)
{
  bool differ = false;
  int gridtype = gridTest->type;

  size_t xsizeTest = grid_is_irregular(gridtype) ? gridTest->size : gridTest->x.size;
  size_t xsizeRef = (size_t) gridRef->vtable->inqXVals(gridRef, NULL);
  if (xsizeTest != xsizeRef) return true;

  if (xsizeTest > 0)
  {
    const double *xvalsRef = gridRef->vtable->inqXValsPtr(gridRef);
    const double *xvalsTest = gridTest->vtable->inqXValsPtr(gridTest);
    if (!xvalsTest) return true;

    for (size_t i = 0; i < xsizeTest; ++i)
      if (fabs(xvalsTest[i] - xvalsRef[i]) > 1.e-10) return true;
  }

  size_t ysizeTest = grid_is_irregular(gridtype) ? gridTest->size : gridTest->y.size;
  size_t ysizeRef = (size_t) gridRef->vtable->inqYVals(gridRef, NULL);
  if (ysizeTest != ysizeRef) return true;

  if (ysizeTest > 0)
  {
    const double *yvalsRef = gridRef->vtable->inqYValsPtr(gridRef);
    const double *yvalsTest = gridTest->vtable->inqYValsPtr(gridTest);
    if (!yvalsTest) return true;

    for (size_t i = 0; i < ysizeTest; ++i)
      if (fabs(yvalsTest[i] - yvalsRef[i]) > 1.e-10) return true;
  }

  return differ;
}

static bool
compareXYvals2(grid_t *gridRef, grid_t *gridTest)
{
  size_t gridsize = gridTest->size;
  bool differ = ((gridTest->x.vals == NULL) ^ (gridRef->x.vals == NULL)) || ((gridTest->y.vals == NULL) ^ (gridRef->y.vals == NULL))
                || ((gridTest->x.bounds == NULL) ^ (gridRef->x.bounds == NULL))
                || ((gridTest->y.bounds == NULL) ^ (gridRef->y.bounds == NULL));

  typedef double (*inqVal)(grid_t * grid, SizeType index);
  inqVal inqXValRef = gridRef->vtable->inqXVal, inqYValRef = gridRef->vtable->inqYVal, inqXValTest = gridTest->vtable->inqXVal,
         inqYValTest = gridTest->vtable->inqYVal;

  if (!differ && gridTest->x.vals)
    differ = fabs(inqXValTest(gridTest, 0) - inqXValRef(gridRef, 0)) > 1.e-9
             || fabs(inqXValTest(gridTest, (SizeType) (gridsize - 1)) - inqXValRef(gridRef, (SizeType) (gridsize - 1))) > 1.e-9;

  if (!differ && gridTest->y.vals)
    differ = fabs(inqYValTest(gridTest, 0) - inqYValRef(gridRef, 0)) > 1.e-9
             || fabs(inqYValTest(gridTest, (SizeType) gridsize - 1) - inqYValRef(gridRef, (SizeType) gridsize - 1)) > 1.e-9;

  return differ;
}

static bool
compare_bounds(const grid_t *grid, const grid_t *gridRef)
{
  bool differ = false;

  if ((grid->x.bounds && !gridRef->x.bounds) || (!grid->x.bounds && gridRef->x.bounds) || (grid->y.bounds && !gridRef->y.bounds)
      || (!grid->y.bounds && gridRef->y.bounds))
    differ = true;

  return differ;
}

static bool
compare_lonlat(int gridID, const grid_t *grid, const grid_t *gridRef)
{
  bool differ = false;
  /*
    printf("gridID      %d\n", gridID);
    printf("grid.xdef   %d\n", grid->x.flag);
    printf("grid.ydef   %d\n", grid->y.flag);
    printf("grid.xsize  %zu\n", grid->x.size);
    printf("grid.ysize  %zu\n", grid->y.size);
    printf("grid.xfirst %f\n", grid->x.first);
    printf("grid.yfirst %f\n", grid->y.first);
    printf("grid.xfirst %f\n", gridInqXval(gridID, 0));
    printf("grid.yfirst %f\n", gridInqYval(gridID, 0));
    printf("grid.xinc   %f\n", grid->x.inc);
    printf("grid.yinc   %f\n", grid->y.inc);
    printf("grid.xinc   %f\n", gridInqXinc(gridID));
    printf("grid.yinc   %f\n", gridInqYinc(gridID));
  */
  if (grid->x.size == gridRef->x.size && grid->y.size == gridRef->y.size)
  {
    if (grid->x.flag == 2 && grid->y.flag == 2)
    {
      if (!(IS_EQUAL(grid->x.first, 0) && IS_EQUAL(grid->x.last, 0) && IS_EQUAL(grid->x.inc, 0))
          && !(IS_EQUAL(grid->y.first, 0) && IS_EQUAL(grid->y.last, 0) && IS_EQUAL(grid->y.inc, 0))
          && IS_NOT_EQUAL(grid->x.first, grid->x.last) && IS_NOT_EQUAL(grid->y.first, grid->y.last))
      {
        if (IS_NOT_EQUAL(grid->x.first, gridInqXval(gridID, 0)) || IS_NOT_EQUAL(grid->y.first, gridInqYval(gridID, 0)))
        {
          differ = true;
        }
        if (!differ && fabs(grid->x.inc) > 0 && fabs(fabs(grid->x.inc) - fabs(gridRef->x.inc)) > fabs(grid->x.inc / 1000))
        {
          differ = true;
        }
        if (!differ && fabs(grid->y.inc) > 0 && fabs(fabs(grid->y.inc) - fabs(gridRef->y.inc)) > fabs(grid->y.inc / 1000))
        {
          differ = true;
        }
      }
    }
    else if (grid->x.vals && grid->y.vals)
      differ = gridRef->vtable->compareXYFull((grid_t *) gridRef, (grid_t *) grid);

    if (!differ) differ = compare_bounds(grid, gridRef);
  }
  else
    differ = true;

  return differ;
}

static bool
compare_projection(int gridID, const grid_t *grid, const grid_t *gridRef)
{
  bool differ = compare_lonlat(gridID, grid, gridRef);

  if (!differ)
  {
    // printf(">%s< >%s<\n", cdiInqVarKeyString(&grid->keys, CDI_KEY_GRIDMAP_VARNAME), cdiInqVarKeyString(&gridRef->keys,
    // CDI_KEY_GRIDMAP_VARNAME)); printf(">%s< >%s<\n", cdiInqVarKeyString(&grid->keys, CDI_KEY_GRIDMAP_NAME),
    // cdiInqVarKeyString(&gridRef->keys, CDI_KEY_GRIDMAP_NAME));
    // if (!str_is_equal(cdiInqVarKeyString(&grid->keys, CDI_KEY_GRIDMAP_VARNAME), cdiInqVarKeyString(&gridRef->keys,
    // CDI_KEY_GRIDMAP_VARNAME))) return true; if (!str_is_equal(cdiInqVarKeyString(&grid->keys, CDI_KEY_GRIDMAP_NAME),
    // cdiInqVarKeyString(&gridRef->keys, CDI_KEY_GRIDMAP_NAME))) return true;
  }

  return differ;
}

static bool
compare_generic(const grid_t *grid, const grid_t *gridRef)
{
  bool differ = false;

  if (grid->x.size == gridRef->x.size && grid->y.size == gridRef->y.size)
  {
    if (grid->x.flag == 1 && grid->y.flag == 1 && grid->x.vals && grid->y.vals)
      differ = gridRef->vtable->compareXYFull((grid_t *) gridRef, (grid_t *) grid);
  }
  else if ((grid->y.size == 0 || grid->y.size == 1) && grid->x.size == gridRef->x.size * gridRef->y.size) {}
  else
    differ = true;

  return differ;
}

static bool
compare_gaussian(int gridID, const grid_t *grid, const grid_t *gridRef)
{
  const double cmp_eps = 0.0015;
  bool differ = false;

  if (grid->x.size == gridRef->x.size && grid->y.size == gridRef->y.size)
  {
    if (grid->x.flag == 2 && grid->y.flag == 2)
    {
      if (!(IS_EQUAL(grid->x.first, 0) && IS_EQUAL(grid->x.last, 0) && IS_EQUAL(grid->x.inc, 0))
          && !(IS_EQUAL(grid->y.first, 0) && IS_EQUAL(grid->y.last, 0)))
        if (fabs(grid->x.first - gridInqXval(gridID, 0)) > cmp_eps || fabs(grid->y.first - gridInqYval(gridID, 0)) > cmp_eps
            || (fabs(grid->x.inc) > 0 && fabs(fabs(grid->x.inc) - fabs(gridRef->x.inc)) > fabs(grid->x.inc / 1000)))
        {
          differ = true;
        }
    }
    else if (grid->x.vals && grid->y.vals)
      differ = gridRef->vtable->compareXYFull((grid_t *) gridRef, (grid_t *) grid);

    if (!differ) differ = compare_bounds(grid, gridRef);
  }
  else
    differ = true;

  return differ;
}

static bool
compare_curvilinear(const grid_t *grid, const grid_t *gridRef)
{
  bool differ = false;

  /*
    printf("gridID      %d\n", gridID);
    printf("grid.xsize  %d\n", grid->x.size);
    printf("grid.ysize  %d\n", grid->y.size);
    printf("grid.xfirst %f\n", grid->x.vals[0]);
    printf("grid.yfirst %f\n", grid->y.vals[0]);
    printf("grid xfirst %f\n", gridInqXval(gridID, 0));
    printf("grid yfirst %f\n", gridInqYval(gridID, 0));
    printf("grid.xlast  %f\n", grid->x.vals[grid->size-1]);
    printf("grid.ylast  %f\n", grid->y.vals[grid->size-1]);
    printf("grid xlast  %f\n", gridInqXval(gridID, grid->size-1));
    printf("grid ylast  %f\n", gridInqYval(gridID, grid->size-1));
    printf("grid.nv     %d\n", grid->nvertex);
    printf("grid nv     %d\n", gridInqNvertex(gridID));
  */
  if (grid->x.size == gridRef->x.size && grid->y.size == gridRef->y.size)
    differ = gridRef->vtable->compareXYAO((grid_t *) gridRef, (grid_t *) grid);

  return differ;
}

static bool
compare_unstructured(const grid_t *grid, const grid_t *gridRef, bool compareCoord)
{
  bool differ = false;

  unsigned char uuid1[CDI_UUID_SIZE] = { 0 };
  unsigned char uuid2[CDI_UUID_SIZE] = { 0 };
  int length = CDI_UUID_SIZE;
  cdiInqVarKeyBytes(&gridRef->keys, CDI_KEY_UUID, uuid1, &length);
  length = CDI_UUID_SIZE;
  cdiInqVarKeyBytes(&grid->keys, CDI_KEY_UUID, uuid2, &length);
  differ = ((!cdiUUIDIsNull(uuid1) || !cdiUUIDIsNull(uuid2)) && memcmp(uuid1, uuid2, CDI_UUID_SIZE));
  if (!differ)
  {
    int numberA = cdiInqVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDUSED);
    int numberB = cdiInqVarKeyInt(&gridRef->keys, CDI_KEY_NUMBEROFGRIDUSED);
    int positionA = cdiInqVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE);
    int positionB = cdiInqVarKeyInt(&gridRef->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE);
    if (compareCoord)
    {
      differ = (grid->nvertex != gridRef->nvertex || (numberA > 0 && positionA != positionB)
                || gridRef->vtable->compareXYFull((grid_t *) gridRef, (grid_t *) grid));
    }
    else
    {
      if (((grid->x.vals == NULL) ^ (gridRef->x.vals == NULL)) && ((grid->y.vals == NULL) ^ (gridRef->y.vals == NULL)))
      {
        int nvertexA = grid->nvertex, nvertexB = gridRef->nvertex;
        differ = (nvertexA && nvertexB && (nvertexA != nvertexB))
                 || ((numberA && numberB && (numberA != numberB)) || (numberA && numberB && positionA != positionB));
      }
      else
      {
        differ = (grid->nvertex != gridRef->nvertex || numberA != numberB || (numberA > 0 && positionA != positionB)
                  || gridRef->vtable->compareXYAO((grid_t *) gridRef, (grid_t *) grid));
      }
    }
  }

  return differ;
}

static bool
gridCompare(int gridID, const grid_t *grid, bool compareCoord)
{
  bool differ = true;
  const grid_t *gridRef = grid_to_pointer(gridID);

  if (grid->type == gridRef->type || grid->type == GRID_GENERIC)
  {
    if (grid->size == gridRef->size)
    {
      differ = false;
      if (grid->type == GRID_LONLAT) { differ = compare_lonlat(gridID, grid, gridRef); }
      else if (grid->type == GRID_PROJECTION) { differ = compare_projection(gridID, grid, gridRef); }
      else if (grid->type == GRID_GENERIC) { differ = compare_generic(grid, gridRef); }
      else if (grid->type == GRID_GAUSSIAN) { differ = compare_gaussian(gridID, grid, gridRef); }
      else if (grid->type == GRID_CURVILINEAR) { differ = compare_curvilinear(grid, gridRef); }
      else if (grid->type == GRID_UNSTRUCTURED) { differ = compare_unstructured(grid, gridRef, compareCoord); }
    }
  }

  int scanningModeA = cdiInqVarKeyInt(&grid->keys, CDI_KEY_SCANNINGMODE);
  int scanningModeB = cdiInqVarKeyInt(&gridRef->keys, CDI_KEY_SCANNINGMODE);
  if (scanningModeA != scanningModeB)
  {
    // often grid definition may differ in UV-relativeToGrid
    differ = true;
#ifdef HIRLAM_EXTENSIONS
    if (cdiDebugExt >= 200)
      printf("gridCompare(gridID=%d): Differs: scanningModeA [%d] != scanningModeB(gridID) [%d]\n", gridID, scanningModeA,
             scanningModeB);
#endif  // HIRLAM_EXTENSIONS
  }

  return differ;
}

int
cmp_key_int(const cdi_keys_t *keysp1, const cdi_keys_t *keysp2, int key)
{
  int v1 = cdiInqVarKeyInt(keysp1, key);
  int v2 = cdiInqVarKeyInt(keysp2, key);
  return (v1 != v2);
}

int
gridCompareP(void *gridptr1, void *gridptr2)
{
  grid_t *g1 = (grid_t *) gridptr1;
  grid_t *g2 = (grid_t *) gridptr2;
  enum
  {
    equal = 0,
    differ = -1
  };

  xassert(g1);
  xassert(g2);

  if (cdiInqVarKeyInt(&g1->keys, CDI_KEY_DATATYPE) != cdiInqVarKeyInt(&g2->keys, CDI_KEY_DATATYPE)) return differ;
  if (g1->type != g2->type) return differ;
  if (g1->isCyclic != g2->isCyclic) return differ;
  if (g1->x.flag != g2->x.flag) return differ;
  if (g1->y.flag != g2->y.flag) return differ;
  if (g1->gme.nd != g2->gme.nd) return differ;
  if (g1->gme.ni != g2->gme.ni) return differ;
  if (g1->gme.ni2 != g2->gme.ni2) return differ;
  if (g1->gme.ni3 != g2->gme.ni3) return differ;
  if (cmp_key_int(&g1->keys, &g2->keys, CDI_KEY_NUMBEROFGRIDUSED)) return differ;
  if (cmp_key_int(&g1->keys, &g2->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE)) return differ;
  if (g1->trunc != g2->trunc) return differ;
  if (g1->nvertex != g2->nvertex) return differ;
  if (g1->reducedPointsSize != g2->reducedPointsSize) return differ;
  if (g1->size != g2->size) return differ;
  if (g1->x.size != g2->x.size) return differ;
  if (g1->y.size != g2->y.size) return differ;
  if (g1->lcomplex != g2->lcomplex) return differ;

  if (IS_NOT_EQUAL(g1->x.first, g2->x.first)) return differ;
  if (IS_NOT_EQUAL(g1->y.first, g2->y.first)) return differ;
  if (IS_NOT_EQUAL(g1->x.last, g2->x.last)) return differ;
  if (IS_NOT_EQUAL(g1->y.last, g2->y.last)) return differ;
  if (IS_NOT_EQUAL(g1->x.inc, g2->x.inc)) return differ;
  if (IS_NOT_EQUAL(g1->y.inc, g2->y.inc)) return differ;
  if (cmp_key_int(&g1->keys, &g2->keys, CDI_KEY_SCANNINGMODE)) return differ;

  bool isIrregular = grid_is_irregular(g1->type);
  {
    const double *restrict g1_xvals = g1->vtable->inqXValsPtr(g1), *restrict g2_xvals = g2->vtable->inqXValsPtr(g2);
    if ((g1_xvals != NULL) ^ (g2_xvals != NULL)) return differ;
    if (g1_xvals)
    {
      size_t size = isIrregular ? g1->size : g1->x.size;
      xassert(size);
      for (size_t i = 0; i < size; i++)
        if (IS_NOT_EQUAL(g1_xvals[i], g2_xvals[i])) return differ;
    }
  }

  {
    const double *restrict g1_yvals = g1->vtable->inqYValsPtr(g1), *restrict g2_yvals = g2->vtable->inqYValsPtr(g2);
    if ((g1_yvals != NULL) ^ (g2_yvals != NULL)) return differ;
    if (g1_yvals)
    {
      size_t size = isIrregular ? g1->size : g1->y.size;
      xassert(size);
      for (size_t i = 0; i < size; i++)
        if (IS_NOT_EQUAL(g1_yvals[i], g2_yvals[i])) return differ;
    }
  }

  {
    const double *restrict g1_area = g1->vtable->inqAreaPtr(g1), *restrict g2_area = g2->vtable->inqAreaPtr(g2);
    if ((g1_area != NULL) ^ (g2_area != NULL)) return differ;
    if (g1_area)
    {
      size_t size = g1->size;
      xassert(size);

      for (size_t i = 0; i < size; i++)
        if (IS_NOT_EQUAL(g1_area[i], g2_area[i])) return differ;
    }
  }

  {
    const double *restrict g1_xbounds = g1->vtable->inqXBoundsPtr(g1), *restrict g2_xbounds = g2->vtable->inqXBoundsPtr(g2);
    if ((g1_xbounds != NULL) ^ (g2_xbounds != NULL)) return differ;
    if (g1_xbounds)
    {
      xassert(g1->nvertex);
      size_t size = (size_t) g1->nvertex * (isIrregular ? g1->size : g1->x.size);
      xassert(size);

      for (size_t i = 0; i < size; i++)
        if (IS_NOT_EQUAL(g1_xbounds[i], g2_xbounds[i])) return differ;
    }
  }

  {
    const double *restrict g1_ybounds = g1->vtable->inqYBoundsPtr(g1), *restrict g2_ybounds = g2->vtable->inqYBoundsPtr(g2);
    if ((g1_ybounds != NULL) ^ (g2_ybounds != NULL)) return differ;
    if (g1_ybounds)
    {
      xassert(g1->nvertex);
      size_t size = (size_t) g1->nvertex * (isIrregular ? g1->size : g1->y.size);
      xassert(size);

      for (size_t i = 0; i < size; i++)
        if (IS_NOT_EQUAL(g1_ybounds[i], g2_ybounds[i])) return differ;
    }
  }

  if (!str_is_equal(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_NAME), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_NAME))) return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_NAME), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_NAME))) return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_LONGNAME), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_LONGNAME)))
    return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_LONGNAME), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_LONGNAME)))
    return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_UNITS), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_UNITS))) return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_UNITS), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_UNITS))) return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_STDNAME), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_STDNAME)))
    return differ;
  if (!str_is_equal(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_STDNAME), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_STDNAME)))
    return differ;

  if (!str_is_equal(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_REFERENCEURI), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_REFERENCEURI)))
    return differ;

  if (g1->mask)
  {
    xassert(g1->size);
    if (!g2->mask) return differ;
    if (memcmp(g1->mask, g2->mask, g1->size * sizeof(mask_t))) return differ;
  }
  else if (g2->mask)
    return differ;

  if (g1->mask_gme)
  {
    xassert(g1->size);
    if (!g2->mask_gme) return differ;
    if (memcmp(g1->mask_gme, g2->mask_gme, g1->size * sizeof(mask_t))) return differ;
  }
  else if (g2->mask_gme)
    return differ;

  unsigned char uuid1[CDI_UUID_SIZE] = { 0 };
  unsigned char uuid2[CDI_UUID_SIZE] = { 0 };
  int length = CDI_UUID_SIZE;
  cdiInqVarKeyBytes(&g1->keys, CDI_KEY_UUID, uuid1, &length);
  length = CDI_UUID_SIZE;
  cdiInqVarKeyBytes(&g2->keys, CDI_KEY_UUID, uuid2, &length);
  if (memcmp(uuid1, uuid2, CDI_UUID_SIZE)) return differ;

  return equal;
}

static void
grid_complete(grid_t *grid)
{
  int gridID = grid->self;

  if (grid->datatype != CDI_UNDEFID) cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, grid->datatype);

  int gridtype = grid->type;
  switch (gridtype)
  {
    case GRID_LONLAT:
    case GRID_GAUSSIAN:
    case GRID_UNSTRUCTURED:
    case GRID_CURVILINEAR:
    case GRID_GENERIC:
    case GRID_PROJECTION:
    case GRID_HEALPIX:
    case GRID_CHARXY:
    {
      if (grid->x.size > 0) gridDefXsize(gridID, (SizeType) grid->x.size);
      if (grid->y.size > 0) gridDefYsize(gridID, (SizeType) grid->y.size);

      if (gridtype == GRID_GAUSSIAN) gridDefNP(gridID, grid->np);

      if (grid->nvertex > 0) gridDefNvertex(gridID, grid->nvertex);

      if (grid->x.flag == 2)
      {
        assert(gridtype != GRID_UNSTRUCTURED && gridtype != GRID_CURVILINEAR);
        double *xvals = (double *) Malloc(grid->x.size * sizeof(double));
        gridGenXvals(grid->x.size, grid->x.first, grid->x.last, grid->x.inc, xvals);
        grid->x.vals = xvals;
        // gridDefXinc(gridID, grid->x.inc);
      }

      if (grid->y.flag == 2)
      {
        assert(gridtype != GRID_UNSTRUCTURED && gridtype != GRID_CURVILINEAR);
        double *yvals = (double *) Malloc(grid->y.size * sizeof(double));
        gridGenYvals(gridtype, grid->y.size, grid->y.first, grid->y.last, grid->y.inc, yvals);
        grid->y.vals = yvals;
        // gridDefYinc(gridID, grid->y.inc);
      }

      if (grid->projtype == CDI_PROJ_RLL)
      {
        const char *name = cdiInqVarKeyString(&grid->x.keys, CDI_KEY_NAME);
        if (name[0] == 0 || name[0] == 'x') cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_NAME, "rlon");
        name = cdiInqVarKeyString(&grid->y.keys, CDI_KEY_NAME);
        if (name[0] == 0 || name[0] == 'y') cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_NAME, "rlat");
        name = cdiInqVarKeyString(&grid->x.keys, CDI_KEY_LONGNAME);
        if (name[0] == 0) cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_LONGNAME, "longitude in rotated pole grid");
        name = cdiInqVarKeyString(&grid->y.keys, CDI_KEY_LONGNAME);
        if (name[0] == 0) cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_LONGNAME, "latitude in rotated pole grid");
        name = cdiInqVarKeyString(&grid->x.keys, CDI_KEY_UNITS);
        if (name[0] == 0) cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_UNITS, "degrees");
        name = cdiInqVarKeyString(&grid->y.keys, CDI_KEY_UNITS);
        if (name[0] == 0) cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_UNITS, "degrees");
        cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_grid_latlon][0]);
        cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_STDNAME, xystdname_tab[grid_xystdname_grid_latlon][1]);
      }

      if (gridtype == GRID_UNSTRUCTURED)
      {
        int number = cdiInqVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDUSED);
        if (number > 0)
        {
          cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, number);
          int position = cdiInqVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE);
          if (position > 0) cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDINREFERENCE, position);
        }
      }

      break;
    }
    case GRID_GAUSSIAN_REDUCED:
    {
      gridDefNP(gridID, grid->np);
      gridDefYsize(gridID, (SizeType) grid->y.size);
      if (grid->x.flag == 2)
      {
        double xvals[2] = { grid->x.first, grid->x.last };
        gridDefXsize(gridID, 2);
        gridDefXvals(gridID, xvals);
      }

      if (grid->y.flag == 2)
      {
        double *yvals = (double *) Malloc(grid->y.size * sizeof(double));
        gridGenYvals(gridtype, grid->y.size, grid->y.first, grid->y.last, grid->y.inc, yvals);
        grid->y.vals = yvals;
        // gridDefYinc(gridID, grid->y.inc);
      }
      break;
    }
    case GRID_SPECTRAL:
    {
      gridDefTrunc(gridID, grid->trunc);
      if (grid->lcomplex) gridDefComplexPacking(gridID, 1);
      break;
    }
    case GRID_FOURIER:
    {
      gridDefTrunc(gridID, grid->trunc);
      break;
    }
    case GRID_GME:
    {
      gridDefParamGME(gridID, grid->gme.nd, grid->gme.ni, grid->gme.ni2, grid->gme.ni3);
      break;
    }
      /*
    case GRID_GENERIC:
      {
        if ( grid->x.size > 0 && grid->y.size > 0 )
          {
            gridDefXsize(gridID, grid->x.size);
            gridDefYsize(gridID, grid->y.size);
            if ( grid->x.vals ) gridDefXvals(gridID, grid->x.vals);
            if ( grid->y.vals ) gridDefYvals(gridID, grid->y.vals);
          }
        break;
      }
      */
    case GRID_TRAJECTORY:
    {
      gridDefXsize(gridID, 1);
      gridDefYsize(gridID, 1);
      break;
    }
    default:
    {
      Error("Gridtype %s unsupported!", gridNamePtr(gridtype));
      break;
    }
  }
}

// Used only in iterator_grib.c
int
gridGenerate(const grid_t *grid)
{
  int gridType = grid->type;
  int gridID = gridCreate(gridType, (SizeType) grid->size);
  grid_t *restrict gridptr = grid_to_pointer(gridID);
  cdiCopyVarKey(&grid->keys, CDI_KEY_DATATYPE, &gridptr->keys);
  gridptr->x.size = grid->x.size;
  gridptr->y.size = grid->y.size;
  gridptr->np = grid->np;
  gridptr->nvertex = grid->nvertex;
  gridptr->x.flag = grid->x.flag;
  int valdef_group1 = 0;
  static const int valdef_group1_tab[]
      = { GRID_LONLAT, GRID_GAUSSIAN, GRID_UNSTRUCTURED, GRID_CURVILINEAR, GRID_GENERIC, GRID_PROJECTION, GRID_HEALPIX };
  for (size_t i = 0; i < sizeof(valdef_group1_tab) / sizeof(valdef_group1_tab[0]); ++i)
    valdef_group1 |= (gridType == valdef_group1_tab[i]);
  if (valdef_group1 && grid->x.flag == 1)
  {
    gridDefXvals(gridID, grid->x.vals);
    if (grid->x.bounds) gridDefXbounds(gridID, grid->x.bounds);
  }
  gridptr->x.first = grid->x.first;
  gridptr->x.last = grid->x.last;
  gridptr->x.inc = grid->x.inc;
  gridptr->y.flag = grid->y.flag;
  if ((valdef_group1 || gridType == GRID_GAUSSIAN_REDUCED) && grid->y.flag == 1)
  {
    gridDefYvals(gridID, grid->y.vals);
    if (grid->y.bounds) gridDefYbounds(gridID, grid->y.bounds);
  }
  gridptr->y.first = grid->y.first;
  gridptr->y.last = grid->y.last;
  gridptr->y.inc = grid->y.inc;
  if (valdef_group1 && grid->area) gridDefArea(gridID, grid->area);

  cdiCopyVarKey(&grid->keys, CDI_KEY_NUMBEROFGRIDUSED, &gridptr->keys);
  cdiCopyVarKey(&grid->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE, &gridptr->keys);
  cdiCopyVarKey(&grid->keys, CDI_KEY_REFERENCEURI, &gridptr->keys);

  cdiCopyVarKey(&grid->keys, CDI_KEY_SCANNINGMODE, &gridptr->keys);

  if (gridType == GRID_PROJECTION) gridptr->name = strdup(grid->name);
  if (gridType == GRID_GAUSSIAN_REDUCED) gridDefReducedPoints(gridID, (int) grid->y.size, grid->reducedPoints);
  gridptr->trunc = grid->trunc;
  gridptr->lcomplex = grid->lcomplex;
  gridptr->gme.nd = grid->gme.nd;
  gridptr->gme.ni = grid->gme.ni;
  gridptr->gme.ni2 = grid->gme.ni2;
  gridptr->gme.ni3 = grid->gme.ni3;

  grid_complete(gridptr);

  cdiCopyVarKey(&grid->keys, CDI_KEY_UUID, &gridptr->keys);

  return gridID;
}

static void
grid_copy_base_array_fields(grid_t *gridptrOrig, grid_t *gridptrDup)
{
  size_t reducedPointsSize = gridptrOrig->reducedPointsSize > 0 ? (size_t) gridptrOrig->reducedPointsSize : (size_t) 0;
  size_t gridsize = gridptrOrig->size;
  int gridtype = gridptrOrig->type;
  bool isIrregular = grid_is_irregular(gridtype);
  if (reducedPointsSize)
  {
    gridptrDup->reducedPoints = (int *) Malloc(reducedPointsSize * sizeof(int));
    memcpy(gridptrDup->reducedPoints, gridptrOrig->reducedPoints, reducedPointsSize * sizeof(int));
  }

  if (gridptrOrig->x.vals != NULL)
  {
    size_t size = isIrregular ? gridsize : gridptrOrig->x.size;
    gridptrDup->x.vals = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->x.vals, gridptrOrig->x.vals, size * sizeof(double));
  }

  if (gridptrOrig->y.vals != NULL)
  {
    size_t size = isIrregular ? gridsize : gridptrOrig->y.size;
    gridptrDup->y.vals = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->y.vals, gridptrOrig->y.vals, size * sizeof(double));
  }

  if (gridptrOrig->x.bounds != NULL)
  {
    size_t size = (isIrregular ? gridsize : gridptrOrig->x.size) * (size_t) gridptrOrig->nvertex;
    gridptrDup->x.bounds = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->x.bounds, gridptrOrig->x.bounds, size * sizeof(double));
  }

  if (gridptrOrig->y.bounds != NULL)
  {
    size_t size = (isIrregular ? gridsize : gridptrOrig->y.size) * (size_t) gridptrOrig->nvertex;
    gridptrDup->y.bounds = (double *) Malloc(size * sizeof(double));
    memcpy(gridptrDup->y.bounds, gridptrOrig->y.bounds, size * sizeof(double));
  }

  {
    const double *gridptrOrig_area = gridptrOrig->vtable->inqAreaPtr(gridptrOrig);
    if (gridptrOrig_area != NULL)
    {
      size_t size = gridsize;
      gridptrDup->area = (double *) Malloc(size * sizeof(double));
      memcpy(gridptrDup->area, gridptrOrig_area, size * sizeof(double));
    }
  }

  if (gridptrOrig->mask != NULL)
  {
    size_t size = gridsize;
    gridptrDup->mask = (mask_t *) Malloc(size * sizeof(mask_t));
    memcpy(gridptrDup->mask, gridptrOrig->mask, size * sizeof(mask_t));
  }

  if (gridptrOrig->mask_gme != NULL)
  {
    size_t size = gridsize;
    gridptrDup->mask_gme = (mask_t *) Malloc(size * sizeof(mask_t));
    memcpy(gridptrDup->mask_gme, gridptrOrig->mask_gme, size * sizeof(mask_t));
  }
}

/*
@Function  gridDuplicate
@Title     Duplicate a horizontal Grid

@Prototype int gridDuplicate(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridDuplicate} duplicates a horizontal Grid.

@Result
@func{gridDuplicate} returns an identifier to the duplicated Grid.

@EndFunction
*/
int
gridDuplicate(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  grid_t *gridptrnew = gridptr->vtable->copy(gridptr);
  int gridIDnew = reshPut(gridptrnew, &gridOps);
  gridptrnew->self = gridIDnew;
  return gridIDnew;
}

void
gridCompress(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  int gridtype = gridInqType(gridID);
  if (gridtype == GRID_UNSTRUCTURED)
  {
    if (gridptr->mask_gme != NULL)
    {
      size_t gridsize = (size_t) gridInqSize(gridID);
      size_t nv = (size_t) gridptr->nvertex;
      double *restrict area = (double *) gridptr->vtable->inqAreaPtr(gridptr),
                       *restrict xvals = (double *) gridptr->vtable->inqXValsPtr(gridptr),
                       *restrict yvals = (double *) gridptr->vtable->inqYValsPtr(gridptr),
                       *restrict xbounds = (double *) gridptr->vtable->inqXBoundsPtr(gridptr),
                       *restrict ybounds = (double *) gridptr->vtable->inqYBoundsPtr(gridptr);
      mask_t *restrict mask_gme = gridptr->mask_gme;
      size_t *restrict selection = (size_t *) Malloc(gridsize * sizeof(selection[0]));
      size_t nselect;
      {
        size_t j = 0;
        for (size_t i = 0; i < gridsize; i++) selection[j] = i, j += (mask_gme[i] != 0);
        nselect = j;
      }
      selection = (size_t *) Realloc(selection, nselect * sizeof(selection[0]));
      if (xvals)
        for (size_t i = 0; i < nselect; i++) xvals[i] = xvals[selection[i]];
      if (yvals)
        for (size_t i = 0; i < nselect; i++) yvals[i] = yvals[selection[i]];
      if (area)
        for (size_t i = 0; i < nselect; i++) area[i] = area[selection[i]];
      if (xbounds)
        for (size_t i = 0; i < nselect; i++)
          for (size_t iv = 0; iv < nv; iv++) xbounds[i * nv + iv] = xbounds[selection[i] * nv + iv];
      if (ybounds)
        for (size_t i = 0; i < nselect; i++)
          for (size_t iv = 0; iv < nv; iv++) ybounds[i * nv + iv] = ybounds[selection[i] * nv + iv];
      Free(selection);

      /* fprintf(stderr, "grid compress %d %d %d\n", i, j, gridsize); */
      gridsize = nselect;
      gridptr->size = gridsize;
      gridptr->x.size = gridsize;
      gridptr->y.size = gridsize;

      double **resizeP[] = { &gridptr->x.vals, &gridptr->y.vals, &gridptr->area, &gridptr->x.bounds, &gridptr->y.bounds };
      size_t newSize[] = { gridsize, gridsize, gridsize, nv * gridsize, nv * gridsize };
      for (size_t i = 0; i < sizeof(resizeP) / sizeof(resizeP[0]); ++i)
        if (*(resizeP[i])) *(resizeP[i]) = (double *) Realloc(*(resizeP[i]), newSize[i] * sizeof(double));

      Free(gridptr->mask_gme);
      gridptr->mask_gme = NULL;
      gridMark4Update(gridID);
    }
  }
  else
    Warning("Unsupported grid type: %s", gridNamePtr(gridtype));
}

static void
gridDefAreaSerial(grid_t *gridptr, const double *area)
{
  size_t size = gridptr->size;
  if (size == 0) Error("size undefined for gridID = %d", gridptr->self);

  if (gridptr->area == NULL)
    gridptr->area = (double *) Malloc(size * sizeof(double));
  else if (CDI_Debug)
    Warning("values already defined!");

  memcpy(gridptr->area, area, size * sizeof(double));
}

void
gridDefArea(int gridID, const double *area)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defArea(gridptr, area);
  gridMark4Update(gridID);
}

static void
gridInqAreaSerial(grid_t *gridptr, double *area)
{
  if (gridptr->area) memcpy(area, gridptr->area, gridptr->size * sizeof(double));
}

void
gridInqArea(int gridID, double *area)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->inqArea(gridptr, area);
}

static int
gridInqPropPresenceBase(grid_t *gridptr, enum gridPropInq inq)
{
  bool present = false;
  switch (inq)
  {
    case GRID_PROP_MASK: present = gridptr->mask != NULL; break;
    case GRID_PROP_MASK_GME: present = gridptr->mask != NULL; break;
    case GRID_PROP_AREA: present = gridptr->area != NULL; break;
    case GRID_PROP_XVALS: present = gridptr->x.vals != NULL; break;
    case GRID_PROP_YVALS: present = gridptr->y.vals != NULL; break;
    case GRID_PROP_XBOUNDS: present = gridptr->x.bounds != NULL; break;
    case GRID_PROP_YBOUNDS: present = gridptr->y.bounds != NULL; break;
  }
  return present;
}

int
gridInqPropPresence(int gridID, enum gridPropInq inq)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqPropPresence(gridptr, inq);
}

int
gridHasArea(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqPropPresence(gridptr, GRID_PROP_AREA);
}

static const double *
gridInqAreaPtrBase(grid_t *gridptr)
{
  return gridptr->area;
}

const double *
gridInqAreaPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqAreaPtr(gridptr);
}

void
gridDefNvertex(int gridID, int nvertex)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  if (gridptr->nvertex != nvertex)
  {
    gridptr->nvertex = nvertex;
    gridMark4Update(gridID);
  }
}

int
gridInqNvertex(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->nvertex;
}

static void
gridDefBoundsGeneric(grid_t *gridptr, const double *bounds, size_t regularSize, double **field)
{
  bool isIrregular = grid_is_irregular(gridptr->type);
  size_t nvertex = (size_t) gridptr->nvertex;
  if (nvertex == 0)
  {
    Warning("nvertex undefined for gridID = %d. Cannot define bounds!", gridptr->self);
    return;
  }

  size_t size = nvertex * (isIrregular ? gridptr->size : regularSize);
  if (size == 0) Error("size undefined for gridID = %d", gridptr->self);

  if (*field == NULL && size) { *field = (double *) Malloc(size * sizeof(double)); }
  else if (CDI_Debug) { Warning("values already defined!"); }

  copy_darray(size, bounds, *field);
}

static void
gridDefXBoundsSerial(grid_t *gridptr, const double *xbounds)
{
  gridDefBoundsGeneric(gridptr, xbounds, gridptr->x.size, &gridptr->x.bounds);
}

/*
@Function  gridDefXbounds
@Title     Define the bounds of a X-axis

@Prototype void gridDefXbounds(int gridID, const double *xbounds)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  xbounds  X-bounds of the grid.

@Description
The function @func{gridDefXbounds} defines all bounds of the X-axis.

@EndFunction
*/
void
gridDefXbounds(int gridID, const double *xbounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defXBounds(gridptr, xbounds);
  gridMark4Update(gridID);
}

static SizeType
gridInqXBoundsSerial(grid_t *gridptr, double *xbounds)
{
  size_t nvertex = (size_t) gridptr->nvertex;

  bool isIrregular = grid_is_irregular(gridptr->type);
  size_t size = nvertex * (isIrregular ? gridptr->size : gridptr->x.size);

  if (gridptr->x.bounds)
  {
    if (size && xbounds) copy_darray(size, gridptr->vtable->inqXBoundsPtr(gridptr), xbounds);
  }
  else
    size = 0;

  return (SizeType) size;
}

/*
@Function  gridInqXbounds
@Title     Get the bounds of a X-axis

@Prototype SizeType gridInqXbounds(int gridID, double *xbounds)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  xbounds  Pointer to the location into which the X-bounds are read.
                    The caller must allocate space for the returned values.

@Description
The function @func{gridInqXbounds} returns the bounds of the X-axis.

@Result
Upon successful completion @func{gridInqXbounds} returns the number of bounds and
the bounds are stored in @func{xbounds}.
Otherwise, 0 is returned and @func{xbounds} is empty.

@EndFunction
*/
SizeType
gridInqXbounds(int gridID, double *xbounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXBounds(gridptr, xbounds);
}

static const double *
gridInqXBoundsPtrSerial(grid_t *gridptr)
{
  return gridptr->x.bounds;
}

const double *
gridInqXboundsPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXBoundsPtr(gridptr);
}

static void
gridDefYBoundsSerial(grid_t *gridptr, const double *ybounds)
{
  gridDefBoundsGeneric(gridptr, ybounds, gridptr->y.size, &gridptr->y.bounds);
}

//----------------------------------------------------------------------------
// Parallel Version
//----------------------------------------------------------------------------

SizeType
gridInqXboundsPart(int gridID, int start, SizeType size, double *xbounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *gridptr_xbounds = gridptr->vtable->inqXBoundsPtr(gridptr);
  if (gridptr_xbounds && size && xbounds) memcpy(xbounds, gridptr_xbounds + start, (size_t) size * sizeof(double));

  return size;
}

SizeType
gridInqYboundsPart(int gridID, int start, SizeType size, double *ybounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *gridptr_ybounds = gridptr->vtable->inqYBoundsPtr(gridptr);
  if (gridptr_ybounds && size && ybounds) memcpy(ybounds, gridptr_ybounds + start, (size_t) size * sizeof(double));

  return size;
}

/*
@Function  gridDefYbounds
@Title     Define the bounds of a Y-axis

@Prototype void gridDefYbounds(int gridID, const double *ybounds)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  ybounds  Y-bounds of the grid.

@Description
The function @func{gridDefYbounds} defines all bounds of the Y-axis.

@EndFunction
*/
void
gridDefYbounds(int gridID, const double *ybounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->defYBounds(gridptr, ybounds);
  gridMark4Update(gridID);
}

static SizeType
gridInqYBoundsSerial(grid_t *gridptr, double *ybounds)
{
  size_t nvertex = (size_t) gridptr->nvertex;

  bool isIrregular = grid_is_irregular(gridptr->type);
  size_t size = nvertex * (isIrregular ? gridptr->size : gridptr->y.size);

  if (gridptr->y.bounds)
  {
    if (size && ybounds) copy_darray(size, gridptr->vtable->inqYBoundsPtr(gridptr), ybounds);
  }
  else
    size = 0;

  return (SizeType) size;
}

/*
@Function  gridInqYbounds
@Title     Get the bounds of a Y-axis

@Prototype SizeType gridInqYbounds(int gridID, double *ybounds)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  ybounds  Pointer to the location into which the Y-bounds are read.
                    The caller must allocate space for the returned values.

@Description
The function @func{gridInqYbounds} returns the bounds of the Y-axis.

@Result
Upon successful completion @func{gridInqYbounds} returns the number of bounds and
the bounds are stored in @func{ybounds}.
Otherwise, 0 is returned and @func{ybounds} is empty.

@EndFunction
*/
SizeType
gridInqYbounds(int gridID, double *ybounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYBounds(gridptr, ybounds);
}

static const double *
gridInqYBoundsPtrSerial(grid_t *gridptr)
{
  return gridptr->y.bounds;
}

const double *
gridInqYboundsPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYBoundsPtr(gridptr);
}

static void
printDblsPrefixAutoBrk(FILE *fp, int dig, const char prefix[], size_t nbyte0, size_t n, const double vals[])
{
  fputs(prefix, fp);
  size_t nbyte = nbyte0;
  for (size_t i = 0; i < n; i++)
  {
    if (nbyte > 80)
    {
      fprintf(fp, "\n%*s", (int) nbyte0, "");
      nbyte = nbyte0;
    }
    nbyte += (size_t) fprintf(fp, "%.*g ", dig, vals[i]);
  }
  fputs("\n", fp);
}

static inline void *
resizeBuffer(void **buf, size_t *bufSize, size_t reqSize)
{
  if (reqSize > *bufSize)
  {
    *buf = Realloc(*buf, reqSize);
    *bufSize = reqSize;
  }
  return *buf;
}

static void
gridPrintAttributes(FILE *fp, int gridID)
{
  int cdiID = gridID;
  int varID = CDI_GLOBAL;
  int atttype, attlen;
  char attname[CDI_MAX_NAME + 1];
  void *attBuf = NULL;
  size_t attBufSize = 0;

  int natts;
  cdiInqNatts(cdiID, varID, &natts);

  for (int iatt = 0; iatt < natts; ++iatt)
  {
    cdiInqAtt(cdiID, varID, iatt, attname, &atttype, &attlen);

    if (attlen == 0) continue;

    if (atttype == CDI_DATATYPE_TXT)
    {
      size_t attSize = (size_t) (attlen + 1) * sizeof(char);
      char *atttxt = (char *) resizeBuffer(&attBuf, &attBufSize, attSize);
      cdiInqAttTxt(cdiID, varID, attname, attlen, atttxt);
      atttxt[attlen] = 0;
      fprintf(fp, "ATTR_TXT: %s = \"%s\"\n", attname, atttxt);
    }
    else if (atttype == CDI_DATATYPE_INT8 || atttype == CDI_DATATYPE_UINT8 || atttype == CDI_DATATYPE_INT16
             || atttype == CDI_DATATYPE_UINT16 || atttype == CDI_DATATYPE_INT32 || atttype == CDI_DATATYPE_UINT32)
    {
      size_t attSize = (size_t) attlen * sizeof(int);
      int *attint = (int *) resizeBuffer(&attBuf, &attBufSize, attSize);
      cdiInqAttInt(cdiID, varID, attname, attlen, &attint[0]);
      if (attlen == 1)
        fprintf(fp, "ATTR_INT: %s =", attname);
      else
        fprintf(fp, "ATTR_INT_%d: %s =", attlen, attname);
      for (int i = 0; i < attlen; ++i) fprintf(fp, " %d", attint[i]);
      fprintf(fp, "\n");
    }
    else if (atttype == CDI_DATATYPE_FLT32 || atttype == CDI_DATATYPE_FLT64)
    {
      size_t attSize = (size_t) attlen * sizeof(double);
      double *attflt = (double *) resizeBuffer(&attBuf, &attBufSize, attSize);
      int dig = (atttype == CDI_DATATYPE_FLT64) ? 15 : 7;
      cdiInqAttFlt(cdiID, varID, attname, attlen, attflt);
      if (attlen == 1)
        fprintf(fp, "ATTR_FLT: %s =", attname);
      else
        fprintf(fp, "ATTR_FLT_%d: %s =", attlen, attname);
      for (int i = 0; i < attlen; ++i) fprintf(fp, " %.*g", dig, attflt[i]);
      fprintf(fp, "\n");
    }
  }

  Free(attBuf);
}

static void
gridPrintKernel(int gridID, int opt, FILE *fp)
{
  char attstr[CDI_MAX_NAME];
  char attstr2[CDI_MAX_NAME];
  size_t nxvals = (size_t) gridInqXvals(gridID, NULL);
  size_t nyvals = (size_t) gridInqYvals(gridID, NULL);

  int type = gridInqType(gridID);
  size_t gridsize = (size_t) gridInqSize(gridID);
  size_t xsize = (size_t) gridInqXsize(gridID);
  size_t ysize = (size_t) gridInqYsize(gridID);
  int nvertex = gridInqNvertex(gridID);
  int datatype;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);

  int dig = (datatype == CDI_DATATYPE_FLT64) ? 15 : 7;

  fprintf(fp,
          "gridtype  = %s\n"
          "gridsize  = %zu\n",
          gridNamePtr(type), gridsize);

  if (type != GRID_GME)
  {
    if (type != GRID_UNSTRUCTURED && type != GRID_SPECTRAL && type != GRID_FOURIER)
    {
      if (xsize > 0) fprintf(fp, "xsize     = %zu\n", xsize);
      if (ysize > 0) fprintf(fp, "ysize     = %zu\n", ysize);
    }

    if (nxvals > 0)
    {
      int length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_NAME, attstr, &length);
      if (attstr[0]) fprintf(fp, "xname     = %s\n", attstr);
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_DIMNAME, attstr2, &length);
      if (attstr2[0] && !str_is_equal(attstr, attstr2)) fprintf(fp, "xdimname  = %s\n", attstr2);
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_LONGNAME, attstr, &length);
      if (attstr[0]) fprintf(fp, "xlongname = %s\n", attstr);
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_UNITS, attstr, &length);
      if (attstr[0]) fprintf(fp, "xunits    = %s\n", attstr);
    }

    if (nyvals > 0)
    {
      int length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_NAME, attstr, &length);
      if (attstr[0]) fprintf(fp, "yname     = %s\n", attstr);
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_DIMNAME, attstr2, &length);
      if (attstr2[0] && !str_is_equal(attstr, attstr2)) fprintf(fp, "ydimname  = %s\n", attstr2);
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_LONGNAME, attstr, &length);
      if (attstr[0]) fprintf(fp, "ylongname = %s\n", attstr);
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_UNITS, attstr, &length);
      if (attstr[0]) fprintf(fp, "yunits    = %s\n", attstr);
    }

    if (type == GRID_UNSTRUCTURED && nvertex > 0) fprintf(fp, "nvertex   = %d\n", nvertex);
  }

  switch (type)
  {
    case GRID_LONLAT:
    case GRID_GAUSSIAN:
    case GRID_GAUSSIAN_REDUCED:
    case GRID_GENERIC:
    case GRID_PROJECTION:
    case GRID_CURVILINEAR:
    case GRID_UNSTRUCTURED:
    case GRID_CHARXY:
    {
      if (type == GRID_GAUSSIAN || type == GRID_GAUSSIAN_REDUCED) fprintf(fp, "np        = %d\n", gridInqNP(gridID));

      if (type == GRID_UNSTRUCTURED)
      {
        int number = 0;
        cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, &number);
        if (number > 0)
        {
          fprintf(fp, "number    = %d\n", number);
          int position = 0;
          cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDINREFERENCE, &position);
          if (position >= 0) fprintf(fp, "position  = %d\n", position);
        }

        int length;
        if (CDI_NOERR == cdiInqKeyLen(gridID, CDI_GLOBAL, CDI_KEY_REFERENCEURI, &length))
        {
          char reference_link[8192];
          length = sizeof(reference_link);
          cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_REFERENCEURI, reference_link, &length);
          fprintf(fp, "uri       = %s\n", reference_link);
        }
      }

      if (nxvals > 0)
      {
        double xfirst = 0.0, xinc = 0.0;

        if (type == GRID_LONLAT || type == GRID_GAUSSIAN || type == GRID_PROJECTION || type == GRID_GENERIC)
        {
          xfirst = gridInqXval(gridID, 0);
          xinc = gridInqXinc(gridID);
        }

        if (IS_NOT_EQUAL(xinc, 0) && opt)
        {
          fprintf(fp,
                  "xfirst    = %.*g\n"
                  "xinc      = %.*g\n",
                  dig, xfirst, dig, xinc);
        }
        else
        {
          double *xvals = (double *) Malloc(nxvals * sizeof(double));
          gridInqXvals(gridID, xvals);
          static const char prefix[] = "xvals     = ";
          printDblsPrefixAutoBrk(fp, dig, prefix, sizeof(prefix) - 1, nxvals, xvals);
          Free(xvals);
        }
      }

      if (nyvals > 0)
      {
        double yfirst = 0.0, yinc = 0.0;

        if (type == GRID_LONLAT || type == GRID_GENERIC || type == GRID_PROJECTION)
        {
          yfirst = gridInqYval(gridID, 0);
          yinc = gridInqYinc(gridID);
        }

        if (IS_NOT_EQUAL(yinc, 0) && opt)
        {
          fprintf(fp,
                  "yfirst    = %.*g\n"
                  "yinc      = %.*g\n",
                  dig, yfirst, dig, yinc);
        }
        else
        {
          double *yvals = (double *) Malloc(nyvals * sizeof(double));
          gridInqYvals(gridID, yvals);
          static const char prefix[] = "yvals     = ";
          printDblsPrefixAutoBrk(fp, dig, prefix, sizeof(prefix) - 1, nyvals, yvals);
          Free(yvals);
        }
      }

      if (type == GRID_PROJECTION) gridPrintAttributes(fp, gridID);

      break;
    }
    case GRID_SPECTRAL:
    {
      fprintf(fp,
              "truncation = %d\n"
              "complexpacking = %d\n",
              gridInqTrunc(gridID), gridInqComplexPacking(gridID));
      break;
    }
    case GRID_FOURIER:
    {
      fprintf(fp, "truncation = %d\n", gridInqTrunc(gridID));
      break;
    }
    case GRID_GME:
    {
      int nd, ni, ni2, ni3;
      gridInqParamGME(gridID, &nd, &ni, &ni2, &ni3);
      fprintf(fp, "ni        = %d\n", ni);
      break;
    }
    default:
    {
      fprintf(stderr, "Unsupported grid type: %s\n", gridNamePtr(type));
      break;
    }
  }
}

void
gridPrintP(void *voidptr, FILE *fp)
{
  grid_t *gridptr = (grid_t *) voidptr;
  int gridID = gridptr->self;

  xassert(gridptr);

  gridPrintKernel(gridID, 0, fp);

  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);

  fprintf(fp,
          "datatype  = %d\n"
          "nd        = %d\n"
          "ni        = %d\n"
          "ni2       = %d\n"
          "ni3       = %d\n"
          "trunc     = %d\n"
          "lcomplex  = %d\n"
          "reducedPointsSize   = %d\n",
          datatype, gridptr->gme.nd, gridptr->gme.ni, gridptr->gme.ni2, gridptr->gme.ni3, gridptr->trunc, gridptr->lcomplex,
          gridptr->reducedPointsSize);
}

static const double *
gridInqXValsPtrSerial(grid_t *gridptr)
{
  return gridptr->x.vals;
}

#ifndef USE_MPI
static const char **
gridInqXCvalsPtrSerial(grid_t *gridptr)
{
  return (const char **) gridptr->x.cvals;
}
#endif

const double *
gridInqXvalsPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXValsPtr(gridptr);
}

#ifndef USE_MPI
const char **
gridInqXCvalsPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXCvalsPtr(gridptr);
}
#endif

static const double *
gridInqYValsPtrSerial(grid_t *gridptr)
{
  return gridptr->y.vals;
}

#ifndef USE_MPI
static const char **
gridInqYCvalsPtrSerial(grid_t *gridptr)
{
  return (const char **) gridptr->y.cvals;
}
#endif

const double *
gridInqYvalsPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYValsPtr(gridptr);
}

#ifndef USE_MPI
const char **
gridInqYCvalsPtr(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYCvalsPtr(gridptr);
}
#endif

void
gridProjParamsInit(struct CDI_GridProjParams *gpp)
{
  // clang-format off
  gpp->mv      = CDI_Grid_Missval;   // Missing value
  gpp->lon_0   = CDI_Grid_Missval;   // The East longitude of the meridian which is parallel to the Y-axis
  gpp->lat_0   = CDI_Grid_Missval;   // Latitude of the projection origin
  gpp->lat_1   = CDI_Grid_Missval;   // First latitude from the pole at which the secant cone cuts the sphere
  gpp->lat_2   = CDI_Grid_Missval;   // Second latitude at which the secant cone cuts the sphere
  gpp->a       = CDI_Grid_Missval;   // Semi-major axis or earth radius in metres (optional)
  gpp->b       = CDI_Grid_Missval;   // Semi-minor axis in metres (optional)
  gpp->rf      = CDI_Grid_Missval;   // Inverse flattening (1/f) (optional)
  gpp->xval_0  = CDI_Grid_Missval;   // Longitude of the first grid point in degree (optional)
  gpp->yval_0  = CDI_Grid_Missval;   // Latitude of the first grid point in degree (optional)
  gpp->x_0     = CDI_Grid_Missval;   // False easting (optional)
  gpp->y_0     = CDI_Grid_Missval;   // False northing (optional)
  gpp->x_SP    = CDI_Grid_Missval;   // Longitude of southern pole
  gpp->y_SP    = CDI_Grid_Missval;   // Latitude of southern pole
  gpp->nside   = 0;                  // HEALPix number of points along a side (number of data points should be = 12 * nside * nside)
  gpp->order   = -1;                 // HEALPix ordering convention (0:ring; 1:nested)
  // clang-format on
}

static void
gridDefParamsCommon(int gridID, struct CDI_GridProjParams gpp)
{
  if (IS_NOT_EQUAL(gpp.a, gpp.mv))
  {
    if (IS_NOT_EQUAL(gpp.b, gpp.mv))
    {
      cdiDefAttFlt(gridID, CDI_GLOBAL, "semi_major_axis", CDI_DATATYPE_FLT64, 1, &gpp.a);
      cdiDefAttFlt(gridID, CDI_GLOBAL, "semi_minor_axis", CDI_DATATYPE_FLT64, 1, &gpp.b);
    }
    else { cdiDefAttFlt(gridID, CDI_GLOBAL, "earth_radius", CDI_DATATYPE_FLT64, 1, &gpp.a); }
  }
  if (IS_NOT_EQUAL(gpp.rf, gpp.mv)) cdiDefAttFlt(gridID, CDI_GLOBAL, "inverse_flattening", CDI_DATATYPE_FLT64, 1, &gpp.rf);
  if (IS_NOT_EQUAL(gpp.x_0, gpp.mv)) cdiDefAttFlt(gridID, CDI_GLOBAL, "false_easting", CDI_DATATYPE_FLT64, 1, &gpp.x_0);
  if (IS_NOT_EQUAL(gpp.y_0, gpp.mv)) cdiDefAttFlt(gridID, CDI_GLOBAL, "false_northing", CDI_DATATYPE_FLT64, 1, &gpp.y_0);
  if (IS_NOT_EQUAL(gpp.xval_0, gpp.mv))
    cdiDefAttFlt(gridID, CDI_GLOBAL, "longitudeOfFirstGridPointInDegrees", CDI_DATATYPE_FLT64, 1, &gpp.xval_0);
  if (IS_NOT_EQUAL(gpp.yval_0, gpp.mv))
    cdiDefAttFlt(gridID, CDI_GLOBAL, "latitudeOfFirstGridPointInDegrees", CDI_DATATYPE_FLT64, 1, &gpp.yval_0);
  if (IS_NOT_EQUAL(gpp.x_SP, gpp.mv))
    cdiDefAttFlt(gridID, CDI_GLOBAL, "longitudeOfSouthernPoleInDegrees", CDI_DATATYPE_FLT64, 1, &gpp.x_SP);
  if (IS_NOT_EQUAL(gpp.y_SP, gpp.mv))
    cdiDefAttFlt(gridID, CDI_GLOBAL, "latitudeOfSouthernPoleInDegrees", CDI_DATATYPE_FLT64, 1, &gpp.y_SP);
}

/*
@Function  gridDefParamsLCC
@Title     Define the parameters of a Lambert Conformal Conic grid

@Prototype void gridDefParamsLCC(int gridID, struct CDI_GridProjParams gridProjParams)
@Parameter
    @Item  gridID          Grid ID, from a previous call to @fref{gridCreate}.
    @Item  gridProjParams  Grid projection parameters.

@Description
The function @func{gridDefParamsLCC} defines the parameters of a Lambert Conformal Conic grid.

@EndFunction
*/
void
gridDefParamsLCC(int gridID, struct CDI_GridProjParams gpp)
{
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, "Lambert_Conformal");

  const char *gmapname = "lambert_conformal_conic";
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname);
  cdiDefAttTxt(gridID, CDI_GLOBAL, "grid_mapping_name", (int) (strlen(gmapname)), gmapname);
  int nlats = 0;
  double lats[2];
  lats[nlats++] = gpp.lat_1;
  if (IS_NOT_EQUAL(gpp.lat_1, gpp.lat_2)) lats[nlats++] = gpp.lat_2;
  cdiDefAttFlt(gridID, CDI_GLOBAL, "standard_parallel", CDI_DATATYPE_FLT64, nlats, lats);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "longitude_of_central_meridian", CDI_DATATYPE_FLT64, 1, &gpp.lon_0);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "latitude_of_projection_origin", CDI_DATATYPE_FLT64, 1, &gpp.lat_0);

  gridDefParamsCommon(gridID, gpp);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->projtype = CDI_PROJ_LCC;

  if (gridptr->type != GRID_PROJECTION) gridptr->type = GRID_PROJECTION;

  gridVerifyProj(gridID);
}

/*
@Function  gridInqParamsLCC
@Title     Get the parameter of a Lambert Conformal Conic grid

@Prototype void gridInqParamsLCC(int gridID, struct CDI_GridProjParams *gpp)
@Parameter
    @Item  gridID          Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  gridProjParams  Grid projection parameters.

@Description
The function @func{gridInqParamsLCC} returns the parameter of a Lambert Conformal Conic grid.

@EndFunction
*/
int
gridInqParamsLCC(int gridID, struct CDI_GridProjParams *gpp)
{
  int status = -1;
  if (gridInqType(gridID) != GRID_PROJECTION) return status;

  gridProjParamsInit(gpp);

  status = -2;
  const char *projection = "lambert_conformal_conic";
  char gmapname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
  if (gmapname[0] && str_is_equal(gmapname, projection))
  {
    char attname[CDI_MAX_NAME + 1];

    int natts;
    cdiInqNatts(gridID, CDI_GLOBAL, &natts);

    if (natts) status = 0;

    for (int iatt = 0; iatt < natts; ++iatt)
    {
      int atttype, attlen;
      cdiInqAtt(gridID, CDI_GLOBAL, iatt, attname, &atttype, &attlen);
      if (attlen > 2) continue;

      double attflt[2];
      if (cdiInqAttConvertedToFloat(gridID, atttype, attname, attlen, attflt))
      {
        // clang-format off
        if      (str_is_equal(attname, "earth_radius"))                       gpp->a      = attflt[0];
        else if (str_is_equal(attname, "semi_major_axis"))                    gpp->a      = attflt[0];
        else if (str_is_equal(attname, "semi_minor_axis"))                    gpp->b      = attflt[0];
        else if (str_is_equal(attname, "inverse_flattening"))                 gpp->rf     = attflt[0];
        else if (str_is_equal(attname, "longitude_of_central_meridian"))      gpp->lon_0  = attflt[0];
        else if (str_is_equal(attname, "latitude_of_projection_origin"))      gpp->lat_0  = attflt[0];
        else if (str_is_equal(attname, "false_easting"))                      gpp->x_0    = attflt[0];
        else if (str_is_equal(attname, "false_northing"))                     gpp->y_0    = attflt[0];
        else if (str_is_equal(attname, "longitudeOfFirstGridPointInDegrees")) gpp->xval_0 = attflt[0];
        else if (str_is_equal(attname, "latitudeOfFirstGridPointInDegrees"))  gpp->yval_0 = attflt[0];
        else if (str_is_equal(attname, "longitudeOfSouthernPoleInDegrees"))   gpp->x_SP   = attflt[0];
        else if (str_is_equal(attname, "latitudeOfSouthernPoleInDegrees"))    gpp->y_SP   = attflt[0];
        else if (str_is_equal(attname, "standard_parallel"))
          {
            gpp->lat_1 = attflt[0];
            gpp->lat_2 = (attlen == 2) ? attflt[1] : attflt[0];
          }
        // clang-format on
      }
    }
  }

  return status;
}

int
gridVerifyProjParamsLCC(struct CDI_GridProjParams *gpp)
{
  static bool lwarn = true;

  if (lwarn)
  {
    // lwarn = false;
    const char *projection = "lambert_conformal_conic";
    if (IS_EQUAL(gpp->lon_0, gpp->mv)) Warning("%s mapping parameter %s missing!", projection, "longitude_of_central_meridian");
    if (IS_EQUAL(gpp->lat_0, gpp->mv)) Warning("%s mapping parameter %s missing!", projection, "latitude_of_central_meridian");
    if (IS_EQUAL(gpp->lat_1, gpp->mv)) Warning("%s mapping parameter %s missing!", projection, "standard_parallel");
    if (IS_NOT_EQUAL(gpp->x_0, gpp->mv) && IS_NOT_EQUAL(gpp->y_0, gpp->mv)
        && (IS_EQUAL(gpp->xval_0, gpp->mv) || IS_EQUAL(gpp->yval_0, gpp->mv)))
    {
      if (proj_lcc_to_lonlat_func)
      {
        gpp->xval_0 = -gpp->x_0;
        gpp->yval_0 = -gpp->y_0;
        proj_lcc_to_lonlat_func(*gpp, 0.0, 0.0, (SizeType) 1, &gpp->xval_0, &gpp->yval_0);
      }
      if (IS_EQUAL(gpp->xval_0, gpp->mv) || IS_EQUAL(gpp->yval_0, gpp->mv))
        Warning("%s mapping parameter %s missing!", projection,
                "longitudeOfFirstGridPointInDegrees and latitudeOfFirstGridPointInDegrees");
    }
  }

  return 0;
}

int
gridVerifyProjParamsSTERE(struct CDI_GridProjParams *gpp)
{
  static bool lwarn = true;

  if (lwarn)
  {
    // lwarn = false;
    const char *projection = "polar_stereographic";
    if (IS_EQUAL(gpp->lon_0, gpp->mv))
      Warning("%s mapping parameter %s missing!", projection, "straight_vertical_longitude_from_pole");
    if (IS_EQUAL(gpp->lat_0, gpp->mv)) Warning("%s mapping parameter %s missing!", projection, "latitude_of_projection_origin");
    if (IS_EQUAL(gpp->lat_1, gpp->mv)) Warning("%s mapping parameter %s missing!", projection, "standard_parallel");
    if (IS_NOT_EQUAL(gpp->x_0, gpp->mv) && IS_NOT_EQUAL(gpp->y_0, gpp->mv)
        && (IS_EQUAL(gpp->xval_0, gpp->mv) || IS_EQUAL(gpp->yval_0, gpp->mv)))
    {
      if (proj_stere_to_lonlat_func)
      {
        gpp->xval_0 = -gpp->x_0;
        gpp->xval_0 = -gpp->y_0;
        proj_stere_to_lonlat_func(*gpp, 0.0, 0.0, (SizeType) 1, &gpp->xval_0, &gpp->yval_0);
      }
      if (IS_EQUAL(gpp->xval_0, gpp->mv) || IS_EQUAL(gpp->yval_0, gpp->mv))
        Warning("%s mapping parameter %s missing!", projection,
                "longitudeOfFirstGridPointInDegrees and latitudeOfFirstGridPointInDegrees");
    }
  }

  return 0;
}

int
gridVerifyProjParamsHEALPIX(struct CDI_GridProjParams *gpp)
{
  static bool lwarn = true;

  if (lwarn)
  {
    lwarn = false;
    const char *projection = "healpix";
    if (IS_EQUAL(gpp->nside, -1)) Error("%s mapping parameter %s missing!", projection, "nside");
    if (IS_EQUAL(gpp->order, -1)) Error("%s mapping parameter %s missing!", projection, "order");
    if (gpp->nside == 0) Error("%s mapping parameter %s unsupported!", projection, "nside", gpp->nside);
    if (gpp->order != 0 && gpp->order != 1) Error("%s mapping parameter %s=%d unsupported!", projection, "order", gpp->order);
  }

  return 0;
}

/*
@Function  gridDefParamsSTERE
@Title     Define the parameter of a Polar stereographic grid

@Prototype void gridDefParamsSTERE(int gridID, struct CDI_GridProjParams gridProjParams)
@Parameter
    @Item  gridID          Grid ID, from a previous call to @fref{gridCreate}.
    @Item  gridProjParams  Grid projection parameters.

@Description
The function @func{gridDefParamsSTERE} defines the parameter of a Polar stereographic grid.

@EndFunction
*/
void
gridDefParamsSTERE(int gridID, struct CDI_GridProjParams gpp)
{
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, "Polar_Stereographic");

  const char *gmapname = "polar_stereographic";
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname);
  cdiDefAttTxt(gridID, CDI_GLOBAL, "grid_mapping_name", (int) (strlen(gmapname)), gmapname);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "standard_parallel", CDI_DATATYPE_FLT64, 1, &gpp.lat_1);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "straight_vertical_longitude_from_pole", CDI_DATATYPE_FLT64, 1, &gpp.lon_0);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "latitude_of_projection_origin", CDI_DATATYPE_FLT64, 1, &gpp.lat_0);

  gridDefParamsCommon(gridID, gpp);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->projtype = CDI_PROJ_STERE;

  gridVerifyProj(gridID);
}

void
gridDefParamsHEALPIX(int gridID, struct CDI_GridProjParams gpp)
{
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, "healpix");

  const char *gmapname = "healpix";
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname);
  cdiDefAttTxt(gridID, CDI_GLOBAL, "grid_mapping_name", (int) (strlen(gmapname)), gmapname);

  cdiDefAttInt(gridID, CDI_GLOBAL, "healpix_nside", CDI_DATATYPE_INT32, 1, &gpp.nside);
  const char *orderName = (gpp.order == 1) ? "nested" : "ring";
  cdiDefAttTxt(gridID, CDI_GLOBAL, "healpix_order", (int) (strlen(orderName)), orderName);

  // gridDefParamsCommon(gridID, gpp);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->projtype = CDI_PROJ_HEALPIX;

  // gridVerifyProj(gridID);
}

/*
@Function  gridInqParamsSTERE
@Title     Get the parameter of a Polar stereographic grid

@Prototype void gridInqParamsSTERE(int gridID, struct CDI_GridProjParams *gpp)
@Parameter
    @Item  gridID    Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  gridProjParams  Grid projection parameters.

@Description
The function @func{gridInqParamsSTERE} returns the parameter of a Polar stereographic grid.

@EndFunction
*/
int
gridInqParamsSTERE(int gridID, struct CDI_GridProjParams *gpp)
{
  int status = -1;
  if (gridInqType(gridID) != GRID_PROJECTION) return status;

  gridProjParamsInit(gpp);

  status = -2;
  const char *projection = "polar_stereographic";
  char gmapname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
  if (gmapname[0] && str_is_equal(gmapname, projection))
  {
    int atttype, attlen;
    char attname[CDI_MAX_NAME + 1];

    int natts;
    cdiInqNatts(gridID, CDI_GLOBAL, &natts);

    if (natts) status = 0;

    for (int iatt = 0; iatt < natts; ++iatt)
    {
      cdiInqAtt(gridID, CDI_GLOBAL, iatt, attname, &atttype, &attlen);
      if (attlen > 2) continue;

      double attflt[2];
      if (cdiInqAttConvertedToFloat(gridID, atttype, attname, attlen, attflt))
      {
        // clang-format off
        if      (str_is_equal(attname, "earth_radius"))                          gpp->a      = attflt[0];
        else if (str_is_equal(attname, "semi_major_axis"))                       gpp->a      = attflt[0];
        else if (str_is_equal(attname, "semi_minor_axis"))                       gpp->b      = attflt[0];
        else if (str_is_equal(attname, "inverse_flattening"))                    gpp->rf     = attflt[0];
        else if (str_is_equal(attname, "standard_parallel"))                     gpp->lat_1  = attflt[0];
        else if (str_is_equal(attname, "straight_vertical_longitude_from_pole")) gpp->lon_0  = attflt[0];
        else if (str_is_equal(attname, "latitude_of_projection_origin"))         gpp->lat_0  = attflt[0];
        else if (str_is_equal(attname, "false_easting"))                         gpp->x_0    = attflt[0];
        else if (str_is_equal(attname, "false_northing"))                        gpp->y_0    = attflt[0];
        else if (str_is_equal(attname, "longitudeOfFirstGridPointInDegrees"))    gpp->xval_0 = attflt[0];
        else if (str_is_equal(attname, "latitudeOfFirstGridPointInDegrees"))     gpp->yval_0 = attflt[0];
        // clang-format on
      }
    }
  }

  return status;
}

int
gridInqParamsHEALPIX(int gridID, struct CDI_GridProjParams *gpp)
{
  int status = -1;
  if (gridInqType(gridID) != GRID_PROJECTION) return status;

  gridProjParamsInit(gpp);

  status = -2;
  const char *projection = "healpix";
  char gmapname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
  if (gmapname[0] && str_is_equal(gmapname, projection))
  {
    int atttype, attlen;
    char attname[CDI_MAX_NAME + 1];

    int natts;
    cdiInqNatts(gridID, CDI_GLOBAL, &natts);

    if (natts) status = 0;

    for (int iatt = 0; iatt < natts; ++iatt)
    {
      cdiInqAtt(gridID, CDI_GLOBAL, iatt, attname, &atttype, &attlen);

      if (atttype == CDI_DATATYPE_TXT)
      {
        char attstring[256];
        if (cdiInqAttTxt(gridID, CDI_GLOBAL, attname, (int) sizeof(attstring), attstring) == 0)
        {
          attstring[attlen] = 0;
          if (str_is_equal(attname, "healpix_order")) gpp->order = strStartsWith(attstring, "nest");
        }
      }
      else
      {
        if (attlen > 2) continue;
        double attflt[2];
        if (cdiInqAttConvertedToFloat(gridID, atttype, attname, attlen, attflt))
        {
          // clang-format off
          if      (str_is_equal(attname, "earth_radius"))                          gpp->a      = attflt[0];
          else if (str_is_equal(attname, "semi_major_axis"))                       gpp->a      = attflt[0];
          else if (str_is_equal(attname, "semi_minor_axis"))                       gpp->b      = attflt[0];
          else if (str_is_equal(attname, "inverse_flattening"))                    gpp->rf     = attflt[0];
          else if (str_is_equal(attname, "longitudeOfFirstGridPointInDegrees"))    gpp->xval_0 = attflt[0];
          else if (str_is_equal(attname, "healpix_nside"))                         gpp->nside  = (int) lround(attflt[0]);
          // clang-format on
        }
      }
    }
  }

  return status;
}

void
gridDefComplexPacking(int gridID, int lcomplex)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->lcomplex != lcomplex)
  {
    gridptr->lcomplex = (lcomplex != 0);
    gridMark4Update(gridID);
  }
}

int
gridInqComplexPacking(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  return (int) gridptr->lcomplex;
}

void
gridDefHasDims(int gridID, int hasdims)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if (gridptr->hasdims != (hasdims != 0))
  {
    gridptr->hasdims = (hasdims != 0);
    gridMark4Update(gridID);
  }
}

int
gridInqHasDims(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  return (int) gridptr->hasdims;
}

/*
@Function  gridDefNumber
@Title     Define the reference number for an unstructured grid

@Prototype void gridDefNumber(int gridID, int number)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  number   Reference number for an unstructured grid.

@Description
The function @func{gridDefNumber} defines the reference number for an unstructured grid.

@EndFunction
*/
void
gridDefNumber(int gridID, int number)
{
  cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, number);
}

/*
@Function  gridInqNumber
@Title     Get the reference number to an unstructured grid

@Prototype int gridInqNumber(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqNumber} returns the reference number to an unstructured grid.

@Result
@func{gridInqNumber} returns the reference number to an unstructured grid.
@EndFunction
*/
int
gridInqNumber(int gridID)
{
  int number = 0;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, &number);
  return number;
}

/*
@Function  gridDefPosition
@Title     Define the position of grid in the reference file

@Prototype void gridDefPosition(int gridID, int position)
@Parameter
    @Item  gridID     Grid ID, from a previous call to @fref{gridCreate}.
    @Item  position   Position of grid in the reference file.

@Description
The function @func{gridDefPosition} defines the position of grid in the reference file.

@EndFunction
*/
void
gridDefPosition(int gridID, int position)
{
  cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDINREFERENCE, position);
}

/*
@Function  gridInqPosition
@Title     Get the position of grid in the reference file

@Prototype int gridInqPosition(int gridID)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqPosition} returns the position of grid in the reference file.

@Result
@func{gridInqPosition} returns the position of grid in the reference file.
@EndFunction
*/
int
gridInqPosition(int gridID)
{
  int position = 0;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDINREFERENCE, &position);
  return position;
}

/*
@Function  gridDefReference
@Title     Define the reference URI for an unstructured grid

@Prototype void gridDefReference(int gridID, const char *reference)
@Parameter
    @Item  gridID      Grid ID, from a previous call to @fref{gridCreate}.
    @Item  reference   Reference URI for an unstructured grid.

@Description
The function @func{gridDefReference} defines the reference URI for an unstructured grid.

@EndFunction
*/
void
gridDefReference(int gridID, const char *reference)
{
  if (reference)
  {
    cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_REFERENCEURI, reference);
    gridMark4Update(gridID);
  }
}

/*
@Function  gridInqReference
@Title     Get the reference URI to an unstructured grid

@Prototype char *gridInqReference(int gridID, char *reference)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqReference} returns the reference URI to an unstructured grid.

@Result
@func{gridInqReference} returns the reference URI to an unstructured grid.
@EndFunction
*/
int
gridInqReference(int gridID, char *reference)
{
  int length = 0;
  if (CDI_NOERR == cdiInqKeyLen(gridID, CDI_GLOBAL, CDI_KEY_REFERENCEURI, &length))
  {
    if (reference) cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_REFERENCEURI, reference, &length);
  }

  return length;
}

/*
@Function  gridDefUUID
@Title     Define the UUID for an unstructured grid

@Prototype void gridDefUUID(int gridID, const char *uuid)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate}.
    @Item  uuid     UUID for an unstructured grid.

@Description
The function @func{gridDefUUID} defines the UUID for an unstructured grid.

@EndFunction
*/
void
gridDefUUID(int gridID, const unsigned char uuid[CDI_UUID_SIZE])
{
  cdiDefKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuid, CDI_UUID_SIZE);

  gridMark4Update(gridID);
}

/*
@Function  gridInqUUID
@Title     Get the UUID to an unstructured grid

@Prototype void gridInqUUID(int gridID, char *uuid)
@Parameter
    @Item  gridID   Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.

@Description
The function @func{gridInqUUID} returns the UUID to an unstructured grid.

@Result
@func{gridInqUUID} returns the UUID to an unstructured grid to the parameter uuid.
@EndFunction
*/
void
gridInqUUID(int gridID, unsigned char uuid[CDI_UUID_SIZE])
{
  memset(uuid, 0, CDI_UUID_SIZE);
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
}

void
cdiGridGetIndexList(unsigned ngrids, int *gridIndexList)
{
  reshGetResHListOfType(ngrids, gridIndexList, &gridOps);
}

static int
gridTxCode(void *voidP)
{
  grid_t *gridptr = (grid_t *) voidP;
  return gridptr->vtable->txCode;
}

enum
{
  GRID_PACK_INT_IDX_SELF,
  GRID_PACK_INT_IDX_TYPE,
  GRID_PACK_INT_IDX_IS_CYCLIC,
  GRID_PACK_INT_IDX_X_FLAG,
  GRID_PACK_INT_IDX_Y_FLAG,
  GRID_PACK_INT_IDX_GME_ND,
  GRID_PACK_INT_IDX_GME_NI,
  GRID_PACK_INT_IDX_GME_NI2,
  GRID_PACK_INT_IDX_GME_NI3,
  GRID_PACK_INT_IDX_TRUNC,
  GRID_PACK_INT_IDX_NVERTEX,
  GRID_PACK_INT_IDX_REDUCED_POINTS_SIZE,
  GRID_PACK_INT_IDX_SIZE,
  GRID_PACK_INT_IDX_X_SIZE,
  GRID_PACK_INT_IDX_Y_SIZE,
  GRID_PACK_INT_IDX_LCOMPLEX,
  GRID_PACK_INT_IDX_MEMBERMASK,
  /*
  GRID_PACK_INT_IDX_XTSTDNNAME,
  GRID_PACK_INT_IDX_YTSTDNNAME,
  GRID_PACK_INT_IDX_ISCANSNEGATIVELY,
  GRID_PACK_INT_IDX_JSCANSPOSITIVELY,
  GRID_PACK_INT_IDX_JPOINTSARECONSECUTIVE,
  */
  gridNint
};

enum
{
  GRID_PACK_DBL_IDX_X_FIRST,
  GRID_PACK_DBL_IDX_Y_FIRST,
  GRID_PACK_DBL_IDX_X_LAST,
  GRID_PACK_DBL_IDX_Y_LAST,
  GRID_PACK_DBL_IDX_X_INC,
  GRID_PACK_DBL_IDX_Y_INC,
  gridNdouble
};

enum
{
  gridHasMaskFlag = 1 << 0,
  gridHasGMEMaskFlag = 1 << 1,
  gridHasXValsFlag = 1 << 2,
  gridHasYValsFlag = 1 << 3,
  gridHasAreaFlag = 1 << 4,
  gridHasXBoundsFlag = 1 << 5,
  gridHasYBoundsFlag = 1 << 6,
  gridHasReducedPointsFlag = 1 << 7,
};

static int
gridGetComponentFlags(const grid_t *gridP)
{
  int flags = 0;
  for (int prop = 0; prop < GRID_PROP_YBOUNDS + 1; ++prop)
    flags |= (gridP->vtable->inqPropPresence((grid_t *) gridP, (enum gridPropInq) prop) << prop);
  flags |= (gridHasReducedPointsFlag & (int) ((unsigned) (gridP->reducedPoints == NULL) - 1U));
  return flags;
}

static int
gridGetPackSize(void *voidP, void *context)
{
  grid_t *gridP = (grid_t *) voidP;
  return gridP->vtable->getPackSize(gridP, context);
}

static int gridGetPackSizeScalars(grid_t *gridP, void *context);
static int gridGetPackSizeArrays(grid_t *gridP, void *context);

static int
gridGetPackSizeBase(grid_t *gridP, void *context)
{
  return gridP->vtable->getPackSizeScalars(gridP, context) + gridP->vtable->getPackSizeArrays(gridP, context);
}

static int
gridGetPackSizeScalars(grid_t *gridP, void *context)
{
  int packBuffSize = 0, ui32PackSize = serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  packBuffSize += serializeGetSize(gridNint, CDI_DATATYPE_INT, context) + ui32PackSize;
  packBuffSize += serializeGetSize(gridNdouble, CDI_DATATYPE_FLT64, context) + ui32PackSize;

  packBuffSize += serializeKeysGetPackSize(&gridP->keys, context);
  packBuffSize += serializeKeysGetPackSize(&gridP->x.keys, context);
  packBuffSize += serializeKeysGetPackSize(&gridP->y.keys, context);

  return packBuffSize;
}

static int
gridGetPackSizeArrays(grid_t *gridP, void *context)
{
  int packBuffSize = 0, ui32PackSize = serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (gridP->reducedPoints)
  {
    xassert(gridP->reducedPointsSize);
    packBuffSize += serializeGetSize(gridP->reducedPointsSize, CDI_DATATYPE_INT, context) + ui32PackSize;
  }

  if (gridP->vtable->inqXValsPtr(gridP))
  {
    size_t count = grid_is_irregular(gridP->type) ? gridP->size : gridP->x.size;
    xassert(count && count <= INT_MAX);
    packBuffSize += serializeGetSize((int) count, CDI_DATATYPE_FLT64, context) + ui32PackSize;
  }

  if (gridP->vtable->inqYValsPtr(gridP))
  {
    size_t count = grid_is_irregular(gridP->type) ? gridP->size : gridP->y.size;
    xassert(count && count <= INT_MAX);
    packBuffSize += serializeGetSize((int) count, CDI_DATATYPE_FLT64, context) + ui32PackSize;
  }

  if (gridP->vtable->inqAreaPtr(gridP))
  {
    size_t count = gridP->size;
    xassert(count && count <= INT_MAX);
    packBuffSize += serializeGetSize((int) count, CDI_DATATYPE_FLT64, context) + ui32PackSize;
  }

  if (gridP->x.bounds)
  {
    xassert(gridP->nvertex);
    size_t count = (grid_is_irregular(gridP->type) ? gridP->size : gridP->x.size) * (size_t) gridP->nvertex;
    xassert(count && count <= INT_MAX);
    packBuffSize += serializeGetSize((int) count, CDI_DATATYPE_FLT64, context) + ui32PackSize;
  }

  if (gridP->y.bounds)
  {
    xassert(gridP->nvertex);
    size_t count = (grid_is_irregular(gridP->type) ? gridP->size : gridP->y.size) * (size_t) gridP->nvertex;
    xassert(count && count <= INT_MAX);
    packBuffSize += (serializeGetSize((int) count, CDI_DATATYPE_FLT64, context) + ui32PackSize);
  }

  if (gridP->mask)
  {
    size_t count = gridP->size;
    xassert(count && count <= INT_MAX);
    packBuffSize += serializeGetSize((int) count, CDI_DATATYPE_UCHAR, context) + ui32PackSize;
  }

  if (gridP->mask_gme)
  {
    size_t count = gridP->size;
    xassert(count && count <= INT_MAX);
    packBuffSize += serializeGetSize((int) count, CDI_DATATYPE_UCHAR, context) + ui32PackSize;
  }

  return packBuffSize;
}

static grid_t *gridUnpackScalars(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context,
                                 int force_id, int *memberMaskP);

static void gridUnpackArrays(grid_t *gridP, int memberMask, char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos,
                             int originNamespace, void *context);

int
gridUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id)
{
  gridInit();
  int memberMask;
  grid_t *gridP
      = gridUnpackScalars(unpackBuffer, unpackBufferSize, unpackBufferPos, originNamespace, context, force_id, &memberMask);
  gridP->vtable->unpackArrays(gridP, memberMask, unpackBuffer, unpackBufferSize, unpackBufferPos, originNamespace, context);
  reshSetStatus(gridP->self, &gridOps, reshGetStatus(gridP->self, &gridOps) & ~RESH_SYNC_BIT);
  return gridP->self;
}

static grid_t *
gridUnpackScalars(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id,
                  int *memberMaskP)
{
  grid_t *gridP;
  uint32_t d;
  int memberMask;
  {
    int intBuffer[gridNint];
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, intBuffer, gridNint, CDI_DATATYPE_INT, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);

    xassert(cdiCheckSum(CDI_DATATYPE_INT, gridNint, intBuffer) == d);
    int targetID = namespaceAdaptKey(intBuffer[0], originNamespace);
    gridP = gridNewEntry(force_id ? targetID : CDI_UNDEFID);

    xassert(!force_id || targetID == gridP->self);

    gridP->type = intBuffer[GRID_PACK_INT_IDX_TYPE];
    gridP->isCyclic = (signed char) intBuffer[GRID_PACK_INT_IDX_IS_CYCLIC];
    gridP->x.flag = (short) intBuffer[GRID_PACK_INT_IDX_X_FLAG];
    gridP->y.flag = (short) intBuffer[GRID_PACK_INT_IDX_Y_FLAG];
    gridP->gme.nd = intBuffer[GRID_PACK_INT_IDX_GME_ND];
    gridP->gme.ni = intBuffer[GRID_PACK_INT_IDX_GME_NI];
    gridP->gme.ni2 = intBuffer[GRID_PACK_INT_IDX_GME_NI2];
    gridP->gme.ni3 = intBuffer[GRID_PACK_INT_IDX_GME_NI3];
    gridP->trunc = intBuffer[GRID_PACK_INT_IDX_TRUNC];
    gridP->nvertex = intBuffer[GRID_PACK_INT_IDX_NVERTEX];
    gridP->reducedPointsSize = intBuffer[GRID_PACK_INT_IDX_REDUCED_POINTS_SIZE];
    /* FIXME: use int64_t for the following 3 */
    gridP->size = (size_t) intBuffer[GRID_PACK_INT_IDX_SIZE];
    gridP->x.size = (size_t) intBuffer[GRID_PACK_INT_IDX_X_SIZE];
    gridP->y.size = (size_t) intBuffer[GRID_PACK_INT_IDX_Y_SIZE];
    gridP->lcomplex = (bool) intBuffer[GRID_PACK_INT_IDX_LCOMPLEX];
    memberMask = intBuffer[GRID_PACK_INT_IDX_MEMBERMASK];
  }

  {
    double doubleBuffer[gridNdouble];
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, doubleBuffer, gridNdouble, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(d == cdiCheckSum(CDI_DATATYPE_FLT, gridNdouble, doubleBuffer));

    gridP->x.first = doubleBuffer[GRID_PACK_DBL_IDX_X_FIRST];
    gridP->y.first = doubleBuffer[GRID_PACK_DBL_IDX_Y_FIRST];
    gridP->x.last = doubleBuffer[GRID_PACK_DBL_IDX_X_LAST];
    gridP->y.last = doubleBuffer[GRID_PACK_DBL_IDX_Y_LAST];
    gridP->x.inc = doubleBuffer[GRID_PACK_DBL_IDX_X_INC];
    gridP->y.inc = doubleBuffer[GRID_PACK_DBL_IDX_Y_INC];
  }

  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &gridP->keys, context);
  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &gridP->x.keys, context);
  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &gridP->y.keys, context);

  *memberMaskP = memberMask;
  return gridP;
}

static void
gridUnpackArrays(grid_t *gridP, int memberMask, char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace,
                 void *context)
{
  UNUSED(originNamespace);
  uint32_t d;

  if (memberMask & gridHasReducedPointsFlag)
  {
    xassert(gridP->reducedPointsSize);
    gridP->reducedPoints = (int *) Malloc((size_t) gridP->reducedPointsSize * sizeof(int));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->reducedPoints, gridP->reducedPointsSize,
                    CDI_DATATYPE_INT, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_INT, gridP->reducedPointsSize, gridP->reducedPoints) == d);
  }

  bool isIrregular = grid_is_irregular(gridP->type);
  if (memberMask & gridHasXValsFlag)
  {
    size_t size = isIrregular ? gridP->size : gridP->x.size;
    xassert(size <= INT_MAX);
    gridP->x.vals = (double *) Malloc(size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->x.vals, (int) size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->x.vals) == d);
  }

  if (memberMask & gridHasYValsFlag)
  {
    size_t size = isIrregular ? gridP->size : gridP->y.size;
    xassert(size > 0 && size <= INT_MAX);
    gridP->y.vals = (double *) Malloc(size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->y.vals, (int) size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->y.vals) == d);
  }

  if (memberMask & gridHasAreaFlag)
  {
    size_t size = gridP->size;
    xassert(size > 0 && size <= INT_MAX);
    gridP->area = (double *) Malloc(size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->area, (int) size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->area) == d);
  }

  if (memberMask & gridHasXBoundsFlag)
  {
    size_t size = (size_t) gridP->nvertex * (isIrregular ? gridP->size : gridP->x.size);
    xassert(size > 0 && size <= INT_MAX);

    gridP->x.bounds = (double *) Malloc(size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->x.bounds, (int) size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->x.bounds) == d);
  }

  if (memberMask & gridHasYBoundsFlag)
  {
    size_t size = (size_t) gridP->nvertex * (isIrregular ? gridP->size : gridP->y.size);
    xassert(size && size <= INT_MAX);
    gridP->y.bounds = (double *) Malloc(size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->y.bounds, (int) size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->y.bounds) == d);
  }

  if (memberMask & gridHasMaskFlag)
  {
    size_t size = gridP->size;
    xassert(size && size <= INT_MAX);
    gridP->mask = (mask_t *) Malloc(size * sizeof(mask_t));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->mask, (int) size, CDI_DATATYPE_UCHAR, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_UCHAR, (int) size, gridP->mask) == d);
  }

  if (memberMask & gridHasGMEMaskFlag)
  {
    size_t size = gridP->size;
    xassert(size && size <= INT_MAX);
    gridP->mask_gme = (mask_t *) Malloc(size * sizeof(mask_t));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, gridP->mask_gme, (int) size, CDI_DATATYPE_UCHAR, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_UCHAR, (int) size, gridP->mask_gme) == d);
  }
}

void
gridPack(void *voidP, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  grid_t *gridP = (grid_t *) voidP;
  gridP->vtable->pack(gridP, packBuffer, packBufferSize, packBufferPos, context);
}

static void
gridPackBase(grid_t *gridP, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  int memberMask = gridP->vtable->packScalars(gridP, packBuffer, packBufferSize, packBufferPos, context);
  gridP->vtable->packArrays(gridP, memberMask, packBuffer, packBufferSize, packBufferPos, context);
}

static int
gridPackScalars(grid_t *gridP, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  uint32_t d;
  int memberMask;

  {
    int intBuffer[gridNint];

    intBuffer[GRID_PACK_INT_IDX_SELF] = gridP->self;
    intBuffer[GRID_PACK_INT_IDX_TYPE] = gridP->type;
    intBuffer[GRID_PACK_INT_IDX_IS_CYCLIC] = gridP->isCyclic;
    intBuffer[GRID_PACK_INT_IDX_X_FLAG] = gridP->x.flag;
    intBuffer[GRID_PACK_INT_IDX_Y_FLAG] = gridP->y.flag;
    intBuffer[GRID_PACK_INT_IDX_GME_ND] = gridP->gme.nd;
    intBuffer[GRID_PACK_INT_IDX_GME_NI] = gridP->gme.ni;
    intBuffer[GRID_PACK_INT_IDX_GME_NI2] = gridP->gme.ni2;
    intBuffer[GRID_PACK_INT_IDX_GME_NI3] = gridP->gme.ni3;
    intBuffer[GRID_PACK_INT_IDX_TRUNC] = gridP->trunc;
    intBuffer[GRID_PACK_INT_IDX_NVERTEX] = gridP->nvertex;
    intBuffer[GRID_PACK_INT_IDX_REDUCED_POINTS_SIZE] = gridP->reducedPointsSize;
    xassert(gridP->size <= INT_MAX && gridP->x.size <= INT_MAX && gridP->y.size <= INT_MAX);
    intBuffer[GRID_PACK_INT_IDX_SIZE] = (int) gridP->size;
    intBuffer[GRID_PACK_INT_IDX_X_SIZE] = (int) gridP->x.size;
    intBuffer[GRID_PACK_INT_IDX_Y_SIZE] = (int) gridP->y.size;
    intBuffer[GRID_PACK_INT_IDX_LCOMPLEX] = gridP->lcomplex;
    intBuffer[GRID_PACK_INT_IDX_MEMBERMASK] = memberMask = gridGetComponentFlags(gridP);

    serializePack(intBuffer, gridNint, CDI_DATATYPE_INT, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_INT, gridNint, intBuffer);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  {
    double doubleBuffer[gridNdouble];

    doubleBuffer[GRID_PACK_DBL_IDX_X_FIRST] = gridP->x.first;
    doubleBuffer[GRID_PACK_DBL_IDX_Y_FIRST] = gridP->y.first;
    doubleBuffer[GRID_PACK_DBL_IDX_X_LAST] = gridP->x.last;
    doubleBuffer[GRID_PACK_DBL_IDX_Y_LAST] = gridP->y.last;
    doubleBuffer[GRID_PACK_DBL_IDX_X_INC] = gridP->x.inc;
    doubleBuffer[GRID_PACK_DBL_IDX_Y_INC] = gridP->y.inc;

    serializePack(doubleBuffer, gridNdouble, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, gridNdouble, doubleBuffer);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  serializeKeysPack(&gridP->keys, packBuffer, packBufferSize, packBufferPos, context);
  serializeKeysPack(&gridP->x.keys, packBuffer, packBufferSize, packBufferPos, context);
  serializeKeysPack(&gridP->y.keys, packBuffer, packBufferSize, packBufferPos, context);

  return memberMask;
}

static void
gridPackArrays(grid_t *gridP, int memberMask, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  uint32_t d;
  bool isIrregular = grid_is_irregular(gridP->type);

  if (memberMask & gridHasReducedPointsFlag)
  {
    size_t size = (size_t) gridP->reducedPointsSize;
    xassert(size > 0 && size <= INT_MAX);
    serializePack(gridP->reducedPoints, (int) size, CDI_DATATYPE_INT, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_INT, (int) size, gridP->reducedPoints);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasXValsFlag)
  {
    size_t size = isIrregular ? gridP->size : gridP->x.size;
    xassert(size && size <= INT_MAX);

    const double *gridP_xvals = gridP->vtable->inqXValsPtr(gridP);
    serializePack(gridP_xvals, (int) size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP_xvals);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasYValsFlag)
  {
    size_t size = isIrregular ? gridP->size : gridP->y.size;
    xassert(size && size <= INT_MAX);
    const double *gridP_yvals = gridP->vtable->inqYValsPtr(gridP);
    serializePack(gridP_yvals, (int) size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP_yvals);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasAreaFlag)
  {
    size_t size = gridP->size;
    xassert(size && size <= INT_MAX);
    serializePack(gridP->area, (int) size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->area);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasXBoundsFlag)
  {
    size_t size = (isIrregular ? gridP->size : gridP->x.size) * (size_t) gridP->nvertex;
    xassert(size && size <= INT_MAX);
    serializePack(gridP->x.bounds, (int) size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->x.bounds);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasYBoundsFlag)
  {
    size_t size = (isIrregular ? gridP->size : gridP->y.size) * (size_t) gridP->nvertex;
    xassert(size && size <= INT_MAX);
    serializePack(gridP->y.bounds, (int) size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, (int) size, gridP->y.bounds);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasMaskFlag)
  {
    size_t size = gridP->size;
    xassert(size && size <= INT_MAX);
    serializePack(gridP->mask, (int) size, CDI_DATATYPE_UCHAR, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_UCHAR, (int) size, gridP->mask);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & gridHasGMEMaskFlag)
  {
    size_t size = gridP->size;
    xassert(size && size <= INT_MAX);
    serializePack(gridP->mask_gme, (int) size, CDI_DATATYPE_UCHAR, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_UCHAR, (int) size, gridP->mask_gme);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }
}

struct gridCompareSearchState
{
  int resIDValue;
  const grid_t *queryKey;
};

static enum cdiApplyRet
gridCompareSearch(int id, void *res, void *data)
{
  struct gridCompareSearchState *state = (struct gridCompareSearchState *) data;
  (void) res;
  if (gridCompare(id, state->queryKey, true) == false)
  {
    state->resIDValue = id;
    return CDI_APPLY_STOP;
  }
  else
    return CDI_APPLY_GO_ON;
}

// Add grid (which must be Malloc'ed to vlist if not already found)
struct addIfNewRes
cdiVlistAddGridIfNew(int vlistID, grid_t *grid, int mode)
{
  /*
    mode: 0 search in vlist and grid table
          1 search in grid table only
          2 search in grid table only and don't store the grid in vlist
   */
  bool gridIsDefinedGlobal = false;
  bool gridIsDefined = false;
  int gridID = CDI_UNDEFID;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int ngrids = vlistptr->ngrids;

  if (mode == 0)
    for (int index = 0; index < ngrids; index++)
    {
      if ((gridID = vlistptr->gridIDs[index]) != CDI_UNDEFID)
      {
        if (gridCompare(gridID, grid, false) == false)
        {
          gridIsDefined = true;
          break;
        }
      }
      else
        Error("Internal problem: undefined gridID in vlist %d, position %u!", vlistID, index);
    }

  if (!gridIsDefined)
  {
    struct gridCompareSearchState query;
    query.queryKey = grid;  // = { .queryKey = grid };
    if ((gridIsDefinedGlobal = (cdiGridApply(gridCompareSearch, &query) == CDI_APPLY_STOP))) gridID = query.resIDValue;

    if (mode == 1 && gridIsDefinedGlobal)
      for (int index = 0; index < ngrids; index++)
        if (vlistptr->gridIDs[index] == gridID)
        {
          gridIsDefinedGlobal = false;
          break;
        }
  }

  if (!gridIsDefined)
  {
    if (!gridIsDefinedGlobal)
    {
      grid->self = gridID = reshPut(grid, &gridOps);
      grid_complete(grid);
    }
    if (mode < 2)
    {
      if (ngrids >= MAX_GRIDS_PS) Error("Internal limit exceeded, MAX_GRIDS_PS=%d needs to be increased!", MAX_GRIDS_PS);
      vlistptr->gridIDs[ngrids] = gridID;
      vlistptr->ngrids++;
    }
  }

  return (struct addIfNewRes){ .Id = gridID, .isNew = (!gridIsDefined && !gridIsDefinedGlobal) };
}

const struct gridVirtTable cdiGridVtable = {
  .destroy = gridDestroyKernel,
  .copy = grid_copy_base,
  .copyScalarFields = grid_copy_base_scalar_fields,
  .copyArrayFields = grid_copy_base_array_fields,
  .defIndices = gridDefIndicesSerial,
  .inqIndices = gridInqIndicesSerial,
  .inqIndicesPtr = gridInqIndicesPtrSerial,
  .defXVals = gridDefXValsSerial,
  .defYVals = gridDefYValsSerial,
  .defMask = gridDefMaskSerial,
  .defMaskGME = gridDefMaskGMESerial,
  .defXBounds = gridDefXBoundsSerial,
  .defYBounds = gridDefYBoundsSerial,
  .defArea = gridDefAreaSerial,
  .inqXVal = gridInqXValSerial,
  .inqYVal = gridInqYValSerial,
  .inqXVals = gridInqXValsSerial,
  .inqXValsPart = gridInqXValsPartSerial,
  .inqYVals = gridInqYValsSerial,
  .inqYValsPart = gridInqYValsPartSerial,
  .inqXValsPtr = gridInqXValsPtrSerial,
  .inqYValsPtr = gridInqYValsPtrSerial,
#ifndef USE_MPI
  .inqXIsc = gridInqXIscSerial,
  .inqYIsc = gridInqYIscSerial,
  .inqXCvals = gridInqXCvalsSerial,
  .inqYCvals = gridInqYCvalsSerial,
  .inqXCvalsPtr = gridInqXCvalsPtrSerial,
  .inqYCvalsPtr = gridInqYCvalsPtrSerial,
#endif
  .inqXInc = gridInqXIncBase,
  .inqYInc = gridInqYIncBase,
  .compareXYFull = compareXYvals,
  .compareXYAO = compareXYvals2,
  .inqArea = gridInqAreaSerial,
  .inqAreaPtr = gridInqAreaPtrBase,
  .inqPropPresence = gridInqPropPresenceBase,
  .inqMask = gridInqMaskSerial,
  .inqMaskGME = gridInqMaskGMESerial,
  .inqXBounds = gridInqXBoundsSerial,
  .inqYBounds = gridInqYBoundsSerial,
  .inqXBoundsPtr = gridInqXBoundsPtrSerial,
  .inqYBoundsPtr = gridInqYBoundsPtrSerial,
  .txCode = GRID,
  .getPackSize = gridGetPackSizeBase,
  .getPackSizeScalars = gridGetPackSizeScalars,
  .getPackSizeArrays = gridGetPackSizeArrays,
  .unpackScalars = gridUnpackScalars,
  .unpackArrays = gridUnpackArrays,
  .pack = gridPackBase,
  .packScalars = gridPackScalars,
  .packArrays = gridPackArrays,
};

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>
#include <string.h>
#include <ctype.h>


static int initIegLib = 0;
static int iegDefaultDprec = 0;

// A version string.
#undef LIBVERSION
#define LIBVERSION 2.0.0
#define XSTRING(x) #x
#define STRING(x) XSTRING(x)
static const char ieg_libvers[] = STRING(LIBVERSION);

const char *
iegLibraryVersion(void)
{
  return ieg_libvers;
}

static int IEG_Debug = 0;  // If set to 1, debugging

static void
iegLibInit(void)
{
  const char *envName = "IEG_PRECISION";

  char *envString = getenv(envName);
  if (envString)
  {
    int nrun = (strlen(envString) == 2) ? 1 : 2;
    int pos = 0;
    while (nrun--)
    {
      switch (tolower((int) envString[pos]))
      {
        case 'r':
        {
          switch ((int) envString[pos + 1])
          {
            case '4': iegDefaultDprec = EXSE_PREC_FP32; break;
            case '8': iegDefaultDprec = EXSE_PREC_FP64; break;
            default: Warning("Invalid digit in %s: %s", envName, envString);
          }
          break;
        }
        default:
        {
          Warning("Invalid character in %s: %s", envName, envString);
          break;
        }
      }
      pos += 2;
    }
  }

  initIegLib = 1;
}

void
iegDebug(int debug)
{
  if (debug) Message("debug level %d", debug);
  IEG_Debug = debug;
}

static void
iegInit(iegrec_t *iegp)
{
  iegp->checked = 0;
  iegp->byteswap = 0;
  iegp->dprec = 0;
  iegp->refval = 0;
  iegp->datasize = 0;
  iegp->buffersize = 0;
  iegp->buffer = NULL;
}

void
iegInitMem(void *ieg)
{
  iegrec_t *iegp = (iegrec_t *) ieg;
  memset(iegp->ipdb, 0, sizeof(iegp->ipdb));
  memset(iegp->igdb, 0, sizeof(iegp->igdb));
  memset(iegp->vct, 0, sizeof(iegp->vct));
}

void *
iegNew(void)
{
  if (!initIegLib) iegLibInit();

  iegrec_t *iegp = (iegrec_t *) Malloc(sizeof(iegrec_t));
  iegInit(iegp);
  iegInitMem(iegp);

  return (void *) iegp;
}

void
iegDelete(void *ieg)
{
  iegrec_t *iegp = (iegrec_t *) ieg;

  if (iegp)
  {
    if (iegp->buffer) Free(iegp->buffer);
    Free(iegp);
  }
}

int
iegCheckFiletype(int fileID, int *swap)
{
  size_t data = 0;
  size_t dimx = 0, dimy = 0;
  size_t fact = 0;
  unsigned char buffer[1048], *pbuf;

  if (fileRead(fileID, buffer, 4) != 4) return 0;

  size_t blocklen = get_uint32(buffer);
  size_t sblocklen = get_swap_uint32(buffer);

  if (IEG_Debug) Message("blocklen = %d sblocklen = %d", blocklen, sblocklen);

  // clang-format off
  if (blocklen == 636 || blocklen == 640)
    {
     *swap = 0;
      fact = 4;
      if (fileRead(fileID, buffer, blocklen+8) != blocklen+8) return 0;
      pbuf = buffer+(37+4)*4;    dimx = (size_t) get_uint32(pbuf);
      pbuf = buffer+(37+5)*4;    dimy = (size_t) get_uint32(pbuf);
      pbuf = buffer+blocklen+4;  data = (size_t) get_uint32(pbuf);
    }
  else if (blocklen == 1040 || blocklen == 1036)
    {
     *swap = 0;
      fact = 8;
      if (fileRead(fileID, buffer, blocklen+8) != blocklen+8) return 0;
      pbuf = buffer+(37+4)*4;    dimx = (size_t) get_uint32(pbuf);
      pbuf = buffer+(37+5)*4;    dimy = (size_t) get_uint32(pbuf);
      pbuf = buffer+blocklen+4;  data = (size_t) get_uint32(pbuf);
    }
  else if (sblocklen == 636 || sblocklen == 640)
    {
     *swap = 1;
      fact = 4;
      if (fileRead(fileID, buffer, sblocklen+8) != sblocklen+8) return 0;
      pbuf = buffer+(37+4)*4;     dimx = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+(37+5)*4;     dimy = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_swap_uint32(pbuf);
    }
  else if (sblocklen == 1040 || sblocklen == 1036)
    {
     *swap = 1;
      fact = 8;
      if (fileRead(fileID, buffer, sblocklen+8) != sblocklen+8) return 0;
      pbuf = buffer+(37+4)*4;     dimx = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+(37+5)*4;     dimy = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_swap_uint32(pbuf);
    }
  // clang-format on

  fileRewind(fileID);

  if (IEG_Debug) Message("swap = %d fact = %d", *swap, fact);
  if (IEG_Debug) Message("dimx = %lu dimy = %lu data = %lu", dimx, dimy, data);

  int found = data && (dimx * dimy * fact == data || dimx * dimy * 8 == data);
  return found;
}

void
iegCopyMeta(void *dieg, void *sieg)
{
  iegrec_t *diegp = (iegrec_t *) dieg;
  iegrec_t *siegp = (iegrec_t *) sieg;

  // diegp->byteswap = siegp->byteswap;
  diegp->dprec = siegp->dprec;
  diegp->refval = siegp->refval;

  memcpy(diegp->ipdb, siegp->ipdb, sizeof(siegp->ipdb));
  memcpy(diegp->igdb, siegp->igdb, sizeof(siegp->igdb));
  memcpy(diegp->vct, siegp->vct, sizeof(siegp->vct));
}

static int
iegInqData(void *ieg, int prec, void *data)
{
  iegrec_t *iegp = (iegrec_t *) ieg;
  int ierr = 0;
  int byteswap = iegp->byteswap;
  size_t datasize = iegp->datasize;
  void *buffer = iegp->buffer;
  int dprec = iegp->dprec;

  switch (dprec)
  {
    case EXSE_PREC_FP32:
    {
      if (byteswap) swap4byte(buffer, datasize);

      if (dprec == prec)
        memcpy(data, buffer, datasize * sizeof(float));
      else
      {
        const float *restrict p = (float *) buffer;
        double *restrict q = (double *) data;
        for (size_t i = 0; i < datasize; i++) q[i] = p[i];
      }

      break;
    }
    case EXSE_PREC_FP64:
    {
      if (byteswap) swap8byte(buffer, datasize);

      if (dprec == prec)
        memcpy(data, buffer, datasize * sizeof(double));
      else
      {
        const double *restrict p = (double *) buffer;
        float *restrict q = (float *) data;
        for (size_t i = 0; i < datasize; i++) q[i] = (float) p[i];
      }

      break;
    }
    default:
    {
      Error("unexpected data precision %d", dprec);
      break;
    }
  }

  return ierr;
}

int
iegInqDataFP32(void *ieg, float *data)
{
  return iegInqData(ieg, EXSE_PREC_FP32, (void *) data);
}

int
iegInqDataFP64(void *ieg, double *data)
{
  return iegInqData(ieg, EXSE_PREC_FP64, (void *) data);
}

static int
iegDefData(iegrec_t *iegp, int prec, const void *data)
{
  int dprec = iegDefaultDprec ? iegDefaultDprec : iegp->dprec;
  iegp->dprec = dprec ? dprec : prec;

  size_t datasize = (size_t) IEG_G_NumLon(iegp->igdb) * (size_t) IEG_G_NumLat(iegp->igdb);
  size_t blocklen = datasize * (size_t) dprec;

  iegp->datasize = datasize;

  if (iegp->buffersize != blocklen)
  {
    iegp->buffersize = blocklen;
    iegp->buffer = Realloc(iegp->buffer, iegp->buffersize);
  }

  switch (dprec)
  {
    case EXSE_PREC_FP32:
    {
      if (dprec == prec)
        memcpy(iegp->buffer, data, datasize * sizeof(float));
      else
      {
        const double *restrict p = (const double *) data;
        float *restrict q = (float *) iegp->buffer;
        for (size_t i = 0; i < datasize; i++) q[i] = (float) p[i];
      }
      break;
    }
    case EXSE_PREC_FP64:
    {
      if (dprec == prec)
        memcpy(iegp->buffer, data, datasize * sizeof(double));
      else
      {
        const float *restrict p = (const float *) data;
        double *restrict q = (double *) iegp->buffer;
        for (size_t i = 0; i < datasize; i++) q[i] = p[i];
      }
      break;
    }
    default:
    {
      Error("unexpected data precision %d", dprec);
      break;
    }
  }

  return 0;
}

int
iegDefDataFP32(void *ieg, const float *data)
{
  return iegDefData((iegrec_t *) ieg, EXSE_PREC_FP32, (void *) data);
}

int
iegDefDataFP64(void *ieg, const double *data)
{
  return iegDefData((iegrec_t *) ieg, EXSE_PREC_FP64, (void *) data);
}

int
iegRead(int fileID, void *ieg)
{
  iegrec_t *iegp = (iegrec_t *) ieg;
  union
  {
    double d[200];
    float f[200];
    int32_t i32[200];
  } buf;

  if (!iegp->checked)
  {
    int status = iegCheckFiletype(fileID, &iegp->byteswap);
    if (status == 0) Error("Not a IEG file!");
    iegp->checked = 1;
  }

  int byteswap = iegp->byteswap;

  // read header record
  size_t blocklen = binReadF77Block(fileID, byteswap);

  if (fileEOF(fileID)) return -1;

  if (IEG_Debug) Message("blocklen = %lu", blocklen);

  int dprec = 0;
  if (blocklen == 636 || blocklen == 640)
    dprec = 4;
  else if (blocklen == 1040 || blocklen == 1036)
    dprec = 8;
  else
  {
    Warning("unexpecteted header size %d!", (int) blocklen);
    return -1;
  }

  iegp->dprec = dprec;

  binReadInt32(fileID, byteswap, 37, buf.i32);
  for (int i = 0; i < 37; i++) iegp->ipdb[i] = (int) buf.i32[i];

  binReadInt32(fileID, byteswap, 18, buf.i32);
  for (int i = 0; i < 18; i++) iegp->igdb[i] = (int) buf.i32[i];

  if (blocklen == 636 || blocklen == 1036)
  {
    fileRead(fileID, buf.f, 4);
    if (byteswap) swap4byte(buf.f, 1);
    iegp->refval = (double) buf.f[0];
  }
  else
  {
    fileRead(fileID, buf.d, 8);
    if (byteswap) swap8byte(buf.d, 1);
    iegp->refval = (double) buf.d[0];
  }

  binReadInt32(fileID, byteswap, 3, buf.i32);
  for (int i = 0; i < 3; i++) iegp->igdb[18 + i] = (int) buf.i32[i];

  if (dprec == EXSE_PREC_FP32)
  {
    fileRead(fileID, buf.f, 400);
    if (byteswap) swap4byte(buf.f, 100);
    for (int i = 0; i < 100; i++) iegp->vct[i] = (double) buf.f[i];
  }
  else
  {
    fileRead(fileID, buf.d, 800);
    if (byteswap) swap8byte(buf.d, 100);
    for (int i = 0; i < 100; i++) iegp->vct[i] = buf.d[i];
  }

  size_t blocklen2 = binReadF77Block(fileID, byteswap);

  if (blocklen2 != blocklen)
  {
    Warning("header blocklen differ!");
    return -1;
  }

  iegp->datasize = (size_t) IEG_G_NumLon(iegp->igdb) * (size_t) IEG_G_NumLat(iegp->igdb);

  if (IEG_Debug) Message("datasize = %zu", iegp->datasize);

  blocklen = binReadF77Block(fileID, byteswap);

  if (iegp->buffersize < blocklen)
  {
    iegp->buffer = Realloc(iegp->buffer, blocklen);
    iegp->buffersize = blocklen;
  }

  if (dprec != (int) (blocklen / iegp->datasize))
  {
    Warning("data precision differ! (h = %d; d = %d)", (int) dprec, (int) (blocklen / iegp->datasize));
    return -1;
  }

  fileRead(fileID, iegp->buffer, blocklen);

  blocklen2 = binReadF77Block(fileID, byteswap);

  if (blocklen2 != blocklen)
  {
    Warning("data blocklen differ!");
    return -1;
  }

  return 0;
}

int
iegWrite(int fileID, void *ieg)
{
  iegrec_t *iegp = (iegrec_t *) ieg;
  union
  {
    int32_t i32[200];
    float fvct[100];
  } buf;
  int dprec = iegp->dprec;
  int byteswap = iegp->byteswap;

  // write header record
  size_t blocklen = (dprec == EXSE_PREC_FP32) ? 636 : 1040;

  binWriteF77Block(fileID, byteswap, blocklen);

  for (int i = 0; i < 37; i++) buf.i32[i] = (int32_t) iegp->ipdb[i];
  binWriteInt32(fileID, byteswap, 37, buf.i32);

  for (int i = 0; i < 18; i++) buf.i32[i] = (int32_t) iegp->igdb[i];
  binWriteInt32(fileID, byteswap, 18, buf.i32);

  double refval = (double) iegp->refval;
  float refvalf = (float) iegp->refval;
  if (dprec == EXSE_PREC_FP32)
    binWriteFlt32(fileID, byteswap, 1, &refvalf);
  else
    binWriteFlt64(fileID, byteswap, 1, &refval);

  for (int i = 0; i < 3; i++) buf.i32[i] = (int32_t) iegp->igdb[18 + i];
  binWriteInt32(fileID, byteswap, 3, buf.i32);

  if (dprec == EXSE_PREC_FP32)
  {
    for (int i = 0; i < 100; i++) buf.fvct[i] = (float) iegp->vct[i];
    binWriteFlt32(fileID, byteswap, 100, buf.fvct);
  }
  else { binWriteFlt64(fileID, byteswap, 100, iegp->vct); }

  binWriteF77Block(fileID, byteswap, blocklen);

  iegp->datasize = (size_t) iegp->igdb[4] * (size_t) iegp->igdb[5];
  blocklen = iegp->datasize * (size_t) dprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  switch (dprec)
  {
    case EXSE_PREC_FP32:
    {
      binWriteFlt32(fileID, byteswap, iegp->datasize, (float *) iegp->buffer);
      break;
    }
    case EXSE_PREC_FP64:
    {
      binWriteFlt64(fileID, byteswap, iegp->datasize, (double *) iegp->buffer);
      break;
    }
    default:
    {
      Error("unexpected data precision %d", dprec);
      break;
    }
  }

  binWriteF77Block(fileID, byteswap, blocklen);

  return 0;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef INCLUDE_GUARD_CDI_REFERENCE_COUNTING
#define INCLUDE_GUARD_CDI_REFERENCE_COUNTING


#include <sys/types.h>
#include <stdlib.h>

/*
This is a base class for all objects that need reference counting.
A CdiReferencedObject has a reference count of one when it is constructed, refObjectRetain() increments the reference count,
refObject Release() decrements it. When the reference count reaches zero, the destructor function is called before the memory of the
object is deallocated with Free().

>>> Warning <<<
This code is currently not thread-safe.

We are currently using the C99 standard, which does not have atomic types.
Also, there are still tons of systems out there that have a gcc without wrong C11 atomics support
(__STDC_NO_ATOMICS__ not defined even though stdatomics.h is not even present).
Consequently, it is impossible to write preprocessor code to even check for the presence of atomic types.
So, we have two options: provide multithreading support by means of locks, or wait a year or two before doing this right.
I, for one, prefer doing things right.
*/
typedef struct CdiReferencedObject CdiReferencedObject;
struct CdiReferencedObject
{
  // protected:
  void (*destructor)(CdiReferencedObject *me);  // Subclass constructors should set this to their own destructor.

  // private:    //Subclasses may read it to determine whether there is only one reference, though.
  size_t refCount;
};

void cdiRefObject_construct(CdiReferencedObject *me);
void cdiRefObject_retain(CdiReferencedObject *me);
void cdiRefObject_release(CdiReferencedObject *me);
void cdiRefObject_destruct(CdiReferencedObject *me);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef INCLUDE_GUARD_CDI_GRIB_FILE_H
#define INCLUDE_GUARD_CDI_GRIB_FILE_H


/*
CdiInputFile is a file abstraction that allows accessing an input file through any number of channels:
It is reference counted, so that it is closed at the right place,
and it is stateless, so that accesses from different callers cannot interfere with each other.
Once the reference counting code is threadsafe, CdiInputFile will also be threadsafe.
*/
typedef struct CdiInputFile
{
  // public:
  CdiReferencedObject super;

  // private:
  char *path;
  int fileDescriptor;
} CdiInputFile;

// Final class, the constructor is private and not defined here.
CdiInputFile *
cdiInputFile_make(const char *path);  // The caller is responsible to call cdiRefObject_release() on the returned object.
int cdiInputFile_read(const CdiInputFile *me, off_t readPosition, size_t readSize, size_t *outActualReadSize,
                      void *buffer);  // Returns one of CDI_EINVAL, CDI_ESYSTEM, CDI_EEOF, OR CDI_NOERR.
/* Returns path string, don't use after destruction of CdiInputFile
 * object */
const char *cdiInputFile_getPath(const CdiInputFile *me);
// Destructor is private as well.

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#define _XOPEN_SOURCE 600


#include <errno.h>
#include <fcntl.h>
#include <string.h>

// On Windows, define ssize_t and pread manually
#ifdef _WIN32
#define ssize_t __int64
#define pread read
#include <io.h>
#else
#include <unistd.h>
#endif

#if HAVE_LIBPTHREAD
#include <pthread.h>
#endif

static void cdiInputFile_destruct(CdiInputFile *me);

// For an explanation of the condestruct() pattern, see the comment in iterator_grib.c
// path != NULL -> construction
// path = NULL -> destruction
static CdiInputFile *
cdiInputFile_condestruct(CdiInputFile *me, const char *path)
{
#define super() (&me->super)
  if (!path) goto destruct;
  cdiRefObject_construct(super());
  me->path = strdup(path);
  if (!me->path) goto destructSuper;
  do {
    me->fileDescriptor = open(me->path, O_RDONLY);
  } while (me->fileDescriptor == -1 && (errno == EINTR || errno == EAGAIN));
  if (me->fileDescriptor == -1) goto freePath;
  // construction successfull, now we can set our own destructor
  super()->destructor = (void (*)(CdiReferencedObject *)) cdiInputFile_destruct;
  goto success;

  // ^        constructor code       ^
  // |                               |
  // v destructor/error-cleanup code v

destruct:
  close(me->fileDescriptor);
freePath:
  Free(me->path);
destructSuper:
  cdiRefObject_destruct(super());
  me = NULL;

success:
  return me;
#undef super
}

static CdiInputFile **openFileList = NULL;
static size_t openFileCount = 0, openFileListSize = 0;
#if HAVE_LIBPTHREAD
static pthread_mutex_t openFileListLock = PTHREAD_MUTEX_INITIALIZER;
#endif

// This either returns a new object, or retains and returns a preexisting open file.
CdiInputFile *
cdiInputFile_make(const char *path)
{
  CdiInputFile *result = NULL;
  xassert(path);
#if HAVE_LIBPTHREAD
  int error = pthread_mutex_lock(&openFileListLock);
  xassert(!error);
#endif
  {
    // Check the list of open files for the given path.
    for (size_t i = openFileCount; i-- && !result;)
    {
      if (!strcmp(path, openFileList[i]->path)) result = openFileList[i];
    }
    // If no open file was found, we open one, otherwise we just retain the existing one one more time.
    if (result) { cdiRefObject_retain(&result->super); }
    else
    {
      result = (CdiInputFile *) Malloc(sizeof(*result));
      if (!cdiInputFile_condestruct(result, path))
      {
        // An error occured during construction, avoid a memory leak.
        Free(result);
        result = NULL;
      }
      else
      {
        // Add the new file to the list of open files.
        if (openFileCount == openFileListSize)
        {
          openFileListSize *= 2;
          if (openFileListSize < 16) openFileListSize = 16;
          openFileList = (CdiInputFile **) Realloc(openFileList, openFileListSize);
        }
        xassert(openFileCount < openFileListSize);
        openFileList[openFileCount++] = result;
      }
    }
  }
#if HAVE_LIBPTHREAD
  error = pthread_mutex_unlock(&openFileListLock);
  xassert(!error);
#endif
  return result;
}

int
cdiInputFile_read(const CdiInputFile *me, off_t readPosition, size_t readSize, size_t *outActualReadSize, void *buffer)
{
  char *byteBuffer = (char *) buffer;
  size_t trash;
  if (!outActualReadSize) outActualReadSize = &trash;
  *outActualReadSize = 0;
  while (readSize)
  {
    ssize_t bytesRead = pread(me->fileDescriptor, byteBuffer, readSize, readPosition);
    if (bytesRead == -1) return (errno == EINVAL) ? CDI_EINVAL : CDI_ESYSTEM;
    if (bytesRead == 0) return CDI_EEOF;
    byteBuffer += bytesRead;
    readPosition += bytesRead;
    readSize -= (size_t) bytesRead;
    *outActualReadSize += (size_t) bytesRead;
  }
  return CDI_NOERR;
}

const char *
cdiInputFile_getPath(const CdiInputFile *me)
{
  return me->path;
}

void
cdiInputFile_destruct(CdiInputFile *me)
{
#if HAVE_LIBPTHREAD
  int error = pthread_mutex_lock(&openFileListLock);
  xassert(!error);
#endif
  {
    // Find the position of me in the list of open files.
    ssize_t position = (ssize_t) openFileCount;
    while (position > 0 && openFileList[--position] != me)
      ;
    // Remove me from the list
    openFileList[position] = openFileList[--openFileCount];
  }
#if HAVE_LIBPTHREAD
  error = pthread_mutex_unlock(&openFileListLock);
  xassert(!error);
#endif
  cdiInputFile_condestruct(me, NULL);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef INSTITUTION_H
#define INSTITUTION_H

int instituteUnpack(void *buf, int size, int *position, int originNamespace, void *context, int force_id);

void instituteDefaultEntries(void);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <assert.h>
#include <stdlib.h>
#include <limits.h>


typedef struct
{
  int self;
  int center;
  int subcenter;
  char *name;
  char *longname;
} institute_t;

static int instituteCompareKernel(institute_t *ip1, institute_t *ip2);
static void instituteDestroyP(institute_t *instituteptr);
static void institutePrintP(institute_t *instituteptr, FILE *fp);
static int instituteGetPackSize(institute_t *instituteptr, void *context);
static void institutePackP(void *instituteptr, void *buf, int size, int *position, void *context);
static int instituteTxCode(void *instituteptr);

static const resOps instituteOps = { (int (*)(void *, void *)) instituteCompareKernel,
                                     (void (*)(void *)) instituteDestroyP,
                                     (void (*)(void *, FILE *)) institutePrintP,
                                     (int (*)(void *, void *)) instituteGetPackSize,
                                     institutePackP,
                                     instituteTxCode };

static void
instituteDefaultValue(institute_t *instituteptr)
{
  instituteptr->self = CDI_UNDEFID;
  instituteptr->center = CDI_UNDEFID;
  instituteptr->subcenter = CDI_UNDEFID;
  instituteptr->name = NULL;
  instituteptr->longname = NULL;
}

void
instituteDefaultEntries(void)
{
  // clang-format off
  cdiResH resH[]
    = { institutDef( 98,   0, "ECMWF",     "European Centre for Medium-Range Weather Forecasts"),
        institutDef(252,   1, "MPIMET",    "Max Planck Institute for Meteorology"),
        institutDef( 98, 232, "MPIMET",    "Max Planck Institute for Meteorology"),
        institutDef( 98, 255, "MPIMET",    "Max-Planck-Institute for Meteorology"),
        institutDef( 78, 255, "DWD",       "Deutscher Wetterdienst"),
        institutDef( 78,   0, "DWD",       "Deutscher Wetterdienst"),
        institutDef(215, 255, "MCH",       "MeteoSwiss"),
        institutDef(  7,   0, "NCEP",      "National Centers for Environmental Prediction"),
        institutDef(  7,   1, "NCEP",      "National Centers for Environmental Prediction"),
        institutDef( 60,   0, "NCAR",      "National Center for Atmospheric Research"),
        institutDef( 74,   0, "METOFFICE", "U.K. Met Office"),
        institutDef( 97,   0, "ESA",       "European Space Agency"),
        institutDef( 99,   0, "KNMI",      "Royal Netherlands Meteorological Institute"),
        institutDef( 80,   0, "CNMC",      "Reparto per la Meteorologia, Rome (REMET)"),
        // institutDef(  0,   0, "IPSL", "IPSL (Institut Pierre Simon Laplace, Paris, France)");
  };
  // clang-format on

  const size_t n = sizeof(resH) / sizeof(*resH);
  for (size_t i = 0; i < n; i++) reshSetStatus(resH[i], &instituteOps, RESH_IN_USE);
}

static int
instituteCompareKernel(institute_t *ip1, institute_t *ip2)
{
  int differ = 0;

  if (ip1->name)
  {
    if (ip1->center > 0 && ip2->center != ip1->center) differ = 1;
    if (ip1->subcenter > 0 && ip2->subcenter != ip1->subcenter) differ = 1;

    if (!differ)
    {
      if (ip2->name)
      {
        const size_t len1 = strlen(ip1->name);
        const size_t len2 = strlen(ip2->name);
        if ((len1 != len2) || memcmp(ip2->name, ip1->name, len2)) differ = 1;
      }
    }
  }
  else if (ip1->longname)
  {
    if (ip2->longname)
    {
      const size_t len1 = strlen(ip1->longname);
      const size_t len2 = strlen(ip2->longname);
      if ((len1 != len2) || memcmp(ip2->longname, ip1->longname, len2)) differ = 1;
    }
  }
  else
  {
    if (!(ip2->center == ip1->center && ip2->subcenter == ip1->subcenter)) differ = 1;
    if (ip1->subcenter > 0 && ip1->subcenter != 255 && ip2->subcenter != ip1->subcenter) differ = 1;
  }

  return differ;
}

struct instLoc
{
  institute_t *ip;
  int id;
};

static enum cdiApplyRet
findInstitute(int id, void *res, void *data)
{
  institute_t *ip1 = ((struct instLoc *) data)->ip;
  institute_t *ip2 = (institute_t *) res;
  if (!instituteCompareKernel(ip1, ip2))
  {
    ((struct instLoc *) data)->id = id;
    return CDI_APPLY_STOP;
  }
  else
    return CDI_APPLY_GO_ON;
}

int
institutInq(int center, int subcenter, const char *name, const char *longname)
{
  institute_t ip_ref;
  ip_ref.self = CDI_UNDEFID;
  ip_ref.center = center;
  ip_ref.subcenter = subcenter;
  ip_ref.name = (name && name[0]) ? (char *) name : NULL;
  ip_ref.longname = (longname && longname[0]) ? (char *) longname : NULL;

  struct instLoc state = { .ip = &ip_ref, .id = CDI_UNDEFID };
  cdiResHFilterApply(&instituteOps, findInstitute, &state);

  return state.id;
}

static institute_t *
instituteNewEntry(cdiResH resH, int center, int subcenter, const char *name, const char *longname)
{
  institute_t *instituteptr = (institute_t *) malloc(sizeof(institute_t));
  instituteDefaultValue(instituteptr);
  if (resH == CDI_UNDEFID)
    instituteptr->self = reshPut(instituteptr, &instituteOps);
  else
  {
    instituteptr->self = resH;
    reshReplace(resH, instituteptr, &instituteOps);
  }
  instituteptr->center = center;
  instituteptr->subcenter = subcenter;
  if (name && *name) instituteptr->name = strdup(name);
  if (longname && *longname) instituteptr->longname = strdup(longname);
  return instituteptr;
}

int
institutDef(int center, int subcenter, const char *name, const char *longname)
{
  institute_t *instituteptr = instituteNewEntry(CDI_UNDEFID, center, subcenter, name, longname);
  return instituteptr->self;
}

int
institutInqCenter(int instID)
{
  return instID != CDI_UNDEFID ? ((institute_t *) (reshGetVal(instID, &instituteOps)))->center : CDI_UNDEFID;
}

int
institutInqSubcenter(int instID)
{
  return instID != CDI_UNDEFID ? ((institute_t *) (reshGetVal(instID, &instituteOps)))->subcenter : CDI_UNDEFID;
}

const char *
institutInqNamePtr(int instID)
{
  return instID != CDI_UNDEFID ? ((institute_t *) (reshGetVal(instID, &instituteOps)))->name : NULL;
}

const char *
institutInqLongnamePtr(int instID)
{
  return instID != CDI_UNDEFID ? ((institute_t *) (reshGetVal(instID, &instituteOps)))->longname : NULL;
}

int
institutInqNumber(void)
{
  int instNum = (int) (reshCountType(&instituteOps));
  return instNum;
}

static void
instituteDestroyP(institute_t *instituteptr)
{
  xassert(instituteptr);
  free(instituteptr->name);
  free(instituteptr->longname);
  free(instituteptr);
}

static void
institutePrintP(institute_t *ip, FILE *fp)
{
  if (ip)
    fprintf(fp,
            "#\n"
            "# instituteID %d\n"
            "#\n"
            "self          = %d\n"
            "center        = %d\n"
            "subcenter     = %d\n"
            "name          = %s\n"
            "longname      = %s\n",
            ip->self, ip->self, ip->center, ip->subcenter, ip->name ? ip->name : "NN", ip->longname ? ip->longname : "NN");
}

static int
instituteTxCode(void *instituteptr)
{
  (void) instituteptr;
  return INSTITUTE;
}

enum
{
  INSTITUTE_PACK_INT_SELF,
  INSTITUTE_PACK_INT_CENTER,
  INSTITUTE_PACK_INT_SUBCENTER,
  INSTITUTE_PACK_INT_NAMELEN,
  INSTITUTE_PACK_INT_LNAMELEN,
  institute_nints,
};

static int
instituteGetPackSize(institute_t *ip, void *context)
{
  size_t namelen = strlen(ip->name), longnamelen = strlen(ip->longname);
  xassert(namelen < INT_MAX && longnamelen < INT_MAX);
  size_t txsize = (size_t) serializeGetSize(institute_nints, CDI_DATATYPE_INT, context)
                  + (size_t) serializeGetSize((int) namelen + 1, CDI_DATATYPE_TXT, context)
                  + (size_t) serializeGetSize((int) longnamelen + 1, CDI_DATATYPE_TXT, context);
  xassert(txsize <= INT_MAX);
  return (int) txsize;
}

static void
institutePackP(void *instituteptr, void *buf, int size, int *position, void *context)
{
  institute_t *p = (institute_t *) instituteptr;
  int tempbuf[institute_nints];
  tempbuf[INSTITUTE_PACK_INT_SELF] = p->self;
  tempbuf[INSTITUTE_PACK_INT_CENTER] = p->center;
  tempbuf[INSTITUTE_PACK_INT_SUBCENTER] = p->subcenter;
  tempbuf[INSTITUTE_PACK_INT_NAMELEN] = (int) strlen(p->name) + 1;
  tempbuf[INSTITUTE_PACK_INT_LNAMELEN] = (int) strlen(p->longname) + 1;
  serializePack(tempbuf, institute_nints, CDI_DATATYPE_INT, buf, size, position, context);
  serializePack(p->name, tempbuf[INSTITUTE_PACK_INT_NAMELEN], CDI_DATATYPE_TXT, buf, size, position, context);
  serializePack(p->longname, tempbuf[INSTITUTE_PACK_INT_LNAMELEN], CDI_DATATYPE_TXT, buf, size, position, context);
}

int
instituteUnpack(void *buf, int size, int *position, int originNamespace, void *context, int force_id)
{
#define adaptKey(key) (namespaceAdaptKey((key), originNamespace))
  int tempbuf[institute_nints];
  int instituteID;
  serializeUnpack(buf, size, position, tempbuf, institute_nints, CDI_DATATYPE_INT, context);
  char *name = (char *) malloc((size_t) tempbuf[INSTITUTE_PACK_INT_NAMELEN] + (size_t) tempbuf[INSTITUTE_PACK_INT_LNAMELEN]),
       *longname = name + tempbuf[INSTITUTE_PACK_INT_NAMELEN];
  serializeUnpack(buf, size, position, name, tempbuf[INSTITUTE_PACK_INT_NAMELEN], CDI_DATATYPE_TXT, context);
  serializeUnpack(buf, size, position, longname, tempbuf[INSTITUTE_PACK_INT_LNAMELEN], CDI_DATATYPE_TXT, context);
  int targetID = force_id ? adaptKey(tempbuf[INSTITUTE_PACK_INT_SELF]) : CDI_UNDEFID;
  institute_t *ip
      = instituteNewEntry(targetID, tempbuf[INSTITUTE_PACK_INT_CENTER], tempbuf[INSTITUTE_PACK_INT_SUBCENTER], name, longname);
  instituteID = ip->self;
  xassert(!force_id || instituteID == targetID);
  free(name);
  reshSetStatus(instituteID, &instituteOps, reshGetStatus(instituteID, &instituteOps) & ~RESH_SYNC_BIT);
#undef adaptKey
  return instituteID;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/*
 * This file is for the use of iterator.c and the CdiIterator subclasses only.
 */

#ifndef INCLUDE_GUARD_CDI_ITERATOR_INT_H
#define INCLUDE_GUARD_CDI_ITERATOR_INT_H


#include <stdbool.h>

/*
class CdiIterator

An iterator is an object that identifies the position of one record in a file, where a record is defined as the data belonging to
one level, timestep, and variable. Using iterators to read a file can be significantly faster than using streams, because they can
avoid building an index of the file. For file formats like grib that do not provide an index within the file, this makes the
difference between reading the file once or reading the file twice.

CdiIterator is an abstract base class. Which derived class is used depends on the type of the file. The class hierarchy currently
looks like this:

    CdiIterator <|--+-- CdiFallbackIterator
                    |
                    +-- CdiGribIterator

The fallback implementation currently uses the stream interface of CDI under the hood to provide full functionality for all
filetypes for which no iterator implementation exists yet.
*/
// TODO[NH]: Debug messages, print function.

struct CdiIterator
{
  int filetype;     // This is used to dispatch calls to the correct subclass.
  bool isAdvanced;  // Used to catch inquiries before the first call to CdiIteratorNextField(). //XXX: Advanced is probably not a
                    // good word (initialized?)

  // The metadata that can be accessed by the inquiry calls.
  // While theoretically redundant, these fields allow the handling of most inquiry calls within the base class.
  // Only the name is excempted because it needs an allocation.
  // These fields are set by the subclasses in the xxxIterNextField() method.
  int datatype, timesteptype;
  int gridId;
  CdiParam param;

  // The status information for reading/advancing is added in the subclasses.
};

void baseIterConstruct(CdiIterator *me, int filetype);
const char *baseIter_constructFromString(
    CdiIterator *me, const char *description);  // Returns a pointer past the end of the parsed portion of the description string.
void baseIterDestruct(CdiIterator *me);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/*
 * A fallback implementation of the iterator interface that opens a stream under the hood.
 *
 * This implementation is mainly available to provide iterator access to file formats that don't support iterator access natively,
 * nevertheless, it allows the file to dictate the order in which data is read, possibly providing performance benefits.
 */

#ifndef INCLUDE_GUARD_CDI_ITERATOR_FALLBACK_H
#define INCLUDE_GUARD_CDI_ITERATOR_FALLBACK_H

#ifdef HAVE_CONFIG_H
#endif

#include <stdlib.h>


typedef struct CdiFallbackIterator CdiFallbackIterator;

CdiIterator *cdiFallbackIterator_new(const char *path, int filetype);
CdiFallbackIterator *cdiFallbackIterator_clone(CdiIterator *me);
CdiIterator *cdiFallbackIterator_getSuper(CdiFallbackIterator *me);
char *cdiFallbackIterator_serialize(CdiIterator *me);
CdiFallbackIterator *cdiFallbackIterator_deserialize(const char *me);

int cdiFallbackIterator_nextField(CdiIterator *me);

char *cdiFallbackIterator_inqTime(CdiIterator *me, CdiTimeType timeType);
int cdiFallbackIterator_levelType(CdiIterator *me, int levelSelector, char **outName, char **outLongName, char **outStdName,
                                  char **outUnit);
int cdiFallbackIterator_level(CdiIterator *me, int levelSelector, double *outValue1, double *outValue2);
int cdiFallbackIterator_zaxisUuid(CdiIterator *me, int *outVgridNumber, int *outLevelCount, unsigned char outUuid[CDI_UUID_SIZE]);
char *cdiFallbackIterator_copyVariableName(CdiIterator *me);
int cdiFallbackIterator_inqTile(CdiIterator *me, int *outTileIndex, int *outTileAttribute);
int cdiFallbackIterator_inqTileCount(CdiIterator *me, int *outTileCount, int *outTileAttributeCount);

void cdiFallbackIterator_readField(CdiIterator *me, double *buffer, size_t *numMissVals);
void cdiFallbackIterator_readFieldF(CdiIterator *me, float *buffer, size_t *numMissVals);

void cdiFallbackIterator_delete(CdiIterator *super);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/*
 * An implementation of the iterator interface for GRIB files.
 * Since GRIB files do not contain an index, this avoids scanning the entire file to generate an in-memory index as streamOpenRead()
 * does. Consequently, using this interface is much more efficient for GRIB files.
 */

#ifndef INCLUDE_GUARD_CDI_ITERATOR_GRIB_H
#define INCLUDE_GUARD_CDI_ITERATOR_GRIB_H

#ifdef HAVE_CONFIG_H
#endif


#ifdef HAVE_LIBGRIB_API
#include <grib_api.h>
#endif

typedef struct recordList recordList;

CdiIterator *cdiGribIterator_new(const char *path, int filetype);
CdiGribIterator *cdiGribIterator_makeClone(CdiIterator *me);
CdiIterator *cdiGribIterator_getSuper(CdiGribIterator *me);
char *cdiGribIterator_serialize(CdiIterator *me);
CdiGribIterator *cdiGribIterator_deserialize(const char *me);

int cdiGribIterator_nextField(CdiIterator *me);

char *cdiGribIterator_inqTime(CdiIterator *me, CdiTimeType timeType);
int cdiGribIterator_levelType(CdiIterator *me, int levelSelector, char **outName, char **outLongName, char **outStdName,
                              char **outUnit);
int cdiGribIterator_level(CdiIterator *me, int levelSelector, double *outValue1, double *outValue2);
int cdiGribIterator_zaxisUuid(CdiIterator *me, int *outVgridNumber, int *outLevelCount, unsigned char outUuid[CDI_UUID_SIZE]);
int cdiGribIterator_inqTile(CdiIterator *me, int *outTileIndex, int *outTileAttribute);
int cdiGribIterator_inqTileCount(CdiIterator *me, int *outTileCount, int *outTileAttributeCount);
char *cdiGribIterator_copyVariableName(CdiIterator *me);

void cdiGribIterator_readField(CdiIterator *me, double *buffer, size_t *numMissVals);
void cdiGribIterator_readFieldF(CdiIterator *me, float *buffer, size_t *numMissVals);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */


#include <assert.h>
#include <ctype.h>

static const char kUnexpectedFileTypeMessage[] = "Internal error: Unexpected file type encountered in iterator.\n"
                                                 "This is either due to an illegal memory access by the application\n"
                                                 " or an internal logical error in CDI (unlikely, but possible).";
static const char kAdvancedString[] = "advanced";
static const char kUnadvancedString[] = "unadvanced";

// Returns a static string.
static const char *
fileType2String(int fileType)
{
  switch (fileType)
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRB: return "CDI::Iterator::GRIB1";
    case CDI_FILETYPE_GRB2: return "CDI::Iterator::GRIB2";
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NC: return "CDI::Iterator::NetCDF";
    case CDI_FILETYPE_NC2: return "CDI::Iterator::NetCDF2";
    case CDI_FILETYPE_NC4: return "CDI::Iterator::NetCDF4";
    case CDI_FILETYPE_NC4C: return "CDI::Iterator::NetCDF4C";
    case CDI_FILETYPE_NC5: return "CDI::Iterator::NetCDF5";
    case CDI_FILETYPE_NCZARR: return "CDI::Iterator::NCZarr";
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: return "CDI::Iterator::SRV";
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: return "CDI::Iterator::EXT";
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: return "CDI::Iterator::IEG";
#endif

    default: return NULL;
  }
}

static int
string2FileType(const char *fileType, const char **outRestString)
{
  // This first part unconditionally checks all known type strings, and only if the given string matches one of these strings, we
  // use fileType2string() to check whether support for this type has been compiled in. This is to avoid throwing "invalid type
  // string" errors when we just have a library version mismatch.
#define check(givenString, typeString, typeConstant)                                                           \
  do {                                                                                                         \
    if (givenString == strstr(givenString, typeString))                                                        \
    {                                                                                                          \
      if (outRestString) *outRestString = givenString + strlen(typeString);                                    \
      if (fileType2String(typeConstant)) return typeConstant;                                                  \
      Error("Support for " typeString                                                                          \
            " not compiled in. Please check that the result of `cdiIterator_serialize()` is only passed to a " \
            "`cdiIterator_deserialize()` implementation of the same CDI library version.");                    \
      return CDI_FILETYPE_UNDEF;                                                                               \
    }                                                                                                          \
  } while (0)
  check(fileType, "CDI::Iterator::GRIB1", CDI_FILETYPE_GRB);
  check(fileType, "CDI::Iterator::GRIB2", CDI_FILETYPE_GRB2);
  check(fileType, "CDI::Iterator::NetCDF", CDI_FILETYPE_NC);
  check(fileType, "CDI::Iterator::NetCDF2", CDI_FILETYPE_NC2);
  check(fileType, "CDI::Iterator::NetCDF4", CDI_FILETYPE_NC4);
  check(fileType, "CDI::Iterator::NetCDF4C", CDI_FILETYPE_NC4C);
  check(fileType, "CDI::Iterator::NetCDF5", CDI_FILETYPE_NC5);
  check(fileType, "CDI::Iterator::NCZarr", CDI_FILETYPE_NCZARR);
  check(fileType, "CDI::Iterator::SRV", CDI_FILETYPE_SRV);
  check(fileType, "CDI::Iterator::EXT", CDI_FILETYPE_EXT);
  check(fileType, "CDI::Iterator::IEG", CDI_FILETYPE_IEG);
#undef check

  // If this point is reached, the given string does not seem to be produced by a cdiIterator_serialize() call.
  Error("The string \"%s\" does not start with a valid iterator type. Please check the source of this string.", fileType);
  *outRestString = fileType;
  return CDI_FILETYPE_UNDEF;
}

/*
@Function cdiIterator_new
@Title Create an iterator for an input file

@Prototype CdiIterator* cdiIterator_new(const char* path)
@Parameter
    @item path Path to the file that is to be read.

@Result An iterator for the given file.

@Description
    Combined allocator and constructor for CdiIterator.

    The returned iterator does not point to the first field yet,
    it must first be advanced once before the first field can be introspected.
    This design decision has two benefits: 1. Empty files require no special
    cases, 2. Users can start a while(!cdiIterator_nextField(iterator)) loop
    right after the call to cdiIterator_new().
*/
CdiIterator *
cdiIterator_new(const char *path)
{
  int trash;
  const int filetype = cdiGetFiletype(path, &trash);
  switch (cdiBaseFiletype(filetype))
  {
    case CDI_FILETYPE_UNDEF: Warning("Can't open file \"%s\": unknown format\n", path); return NULL;

#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_new(path, filetype);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_new(path, filetype);

    default:
      Warning("the file \"%s\" is of type %s, but support for this format is not compiled in!", path, strfiletype(filetype));
      return NULL;
  }
}

void
baseIterConstruct(CdiIterator *me, int filetype)
{
  me->filetype = filetype;
  me->isAdvanced = false;
}

const char *
baseIter_constructFromString(CdiIterator *me, const char *description)
{
  const char *result = description;
  me->filetype = string2FileType(result, &result);
  assert(me->filetype != CDI_FILETYPE_UNDEF
         && "Please report this error.");  // This condition should have been checked for in a calling function.
  for (; *result && isspace(*result); result++)
    ;
  if (result == strstr(result, kAdvancedString))
  {
    me->isAdvanced = true;
    result += sizeof(kAdvancedString) - 1;
  }
  else if (result == strstr(result, kUnadvancedString))
  {
    me->isAdvanced = false;
    result += sizeof(kUnadvancedString) - 1;
  }
  else
  {
    Error("Invalid iterator description string \"%s\". Please check the origin of this string.", description);
    return NULL;
  }
  return result;
}

#define sanityCheck(me)                                                                                                     \
  do {                                                                                                                      \
    if (!me) xabort("NULL was passed to %s as an iterator. Please check the return value of cdiIterator_new().", __func__); \
    if (!me->isAdvanced) xabort("Calling %s is not allowed without calling cdiIterator_nextField() first.", __func__);      \
  } while (0)

/*
@Function cdiIterator_clone
@Title Make a copy of an iterator

@Prototype CdiIterator* cdiIterator_clone(CdiIterator* me)
@Parameter
    @item iterator The iterator to copy.

@Result The clone.

@Description
    Clones the given iterator. Make sure to call cdiIterator_delete() on both
    the copy and the original.

    This is not a cheap operation: Depending on the type of the file, it will
    either make a copy of the current field in memory (GRIB files), or reopen
    the file (all other file types). Use it sparingly. And if you do, try to
    avoid keeping too many clones around: their memory footprint is
    significant.
*/
CdiIterator *
cdiIterator_clone(CdiIterator *me)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_getSuper(cdiGribIterator_clone(me));
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_getSuper(cdiFallbackIterator_clone(me));

    default: Error(kUnexpectedFileTypeMessage); return NULL;
  }
}

/*
@Function cdiGribIterator_clone
@Title Gain access to GRIB specific functionality

@Prototype CdiGribIterator* cdiGribIterator_clone(CdiIterator* me)
@Parameter
    @item iterator The iterator to operate on.

@Result A clone that allows access to GRIB specific functionality, or NULL if the underlying file is not a GRIB file.

@Description
    Clones the given iterator iff the underlying file is a GRIB file, the returned iterator allows access to GRIB specific
functionality. Make sure to check that the return value is not NULL, and to call cdiGribIterator_delete() on the copy.

    This is not a cheap operation: It will make a copy of the current field in memory. Use it sparingly. And if you do, try to avoid
keeping too many clones around, their memory footprint is significant.
*/
CdiGribIterator *
cdiGribIterator_clone(CdiIterator *me)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_makeClone(me);
#endif

    default: return NULL;
  }
}

/*
@Function cdiIterator_serialize
@Title Serialize an iterator for sending it to another process

@Prototype char* cdiIterator_serialize(CdiIterator* me)
@Parameter
    @item iterator The iterator to operate on.

@Result A malloc'ed string that contains the full description of the iterator.

@Description
    Make sure to call Free() on the resulting string.
*/
char *
cdiIterator_serialize(CdiIterator *me)
{
  if (!me) xabort("NULL was passed to %s as an iterator. Please check the return value of cdiIterator_new().", __func__);
  char *subclassDescription = NULL;
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: subclassDescription = cdiGribIterator_serialize(me); break;
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      subclassDescription = cdiFallbackIterator_serialize(me);
      break;

    default: Error(kUnexpectedFileTypeMessage); return NULL;
  }

  const char *ftypeStr = fileType2String(me->filetype), *advStr = me->isAdvanced ? kAdvancedString : kUnadvancedString;
  size_t len = strlen(ftypeStr) + 1 + strlen(advStr) + 1 + strlen(subclassDescription) + 1;
  char *result = (char *) Malloc(len);
  snprintf(result, len, "%s %s %s", ftypeStr, advStr, subclassDescription);
  Free(subclassDescription);
  return result;
}

/*
@Function cdiIterator_deserialize
@Title Recreate an iterator from its textual description

@Prototype CdiIterator* cdiIterator_deserialize(const char* description)
@Parameter
    @item description The result of a call to cdiIterator_serialize().

@Result A clone of the original iterator.

@Description
    A pair of cdiIterator_serialize() and cdiIterator_deserialize() is functionally equivalent to a call to cdiIterator_clone()

    This function will reread the current field from disk, so don't expect immediate return.
*/
// This only checks the type of the iterator and calls the corresponding subclass function,
// the real deserialization is done in baseIter_constructFromString().
CdiIterator *
cdiIterator_deserialize(const char *description)
{
  switch (cdiBaseFiletype(string2FileType(description, NULL)))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_getSuper(cdiGribIterator_deserialize(description));
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_getSuper(cdiFallbackIterator_deserialize(description));

    default: Error(kUnexpectedFileTypeMessage); return NULL;
  }
}

/*
@Function cdiIterator_print
@Title Print a textual description of the iterator to a stream

@Prototype void cdiIterator_print(CdiIterator* iterator, FILE* stream);
@Parameter
    @item iterator The iterator to print.
    @item stream The stream to print to.

@Description
    Use for debugging output.
*/
void
cdiIterator_print(CdiIterator *me, FILE *stream)
{
  char *description = cdiIterator_serialize(me);
  fprintf(stream, "%s\n", description);
  Free(description);
}

/*
@Function cdiIterator_nextField
@Title Advance an iterator to the next field in the file

@Prototype int cdiIterator_nextField(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result An error code. May be one of:
  * CDI_NOERR: The iterator has successfully been advanced to the next field.
  * CDI_EEOF: No more fields to read in this file.

@Description
    One call to cdiIterator_nextField() is required before the metadata of the first field can be examined.
    Usually, it will be used directly as the condition for a while() loop.
*/
int
cdiIterator_nextField(CdiIterator *me)
{
  if (!me) xabort("NULL was passed in as an iterator. Please check the return value of cdiIterator_new().");
  me->isAdvanced = true;
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_nextField(me);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_nextField(me);

    default: Error(kUnexpectedFileTypeMessage); return CDI_EINVAL;
  }
}

static char *
cdiIterator_inqTime(CdiIterator *me, CdiTimeType timeType)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_inqTime(me, timeType);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_inqTime(me, timeType);

    default: Error(kUnexpectedFileTypeMessage); return NULL;
  }
}

/*
@Function cdiIterator_inqStartTime
@Title Get the start time of a measurement

@Prototype char* cdiIterator_inqStartTime(CdiIterator* me)
@Parameter
    @item iterator The iterator to operate on.

@Result A malloc'ed string containing the (start) time of the current field in the format "YYYY-MM-DDTHH:MM:SS.mmm".

@Description
The returned time is either the time of the data (fields defined at a time point),
or the start time of an integration time range (statistical fields).

Converts the time to the ISO-8601 format and returns it in a newly allocated buffer.
The caller is responsible to Free() the resulting string.

If the file is a GRIB file, the calendar that is used to resolve the relative times is the proleptic calendar
as it is implemented by the standard C mktime() function.
This is due to the fact that GRIB-API version 1.12.3 still does not implement the calendar identification fields.
*/
char *
cdiIterator_inqStartTime(CdiIterator *me)
{
  return cdiIterator_inqTime(me, kCdiTimeType_startTime);
}

/*
@Function cdiIterator_inqEndTime
@Title Get the end time of a measurement

@Prototype char* cdiIterator_inqEndTime(CdiIterator* me)
@Parameter
    @item iterator The iterator to operate on.

@Result A malloc'ed string containing the end time of the current field in the format "YYYY-MM-DDTHH:MM:SS.mmm", or NULL if no such
time is defined.

@Description
The returned time is the end time of an integration period if such a time exists (statistical fields).
Otherwise, NULL is returned.

Converts the time to the ISO-8601 format and returns it in a newly allocated buffer.
The caller is responsible to Free() the resulting string.

If the file is a GRIB file, the calendar that is used to resolve the relative times is the proleptic calendar
as it is implemented by the standard C mktime() function.
This is due to the fact that GRIB-API version 1.12.3 still does not implement the calendar identification fields.
*/
char *
cdiIterator_inqEndTime(CdiIterator *me)
{
  return cdiIterator_inqTime(me, kCdiTimeType_endTime);
}

/*
@Function cdiIterator_inqRTime
@Title Get the validity time of the current field

@Prototype char* cdiIterator_inqRTime(CdiIterator* me)
@Parameter
    @item iterator The iterator to operate on.

@Result A malloc'ed string containing the validity time of the current field in the format "YYYY-MM-DDTHH:MM:SS.mmm".

@Description
The returned time is the validity time as it is returned by taxisInqVtime(), only more precise.
That is, if the field is a time point, its time is returned,
if it is a statistical field with an integration period, the end time of the integration period is returned.

Converts the time to the ISO-8601 format and returns it in a newly allocated buffer.
The caller is responsible to Free() the resulting string.

If the file is a GRIB file, the calendar that is used to resolve the relative times is the proleptic calendar
as it is implemented by the standard C mktime() function.
This is due to the fact that GRIB-API version 1.12.3 still does not implement the calendar identification fields.
*/
char *
cdiIterator_inqRTime(CdiIterator *me)
{
  return cdiIterator_inqTime(me, kCdiTimeType_referenceTime);
}

/*
@Function cdiIterator_inqVTime
@Title Get the validity time of the current field

@Prototype char* cdiIterator_inqVTime(CdiIterator* me)
@Parameter
    @item iterator The iterator to operate on.

@Result A malloc'ed string containing the validity time of the current field in the format "YYYY-MM-DDTHH:MM:SS.mmm".

@Description
The returned time is the validity time as it is returned by taxisInqVtime(), only more precise.
That is, if the field is a time point, its time is returned,
if it is a statistical field with an integration period, the end time of the integration period is returned.

Converts the time to the ISO-8601 format and returns it in a newly allocated buffer.
The caller is responsible to Free() the resulting string.

If the file is a GRIB file, the calendar that is used to resolve the relative times is the proleptic calendar
as it is implemented by the standard C mktime() function.
This is due to the fact that GRIB-API version 1.12.3 still does not implement the calendar identification fields.
*/
char *
cdiIterator_inqVTime(CdiIterator *me)
{
  char *result = cdiIterator_inqEndTime(me);
  return (result) ? result : cdiIterator_inqStartTime(me);
}

/*
@Function cdiIterator_inqLevelType
@Title Get the type of a level

@Prototype int cdiIterator_inqLevelType(CdiIterator* me, int levelSelector, char **outName = NULL, char **outLongName = NULL, char
**outStdName = NULL, char **outUnit = NULL)
@Parameter
    @item iterator The iterator to operate on.
    @item levelSelector Zero for the top level, one for the bottom level
    @item outName Will be set to a Malloc()'ed string with the name of the level if not NULL.
    @item outLongName Will be set to a Malloc()'ed string with the long name of the level if not NULL.
    @item outStdName Will be set to a Malloc()'ed string with the standard name of the level if not NULL.
    @item outUnit Will be set to a Malloc()'ed string with the unit of the level if not NULL.

@Result An integer indicating the type of the level.

@Description
Find out some basic information about the given level, the levelSelector selects the function of the requested level.
If the requested level does not exist, this returns CDI_UNDEFID.
*/
int
cdiIterator_inqLevelType(CdiIterator *me, int levelSelector, char **outName, char **outLongName, char **outStdName, char **outUnit)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_levelType(me, levelSelector, outName, outLongName, outStdName, outUnit);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_levelType(me, levelSelector, outName, outLongName, outStdName, outUnit);

    default: Error(kUnexpectedFileTypeMessage); return CDI_UNDEFID;
  }
}

/*
@Function cdiIterator_inqLevel
@Title Get the value of the z-coordinate

@Prototype void cdiIterator_inqLevel(CdiIterator* me, int levelSelector, double* outValue1, double* outValue2 = NULL)
@Parameter
    @item iterator The iterator to operate on.
    @item levelSelector Zero for the top level, one for the bottom level
    @item outValue1 For "normal" levels this returns the value, for hybrid levels the first coordinate, for generalized levels the
level number.
    @item outValue2 Zero for "normal" levels, for hybrid levels, this returns the second coordinate, for generalized levels the
level count.

@Result An error code.

@Description
Returns the value of the z-coordinate, whatever that may be.
*/
int
cdiIterator_inqLevel(CdiIterator *me, int levelSelector, double *outValue1, double *outValue2)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_level(me, levelSelector, outValue1, outValue2);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_level(me, levelSelector, outValue1, outValue2);

    default: Error(kUnexpectedFileTypeMessage); return CDI_EINVAL;
  }
}

/*
@Function cdiIterator_inqLevelUuid
@Title Get the UUID of the z-axis used by this field

@Prototype int cdiIterator_inqLevelUuid(CdiIterator* me, int levelSelector, unsigned char (*outUuid)[16])
@Parameter
    @item iterator The iterator to operate on.
    @item outVgridNumber The number of the associated vertical grid description.
    @item outLevelCount The number of levels in the associated vertical grid description.
    @item outUuid A pointer to a user supplied buffer of 16 bytes to store the UUID in.

@Result An error code.

@Description
Returns identifying information for the external z-axis description. May only be called for generalized levels.
*/
int
cdiIterator_inqLevelUuid(CdiIterator *me, int *outVgridNumber, int *outLevelCount, unsigned char outUuid[CDI_UUID_SIZE])
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_zaxisUuid(me, outVgridNumber, outLevelCount, outUuid);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_zaxisUuid(me, outVgridNumber, outLevelCount, outUuid);

    default: Error(kUnexpectedFileTypeMessage); return CDI_ELIBNAVAIL;
  }
}

/*
@Function cdiIterator_inqTile
@Title Inquire the tile information for the current field

@Prototype int cdiIterator_inqTile(CdiIterator* me, int* outTileIndex, int* outTileAttribute)
@Parameter
    @item iterator The iterator to operate on.
    @item outTileIndex The index of the current tile, -1 if no tile information is available.
    @item outTileAttribute The attribute of the current tile, -1 if no tile information is available.

@Result An error code. CDI_EINVAL if there is no tile information associated with the current field.

@Description
Inquire the tile index and attribute for the current field.
*/
int
cdiIterator_inqTile(CdiIterator *me, int *outTileIndex, int *outTileAttribute)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_inqTile(me, outTileIndex, outTileAttribute);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_inqTile(me, outTileIndex, outTileAttribute);

    default: Error(kUnexpectedFileTypeMessage); return CDI_ELIBNAVAIL;
  }
}

/**
@Function cdiIterator_inqTileCount
@Title Inquire the tile count and tile attribute counts for the current field

@Prototype int cdiIterator_inqTileCount(CdiIterator* me, int* outTileCount, int* outTileAttributeCount)
@Parameter
    @item iterator The iterator to operate on.
    @item outTileCount The number of tiles used for this variable, zero if no tile information is available.
    @item outTileAttributeCount The number of attributes available for the tile of this field, zero if no tile information is
available. Note: This is not the global attribute count, which would be impossible to infer without reading the entire file if it's
a GRIB file.

@Result An error code. CDI_EINVAL if there is no tile information associated with the current field.

@Description
Inquire the tile count and tile attribute counts for the current field.
*/
int
cdiIterator_inqTileCount(CdiIterator *me, int *outTileCount, int *outTileAttributeCount)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_inqTileCount(me, outTileCount, outTileAttributeCount);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_inqTileCount(me, outTileCount, outTileAttributeCount);

    default: Error(kUnexpectedFileTypeMessage); return CDI_ELIBNAVAIL;
  }
}

/*
@Function cdiIterator_inqParam
@Title Get discipline, category, and number

@Prototype CdiParam cdiIterator_inqParam(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result A struct containing the requested information.

@Description
    Simple metadata inspection function.
*/
CdiParam
cdiIterator_inqParam(CdiIterator *me)
{
  sanityCheck(me);
  return me->param;
}

/*
@Function cdiIterator_inqParamParts
@Title Get discipline, category, and number

@Prototype void cdiIterator_inqParamParts(CdiIterator *me, int *outDiscipline, int *outCategory, int *outNumber)
@Parameter
    @item iterator The iterator to operate on.
    @item outDiscipline This is used to return the discipline.
    @item outCategory This is used to return the category.
    @item outNumber This is used to return the number.

@Description
    Simple metadata inspection function.

    Some FORTRAN compilers produce wrong code for the cdiIterator_inqParam()-wrapper,
    rendering it unusable from FORTRAN. This function is the workaround.
*/
void
cdiIterator_inqParamParts(CdiIterator *me, int *outDiscipline, int *outCategory, int *outNumber)
{
  CdiParam result = cdiIterator_inqParam(me);
  if (outDiscipline) *outDiscipline = result.discipline;
  if (outCategory) *outCategory = result.category;
  if (outNumber) *outNumber = result.number;
}

/*
@Function cdiIterator_inqDatatype
@Title Get the datatype of the current field

@Prototype int cdiIterator_inqDatatype(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result The datatype that is used to store this field on disk.

@Description
    Simple metadata inspection function.
*/
int
cdiIterator_inqDatatype(CdiIterator *me)
{
  sanityCheck(me);
  return me->datatype;
}

/*
@Function cdiIterator_inqFiletype
@Title Get the filetype of the current field

@Prototype int cdiIterator_inqFiletype(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result The filetype that is used to store this field on disk.

@Description
    Simple metadata inspection function.
*/
int
cdiIterator_inqFiletype(CdiIterator *me)
{
  return me->filetype;
}

/*
@Function cdiIterator_inqTsteptype
@Title Get the timestep type

@Prototype int cdiIterator_inqTsteptype(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result The timestep type.

@Description
    Simple metadata inspection function.
*/
int
cdiIterator_inqTsteptype(CdiIterator *me)
{
  sanityCheck(me);
  return me->timesteptype;
}

/*
@Function cdiIterator_inqVariableName
@Title Get the variable name of the current field

@Prototype char* cdiIterator_inqVariableName(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result A pointer to a C-string containing the name. The storage for this string is allocated with Malloc(), and it is the
responsibility of the caller to Free() it.

@Description
    Allocates a buffer to hold the string, copies the current variable name into this buffer, and returns the buffer.
    The caller is responsible to make the corresponding Free() call.
*/
char *
cdiIterator_inqVariableName(CdiIterator *me)
{
  sanityCheck(me);
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: return cdiGribIterator_copyVariableName(me);
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      return cdiFallbackIterator_copyVariableName(me);

    default: Error(kUnexpectedFileTypeMessage); return NULL;
  }
}

/*
@Function cdiIterator_inqGridId
@Title Get the ID of the current grid

@Prototype int cdiIterator_inqGridId(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Result A gridId that can be used for further introspection.

@Description
    This provides access to the grid related metadata.
    The resulting ID is only valid until the next time cdiIterator_nextField() is called.
*/
int
cdiIterator_inqGridId(CdiIterator *me)
{
  sanityCheck(me);
  return me->gridId;
}

/*
@Function cdiIterator_readField
@Title Read the whole field into a double buffer

@Prototype void cdiIterator_readField(CdiIterator *me, double *buffer, SizeType *numMissVals)
@Parameter
    @item iterator The iterator to operate on.
    @item buffer A pointer to the double array that the data should be written to.
    @item numMissVals A pointer to a variable where the count of missing values will be stored. May be NULL.

@Description
    It is assumed that the caller first analyses the return value of cdiIterator_inqGridId to determine the required size of the
buffer. Failing to do so results in undefined behavior. You have been warned.
*/
void
cdiIterator_readField(CdiIterator *me, double *buffer, SizeType *numMissVals)
{
  size_t numMiss = 0;
  sanityCheck(me);
  if (!buffer) xabort("NULL was passed in a buffer. Please provide a suitable buffer.");
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: cdiGribIterator_readField(me, buffer, &numMiss); return;
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      cdiFallbackIterator_readField(me, buffer, &numMiss);
      return;
    default: Error(kUnexpectedFileTypeMessage);
  }

  *numMissVals = (SizeType) numMiss;
}

/*
@Function cdiIterator_readFieldF
@Title Read the whole field into a double buffer

@Prototype void cdiIterator_readFieldF(CdiIterator  me, float *buffer, SizeType *numMissVals)
@Parameter
    @item iterator The iterator to operate on.
    @item buffer   A pointer to the double array that the data should be written to.
    @item numMissVals    A pointer to a variable where the count of missing values will be stored. May be NULL.

@Description
    It is assumed that the caller first analyses the return value of cdiIterator_inqGridId to determine the required size of the
buffer. Failing to do so results in undefined behavior. You have been warned.
*/
void
cdiIterator_readFieldF(CdiIterator *me, float *buffer, SizeType *numMissVals)
{
  size_t numMiss = 0;
  sanityCheck(me);
  if (!buffer) xabort("NULL was passed in a buffer. Please provide a suitable buffer.");
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: cdiGribIterator_readFieldF(me, buffer, &numMiss); return;
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      cdiFallbackIterator_readFieldF(me, buffer, &numMiss);
      return;
    default: Error(kUnexpectedFileTypeMessage);
  }

  *numMissVals = (SizeType) numMiss;
}

/*
@Function cdiIterator_delete
@Title Destroy an iterator

@Prototype void cdiIterator_delete(CdiIterator* iterator)
@Parameter
    @item iterator The iterator to operate on.

@Description
    Combined destructor & deallocator.
*/
void
cdiIterator_delete(CdiIterator *me)
{
  if (!me) xabort("NULL was passed in as an iterator. Please check the return value of cdiIterator_new().");
  switch (cdiBaseFiletype(me->filetype))
  {
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRIB: cdiGribIterator_delete((CdiGribIterator *) me); break;
#endif

#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
#endif
      cdiFallbackIterator_delete(me);
      break;

    default: Error(kUnexpectedFileTypeMessage);
  }
}

void
baseIterDestruct(CdiIterator *me)
{
  /*currently empty, but that's no reason not to call it*/
  (void) me;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */



#include <assert.h>
#include <limits.h>
#include <stdlib.h>

// On Windows, define ssize_t manually
#ifdef _WIN32
#define ssize_t __int64
#else
#include <unistd.h>
#endif

struct CdiFallbackIterator
{
  CdiIterator super;
  char *path;  // needed for clone() & serialize()
  int streamId, vlistId, subtypeId;

  int variableCount, curVariable;
  int curLevelCount, curLevel;
  int curSubtypeCount, curSubtype;
  int curTimestep;
};

CdiIterator *
cdiFallbackIterator_getSuper(CdiFallbackIterator *me)
{
  return &me->super;
}

CdiIterator *
cdiFallbackIterator_new(const char *path, int filetype)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) Malloc(sizeof(*me));
  baseIterConstruct(&me->super, filetype);
  me->subtypeId = CDI_UNDEFID;  // Will be set in cdiFallbackIterator_nextField()
  me->curSubtypeCount = -1;     // Will be set in cdiFallbackIterator_nextField()
  me->curLevelCount = -1;       // Will be set in cdiFallbackIterator_nextField()
  // These values are chosen so that the natural increment at the start of cdiFallbackIterator_nextField() will correctly position
  // us at the first slice.
  me->curTimestep = 0;
  me->curVariable = -1;
  me->curSubtype = -1;
  me->curLevel = -1;
  me->streamId = streamOpenRead(path);
  if (me->streamId != CDI_UNDEFID)
  {
    me->vlistId = streamInqVlist(me->streamId);
    if (me->vlistId != CDI_UNDEFID && (me->variableCount = vlistNvars(me->vlistId)) > 0
        && streamInqTimestep(me->streamId, me->curTimestep) > 0 && (me->path = strdup(path)))
    {
      return (CdiIterator *) me;
    }
    Free(me->path);
    streamClose(me->streamId);
  }
  baseIterDestruct(&me->super);
  Free(me);
  return NULL;
}

void
cdiFallbackIterator_delete(CdiIterator *super)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  Free(me->path);
  streamClose(me->streamId);
  baseIterDestruct(super);
  Free(me);
}

// Fetches the info that is derived from the current variable. Most of this is published by the data members in the base class.
static void
fetchVariableInfo(CdiFallbackIterator *me)
{
  // Fetch data that's published via base class data members.
  me->super.datatype = vlistInqVarDatatype(me->vlistId, me->curVariable);
  me->super.timesteptype = vlistInqVarTsteptype(me->vlistId, me->curVariable);
  me->super.gridId = vlistInqVarGrid(me->vlistId, me->curVariable);
  int param = vlistInqVarParam(me->vlistId, me->curVariable);
  cdiDecodeParam(param, &me->super.param.number, &me->super.param.category, &me->super.param.discipline);

  // Fetch the current level and subtype counts.
  me->curLevelCount = zaxisInqSize(vlistInqVarZaxis(me->vlistId, me->curVariable));
  me->subtypeId = vlistInqVarSubtype(me->vlistId, me->curVariable);
  me->curSubtypeCount = (me->subtypeId == CDI_UNDEFID) ? 1 : subtypeInqSize(me->subtypeId);
}

CdiFallbackIterator *
cdiFallbackIterator_clone(CdiIterator *super)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;

  // Make another stream for this file. This yields an unadvanced iterator.
  CdiFallbackIterator *clone = (CdiFallbackIterator *) (void *) cdiFallbackIterator_new(me->path, me->super.filetype);
  if (clone)
  {
    // Point the clone to the same position in the file.
    clone->variableCount = me->variableCount;
    clone->curVariable = me->curVariable;
    clone->curLevelCount = me->curLevelCount;
    clone->curLevel = me->curLevel;
    clone->curSubtypeCount = me->curSubtypeCount;
    clone->curSubtype = me->curSubtype;
    clone->curTimestep = me->curTimestep;

    clone->super.isAdvanced = super->isAdvanced;
    if (super->isAdvanced) fetchVariableInfo(clone);
  }

  return clone;
}

char *
cdiFallbackIterator_serialize(CdiIterator *super)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;

  char *escapedPath = cdiEscapeSpaces(me->path);
  size_t len = strlen(escapedPath) + 7 * (3 * sizeof(int) * CHAR_BIT / 8 + 1) + 1;
  char *result = (char *) Malloc(len);
  snprintf(result, len, "%s %d %d %d %d %d %d %d", escapedPath, me->variableCount, me->curVariable, me->curLevelCount, me->curLevel,
           me->curSubtypeCount, me->curSubtype, me->curTimestep);
  Free(escapedPath);
  return result;
}

CdiFallbackIterator *
cdiFallbackIterator_deserialize(const char *description)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) Malloc(sizeof(*me));
  if (!me) goto fail;

  description = baseIter_constructFromString(&me->super, description);

  while (*description == ' ') description++;
  me->path = cdiUnescapeSpaces(description, &description);
  if (!me->path) goto destructSuper;

  me->streamId = streamOpenRead(me->path);
  if (me->streamId == CDI_UNDEFID) goto freePath;
  me->vlistId = streamInqVlist(me->streamId);
  if (me->vlistId == CDI_UNDEFID) goto closeStream;

    // This reads one variable from the description string, does error checking, and advances the given string pointer.
#define decodeValue(variable, description)                                                                                    \
  do {                                                                                                                        \
    const char *savedStart = description;                                                                                     \
    long long decodedValue                                                                                                    \
        = strtoll(description, (char **) &description, 0); /*The cast is a workaround for the wrong signature of strtoll().*/ \
    variable = (int) decodedValue;                                                                                            \
    if (savedStart == description) goto closeStream;                                                                          \
    if ((long long) decodedValue != (long long) variable) goto closeStream;                                                   \
  } while (0)
  decodeValue(me->variableCount, description);
  decodeValue(me->curVariable, description);
  decodeValue(me->curLevelCount, description);
  decodeValue(me->curLevel, description);
  decodeValue(me->curSubtypeCount, description);
  decodeValue(me->curSubtype, description);
  decodeValue(me->curTimestep, description);
#undef decodeValue

  if (streamInqTimestep(me->streamId, me->curTimestep) <= 0) goto closeStream;
  if (me->super.isAdvanced) fetchVariableInfo(me);

  return me;

closeStream:
  streamClose(me->streamId);
freePath:
  Free(me->path);
destructSuper:
  baseIterDestruct(&me->super);
  Free(me);
fail:
  return NULL;
}

static int
advance(CdiFallbackIterator *me)
{
  me->curLevel++;
  if (me->curLevel >= me->curLevelCount)
  {
    me->curLevel = 0;
    me->curSubtype++;
    if (me->curSubtype >= me->curSubtypeCount)
    {
      me->curSubtype = 0;
      me->curVariable++;
      if (me->curVariable >= me->variableCount)
      {
        me->curVariable = 0;
        me->curTimestep++;
        if (streamInqTimestep(me->streamId, me->curTimestep) <= 0) return CDI_EEOF;
      }
    }
  }
  return CDI_NOERR;
}

int
cdiFallbackIterator_nextField(CdiIterator *super)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  int result = advance(me);
  if (result) return result;

  if (!me->curLevel && !me->curSubtype)
    fetchVariableInfo(me);  // Check whether we are processing a new variable/timestep and fetch the information that may have
                            // changed in this case.
  return CDI_NOERR;
}

char *
cdiFallbackIterator_inqTime(CdiIterator *super, CdiTimeType timeType)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;

  // retrieve the time information
  int taxisId = vlistInqTaxis(me->vlistId);
  int date = 0, time = 0;
  switch (timeType)
  {
    case kCdiTimeType_referenceTime:
      date = taxisInqRdate(taxisId);
      time = taxisInqRtime(taxisId);
      break;

    case kCdiTimeType_startTime:
      date = taxisInqVdate(taxisId);
      time = taxisInqVtime(taxisId);
      break;

    case kCdiTimeType_endTime:
      return NULL;  // The stream interface does not export the start/end times of statistical fields, so we treat all data as point
                    // of time data, returning the validity time as the start time.

    default: assert(0 && "internal error, please report this bug");
  }

  // decode the time information and reencode it into an ISO-compliant string
  int year, month, day, hour, minute, second;
  cdiDecodeDate(date, &year, &month, &day);
  cdiDecodeTime(time, &hour, &minute, &second);
  size_t len = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 4 + 1;
  char *result = (char *) Malloc(len);
  snprintf(result, len, "%04d-%02d-%02dT%02d:%02d:%02d.000", year, month, day, hour, minute, second);
  return result;
}

int
cdiFallbackIterator_levelType(CdiIterator *super, int levelSelector, char **outName, char **outLongName, char **outStdName,
                              char **outUnit)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  int zaxisId = vlistInqVarZaxis(me->vlistId, me->curVariable);
  (void) levelSelector;
#define copyString(outPointer, key)                                   \
  do {                                                                \
    if (outPointer)                                                   \
    {                                                                 \
      char tempBuffer[CDI_MAX_NAME];                                  \
      int length = CDI_MAX_NAME;                                      \
      cdiInqKeyString(zaxisId, CDI_GLOBAL, key, tempBuffer, &length); \
      *outPointer = strdup(tempBuffer);                               \
    }                                                                 \
  } while (0)
  copyString(outName, CDI_KEY_NAME);
  copyString(outLongName, CDI_KEY_LONGNAME);
  copyString(outStdName, CDI_KEY_STDNAME);
  copyString(outUnit, CDI_KEY_UNITS);
#undef copyString
  int ltype = 0;
  cdiInqKeyInt(zaxisId, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, &ltype);
  return ltype;
}

int
cdiFallbackIterator_level(CdiIterator *super, int levelSelector, double *outValue1, double *outValue2)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  int zaxisId = vlistInqVarZaxis(me->vlistId, me->curVariable);

  // handle NULL pointers once and for all
  double trash;
  if (!outValue1) outValue1 = &trash;
  if (!outValue2) outValue2 = &trash;

  // get the level value
  if (levelSelector)
  {
    *outValue1 = (zaxisInqLbounds(zaxisId, NULL)) ? zaxisInqLbound(zaxisId, me->curLevel) : zaxisInqLevel(zaxisId, me->curLevel);
  }
  else
  {
    *outValue1 = (zaxisInqUbounds(zaxisId, NULL)) ? zaxisInqUbound(zaxisId, me->curLevel) : zaxisInqLevel(zaxisId, me->curLevel);
  }
  *outValue2 = 0.0;

  // if this is a hybrid zaxis, lookup the coordinates in the vertical coordinate table
  ssize_t intLevel = (ssize_t) (2 * *outValue1);
  if (0 <= intLevel && intLevel < zaxisInqVctSize(zaxisId) - 1)
  {
    const double *coordinateTable = zaxisInqVctPtr(zaxisId);
    *outValue1 = coordinateTable[intLevel];
    *outValue2 = coordinateTable[intLevel + 1];
  }
  return CDI_NOERR;
}

int
cdiFallbackIterator_zaxisUuid(CdiIterator *super, int *outVgridNumber, int *outLevelCount, unsigned char outUuid[CDI_UUID_SIZE])
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  int zaxisId = vlistInqVarZaxis(me->vlistId, me->curVariable);
  int ltype = 0;
  cdiInqKeyInt(zaxisId, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, &ltype);
  if (ltype != ZAXIS_HYBRID) return CDI_EINVAL;
  if (outVgridNumber)
  {
    *outVgridNumber = 0;
    cdiInqKeyInt(zaxisId, CDI_GLOBAL, CDI_KEY_NUMBEROFVGRIDUSED, outVgridNumber);
  }
  if (outLevelCount)
  {
    *outLevelCount = 0;
    cdiInqKeyInt(zaxisId, CDI_GLOBAL, CDI_KEY_NLEV, outLevelCount);
  }
  if (outUuid)
  {
    int length = CDI_UUID_SIZE;
    memset(outUuid, 0, (size_t) length);
    cdiInqKeyBytes(zaxisId, CDI_GLOBAL, CDI_KEY_UUID, outUuid, &length);
  }
  return CDI_NOERR;
}

int
cdiFallbackIterator_inqTile(CdiIterator *super, int *outTileIndex, int *outTileAttribute)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  int dummy = 0;
  if (!outTileIndex) outTileIndex = &dummy;
  if (!outTileAttribute) outTileAttribute = &dummy;

  int error = CDI_NOERR;
  if (me->subtypeId == CDI_UNDEFID)  // must not call subtypeInqAttribute() with an invalid subtype ID, because it would abort the
                                     // program instead of returning an error
  {
    error = CDI_EINVAL;
  }
  else
  {
    if (subtypeInqAttribute(me->subtypeId, me->curSubtype, "tileIndex", outTileIndex)) error = CDI_EINVAL;
    if (subtypeInqAttribute(me->subtypeId, me->curSubtype, "tileAttribute", outTileAttribute)) error = CDI_EINVAL;
  }
  if (error) *outTileIndex = *outTileAttribute = -1;  // Guarantee defined values in case of an error.
  return error;
}

int
cdiFallbackIterator_inqTileCount(CdiIterator *super, int *outTileCount, int *outTileAttributeCount)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  int temp = 0;
  if (!outTileCount) outTileCount = &temp;
  if (!outTileAttributeCount) outTileAttributeCount = &temp;

  int error = CDI_NOERR;
  if (me->subtypeId == CDI_UNDEFID)  // must not call subtypeInqAttribute() with an invalid subtype ID, because it would abort the
                                     // program instead of returning an error
  {
    error = CDI_EINVAL;
  }
  else
  {
    if (subtypeInqAttribute(me->subtypeId, me->curSubtype, "numberOfTiles", outTileCount)) error = CDI_EINVAL;
    if (subtypeInqAttribute(me->subtypeId, me->curSubtype, "numberOfTileAttributes", outTileAttributeCount)) error = CDI_EINVAL;
  }
  if (error) *outTileCount = *outTileAttributeCount = -1;  // Guarantee defined values in case of an error.
  return CDI_NOERR;
}

char *
cdiFallbackIterator_copyVariableName(CdiIterator *super)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  return vlistCopyVarName(me->vlistId, me->curVariable);
}

void
cdiFallbackIterator_readField(CdiIterator *super, double *buffer, size_t *numMissVals)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  SizeType missingValues = 0;
  streamReadVarSlice(me->streamId, me->curVariable, me->curLevel, buffer, &missingValues);
  if (numMissVals) *numMissVals = (size_t) missingValues;
}

void
cdiFallbackIterator_readFieldF(CdiIterator *super, float *buffer, size_t *numMissVals)
{
  CdiFallbackIterator *me = (CdiFallbackIterator *) (void *) super;
  SizeType missingValues = 0;
  streamReadVarSliceF(me->streamId, me->curVariable, me->curLevel, buffer, &missingValues);
  if (numMissVals) *numMissVals = (size_t) missingValues;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef STREAM_GRB_H
#define STREAM_GRB_H

double zaxis_units_to_centimeter(int zaxisID);
double zaxis_units_to_meter(int zaxisID);
bool zaxis_units_is_Pa(int zaxisID);

void ensureBufferSize(size_t requiredSize, size_t *curSize, void **buffer);
int grbDecompress(size_t recsize, size_t *buffersize, void **gribbuffer);

static inline void
grib_check_recsize(int version, size_t recsize)
{
  if (recsize > 0xFFFFFFFF) Error("GRIB%d record size=%zu limit exceeded (max=%zu)", version, recsize, 0xFFFFFFFF);
}

static inline bool
gribbyte_get_bit(int number, int bit)
{
  return (bool) ((number >> (8 - bit)) & 1);
}
static inline void
gribbyte_set_bit(int *number, int bit)
{
  *number |= 1 << (8 - bit);
}
static inline void
gribbyte_clear_bit(int *number, int bit)
{
  *number &= ~(1 << (8 - bit));
}

int grbBitsPerValue(int datatype);

int fdbInqContents(stream_t *streamptr);
long grbInqContents(stream_t *streamptr);
int fdbInqTimestep(stream_t *streamptr, int tsID);
int grbInqTimestep(stream_t *streamptr, int tsID);

void grbDefField(stream_t *streamptr);
void grb_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals);
void grb_write_field(stream_t *streamptr, int memtype, const void *data, size_t numMissVals);
void grbCopyField(stream_t *streamptr2, stream_t *streamptr1);

void grb_read_var(stream_t *streamptr, int varID, int memtype, void *data, size_t *numMissVals);
void grb_write_var(stream_t *streamptr, int varID, int memtype, const void *data, size_t numMissVals);

void grb_read_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, void *data, size_t *numMissVals);
void grb_write_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, const void *data, size_t numMissVals);

int grib1ltypeToZaxisType(int grib_ltype);
int grib2ltypeToZaxisType(int grib_ltype);

int zaxisTypeToGrib1ltype(int zaxistype);
int zaxisTypeToGrib2ltype(int zaxistype);

int grbGetGridtype(int *gridID, SizeType gridsize, bool *gridIsRotated, bool *gridIsCurvilinear);

struct cdiGribParamChange
{
  int code, ltype, lev;
  bool active;
};

struct cdiGribScanModeChange
{
  int value;
  bool active;
};

extern struct cdiGribParamChange cdiGribChangeParameterID;
extern struct cdiGribScanModeChange cdiGribDataScanningMode;

// Used in CDO
void streamGrbChangeParameterIdentification(int code, int ltype, int lev);
void streamGrbDefDataScanningMode(int scanmode);

#endif /* STREAM_GRB_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef ZAXIS_H
#define ZAXIS_H

#ifdef HAVE_CONFIG_H
#endif


// clang-format off
typedef struct
{
  double    *vals;
#ifndef USE_MPI
  char     **cvals;
  int        clength;
#endif
  double    *lbounds;
  double    *ubounds;
  double    *weights;
  int        self;
  int        scalar;
  int        type;
  int        size;
  int        direction;
  int        vctsize;
  unsigned   positive;
  double    *vct;
  cdi_keys_t keys;
  cdi_atts_t atts;
}
zaxis_t;
// clang-format on

void zaxisGetTypeDescription(int zaxisType, int *outPositive, const char **outName, const char **outLongName,
                             const char **outStdName,
                             const char **outUnit);  // The returned const char* point to static storage. Don't free or modify them.

unsigned cdiZaxisCount(void);

zaxis_t *zaxis_to_pointer(int zaxisID);

void cdiZaxisGetIndexList(unsigned numIDs, int *IDs);

int zaxisUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id);

const resOps *getZaxisOps(void);

const char *zaxisInqNamePtr(int zaxisID);

const double *zaxisInqLevelsPtr(int zaxisID);
#ifndef USE_MPI
char **zaxisInqCValsPtr(int zaxisID);
#endif
void zaxisResize(int zaxisID, int size);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif



#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_LIBGRIB_API

struct CdiGribIterator
{
  CdiIterator super;

  CdiInputFile *file;
  off_t fileOffset;
  unsigned char *gribBuffer;
  size_t bufferSize, curRecordSize;
  grib_handle *gribHandle;
};

CdiIterator *
cdiGribIterator_getSuper(CdiGribIterator *me)
{
  return &me->super;
}

// Since the error handling in constructors is usually very closely related to the workings of a destructor,
// this function combines both functions in one, using a centralized exit.
// The mode of operation depends on whether me is a NULL pointer on entry:
// If it is NULL, a new object is allocated and constructed, which is returned if construction is successful.
// If a non-NULL pointer is passed in, the object is destructed and NULL is returned. In this case, the other arguments are ignored.
static CdiGribIterator *
cdiGribIterator_condestruct(CdiGribIterator *me, const char *path, int filetype)
{
#define super() (&me->super)
  if (me) goto destruct;
  me = (CdiGribIterator *) Malloc(sizeof(*me));
  baseIterConstruct(super(), filetype);

  me->file = cdiInputFile_make(path);
  if (!me->file) goto destructSuper;
  me->fileOffset = 0;
  me->gribHandle = NULL;
  me->gribBuffer = NULL;
  me->bufferSize = me->curRecordSize = 0;
  me->super.gridId = CDI_UNDEFID;

  goto success;

  // ^        constructor code        ^
  // |                                |
  // v destructor/error-cleanup code  v

destruct:
  if (me->super.gridId != CDI_UNDEFID) gridDestroy(me->super.gridId);
  if (me->gribHandle) grib_handle_delete((struct grib_handle *) me->gribHandle);
  Free(me->gribBuffer);
  cdiRefObject_release(&me->file->super);
destructSuper:
  baseIterDestruct(super());
  Free(me);
  me = NULL;

success:
  return me;
#undef super
}

CdiIterator *
cdiGribIterator_new(const char *path, int filetype)
{
  return &cdiGribIterator_condestruct(NULL, path, filetype)->super;
}

CdiGribIterator *
cdiGribIterator_makeClone(CdiIterator *super)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  // Allocate memory and copy data. (operations that may fail)
  CdiGribIterator *result = (struct CdiGribIterator *) Malloc(sizeof(*result));
  if (!result) goto fail;

  result->file = me->file;
  result->fileOffset = me->fileOffset;
  result->gribBuffer = NULL;
  result->bufferSize = me->bufferSize;
  result->curRecordSize = me->curRecordSize;
  result->gribHandle = NULL;

  if (me->gribBuffer)
  {
    result->gribBuffer = (unsigned char *) Malloc(me->bufferSize);
    if (!result->gribBuffer) goto freeResult;
    memcpy(result->gribBuffer, me->gribBuffer, me->curRecordSize);
  }
  if (me->gribHandle)
  {
    result->gribHandle = grib_handle_new_from_message(NULL, result->gribBuffer, result->curRecordSize);
    if (!result->gribHandle) goto freeBuffer;
  }
  if (super->gridId != CDI_UNDEFID)
  {
    result->super.gridId = gridDuplicate(super->gridId);
    if (result->super.gridId == CDI_UNDEFID) goto deleteGribHandle;
  }

  // Finish construction. (operations that cannot fail)
  baseIterConstruct(&result->super, super->filetype);
  result->super.datatype = super->datatype;
  result->super.timesteptype = super->timesteptype;
  result->super.param = super->param;
  cdiRefObject_retain(&result->file->super);

  return result;

  // Error handling.
deleteGribHandle:
  if (result->gribHandle) grib_handle_delete(result->gribHandle);
freeBuffer:
  Free(result->gribBuffer);
freeResult:
  Free(result);
fail:
  return NULL;
}

char *
cdiGribIterator_serialize(CdiIterator *super)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  const char *path = cdiInputFile_getPath(me->file);
  char *escapedPath = cdiEscapeSpaces(path);
  size_t len = strlen(escapedPath) + 3 * sizeof(int) * CHAR_BIT / 8;
  char *result = (char *) Malloc(len);
  snprintf(result, len, "%s %zu", escapedPath, (size_t) me->fileOffset);
  Free(escapedPath);
  return result;
}

CdiGribIterator *
cdiGribIterator_deserialize(const char *description)
{
  char *path;
  CdiGribIterator *me = (CdiGribIterator *) Malloc(sizeof(*me));
  if (!me) goto fail;

  description = baseIter_constructFromString(&me->super, description);

  while (*description == ' ') description++;
  path = cdiUnescapeSpaces(description, &description);
  if (!path) goto destructSuper;

  me->file = cdiInputFile_make(path);
  Free(path);
  if (!me->file) goto destructSuper;

  {
    const char *savedStart = description;
    char *description_ = (char *) description;
    long long decodedOffset = strtoll(description, &description_, 0);
    description = description_;
    me->fileOffset = (off_t) decodedOffset;
    if (savedStart == description) goto closeFile;
    if ((unsigned long long) decodedOffset > (unsigned long long) me->fileOffset) goto closeFile;
  }

  me->gribBuffer = NULL;
  me->bufferSize = me->curRecordSize = 0;
  me->gribHandle = NULL;
  me->super.gridId = CDI_UNDEFID;
  if (me->super.isAdvanced && cdiGribIterator_nextField(&me->super)) goto closeFile;

  return me;

closeFile:
  cdiRefObject_release(&me->file->super);
destructSuper:
  baseIterDestruct(&me->super);
  Free(me);
fail:
  return NULL;
}

static void
cdiGribIterator_ensureBuffer(CdiGribIterator *me, size_t requiredSize)
{
  if (me->bufferSize < requiredSize)
  {
    me->bufferSize *= 2;
    if (me->bufferSize < requiredSize) me->bufferSize = requiredSize;
    me->gribBuffer = (unsigned char *) Realloc(me->gribBuffer, me->bufferSize);
  }
}

static bool
isGrib1DualLevel(int levelType)
{
  switch (levelType)
  {
    case 101:
    case 104:
    case 106:
    case 108:
    case 110:
    case 112:
    case 114:
    case 116:
    case 120:
    case 121:
    case 128:
    case 141:  // This is the complete list after grib_api-1.12.3/definitions/grib1/sections.1.def:106-117:, the code in
               // cdi/src/stream_gribapi.c:grib1GetLevel() seems to be incomplete.
      return true;
    default: return false;
  }
}

static const unsigned char *
positionOfGribMarker(const unsigned char *data, size_t size)
{
  for (const unsigned char *currentPosition = data, *end = data + size; currentPosition < end; currentPosition++)
  {
    currentPosition = (unsigned char *) memchr(currentPosition, 'G',
                                               size - (size_t) (currentPosition - data)
                                                   - 3);  //-3 to ensure that we don't overrun the buffer during the strncmp() call.
    if (!currentPosition) return NULL;
    if (!strncmp((const char *) currentPosition, "GRIB", 4)) return currentPosition;
  }
  return NULL;
}

// This clobbers the contents of the gribBuffer!
// Returns the file offset of the next 'GRIB' marker.
static ssize_t
scanToGribMarker(CdiGribIterator *me)
{
  cdiGribIterator_ensureBuffer(me, 8 * 1024);
  const size_t kMaxScanSize = 16 * 1024 * 1024;
  for (size_t scannedBytes = 0, scanSize; scannedBytes < kMaxScanSize; scannedBytes += scanSize)
  {
    // Load a chunk of data into our buffer.
    scanSize = me->bufferSize;
    if (scannedBytes + scanSize > kMaxScanSize) scanSize = kMaxScanSize - scannedBytes;
    assert(scanSize <= me->bufferSize);
    int status = cdiInputFile_read(me->file, me->fileOffset + (off_t) scannedBytes, scanSize, &scanSize, me->gribBuffer);
    if (status != CDI_NOERR && status != CDI_EEOF) return -1;

    const unsigned char *startPosition = positionOfGribMarker(me->gribBuffer, scanSize);
    if (startPosition) { return (ssize_t) (me->fileOffset + (off_t) scannedBytes + (off_t) (startPosition - me->gribBuffer)); }

    // Get the offset for the next iteration if there is a next iteration.
    scanSize -= 3;                           // so that we won't miss a 'GRIB' sequence that happens to be cut off
    scanSize &= ~(size_t) 0xf;               // make 16 bytes aligned
    if ((ssize_t) scanSize <= 0) return -1;  // ensure that we make progress
  }
  return -1;
}

static unsigned
decode24(void *beData)
{
  unsigned char *bytes = (unsigned char *) beData;
  return ((unsigned) bytes[0] << 16) + ((unsigned) bytes[1] << 8) + (unsigned) bytes[2];
}

static uint64_t
decode64(void *beData)
{
  unsigned char *bytes = (unsigned char *) beData;
  uint64_t result = 0;
  for (size_t i = 0; i < 8; i++) result = (result << 8) + bytes[i];
  return result;
}

// Determine the size of the GRIB record that begins at the given file offset.
static int
getRecordSize(CdiGribIterator *me, off_t gribFileOffset, size_t *outRecordSize)
{
  char buffer[16];
  size_t readSize;
  int status = cdiInputFile_read(me->file, gribFileOffset, sizeof(buffer), &readSize, buffer);
  if (status != CDI_NOERR && status != CDI_EEOF) return status;
  if (readSize < sizeof(buffer)) return CDI_EEOF;
  *outRecordSize = 0;
  switch (buffer[7])
  {
    case 1:
      *outRecordSize = decode24(&buffer[4]);
      if (*outRecordSize & (1 << 23))
      {
        *outRecordSize = 120 * (*outRecordSize & ((1 << 23) - 1));  // Rescaling for long records.
        // The corresponding code in cgribexlib.c:4532-4570: is much more complicated
        // due to the fact that it subtracts the padding bytes that are inserted after section 4.
        // However, we are only interested in the total size of data we need to read here,
        // so we can ignore the presence of some padding bytes.
      }
      return CDI_NOERR;

    case 2: *outRecordSize = decode64(&buffer[8]); return CDI_NOERR;

    default: return CDI_EUFTYPE;
  }
}

#if 0
static void hexdump(void *data, size_t size)
{
  unsigned char *charData = data;
  for(size_t offset = 0; offset < size; )
    {
      printf("%016zx:", offset);
      for(size_t i = 0; i < 64 && offset < size; i++, offset++)
        {
          if((i & 63) && !(i & 15)) printf(" |");
          if((i & 15) && !(i & 3)) printf("  ");
          printf(" %02x", charData[offset]);
        }
      printf("\n");
    }
}
#endif

// Read a record into memory and wrap it in a grib_handle.
// XXX: I have omitted checking for szip compression as it is done in grbReadVarDP() & friends since that appears to be a
// non-standard extension of the GRIB1 standard: bit 1 in octet 14 of the binary data section which is used to signal szip
// compression is defined to be reserved in the standard. As such, it seems prudent not to support this and to encourage people with
// such szip compressed files to switch to the GRIB2/JPEG2000 format. However, in the case that this reasoning is wrong, this
// function is probably the place to add the check for zsip compression.
static int
readMessage(CdiGribIterator *me)
{
  // Destroy the old grib_handle.
  if (me->gribHandle) grib_handle_delete(me->gribHandle), me->gribHandle = NULL;
  me->fileOffset += (off_t) me->curRecordSize;

  // Find the next record and determine its size.
  ssize_t gribFileOffset = scanToGribMarker(me);
  int result = CDI_EEOF;
  if (gribFileOffset < 0) goto fail;
  result = getRecordSize(me, gribFileOffset, &me->curRecordSize);
  if (result) goto fail;

  // Load the whole record into our buffer and create a grib_handle for it.
  cdiGribIterator_ensureBuffer(me, me->curRecordSize);
  result = cdiInputFile_read(me->file, gribFileOffset, me->curRecordSize, NULL, me->gribBuffer);
  if (result) goto fail;
  me->gribHandle = grib_handle_new_from_message(NULL, me->gribBuffer, me->curRecordSize);
  result = CDI_EUFSTRUCT;
  if (!me->gribHandle) goto fail;

  return CDI_NOERR;

fail:
  me->curRecordSize = 0;  // This ensures that we won't jump to an uncontrolled file position if cdiGribIterator_nextField() is
                          // called another time after it has returned an error.
  return result;
}

int
cdiGribIterator_nextField(CdiIterator *super)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  if (super->gridId != CDI_UNDEFID) gridDestroy(super->gridId), super->gridId = CDI_UNDEFID;

  // Get the next GRIB message into our buffer.
  int result = readMessage(me);
  if (result) return result;

  // Get the metadata that's published as variables in the base class.
  super->datatype = gribGetDatatype(me->gribHandle);
  super->timesteptype = gribapiGetTsteptype(me->gribHandle);
  cdiDecodeParam(gribapiGetParam(me->gribHandle), &super->param.number, &super->param.category, &super->param.discipline);
  grid_t grid;
  gribapiGetGrid(me->gribHandle, &grid);
  super->gridId = gridGenerate(&grid);

  return CDI_NOERR;
}

char *
cdiGribIterator_inqTime(CdiIterator *super, CdiTimeType timeType)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;
  return gribMakeTimeString(me->gribHandle, timeType);
}

int
cdiGribIterator_levelType(CdiIterator *super, int levelSelector, char **outName, char **outLongName, char **outStdName,
                          char **outUnit)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  // First determine the zaxis type corresponding to the given level.
  int zaxisType = ZAXIS_GENERIC;
  if (gribEditionNumber(me->gribHandle) <= 1)
  {
    int levelType = (int) gribGetLongDefault(me->gribHandle, "indicatorOfTypeOfLevel", 255);
    if (levelSelector && !isGrib1DualLevel(levelType)) levelType = 255;
    zaxisType = grib1ltypeToZaxisType(levelType);
  }
  else
  {
    int levelType
        = (int) gribGetLongDefault(me->gribHandle, levelSelector ? "typeOfSecondFixedSurface" : "typeOfFirstFixedSurface", 255);
    zaxisType = grib2ltypeToZaxisType(levelType);
  }

  // Then lookup the requested names.
  const char *name, *longName, *stdName, *unit;
  zaxisGetTypeDescription(zaxisType, NULL, &name, &longName, &stdName, &unit);
  if (outName) *outName = strdup(name);
  if (outLongName) *outLongName = strdup(longName);
  if (outStdName) *outStdName = strdup(stdName);
  if (outUnit) *outUnit = strdup(unit);

  return zaxisType;
}

static double
logicalLevelValue2(long gribType, long storedValue, long power)
{
  double factor = 1;
  assert(power >= 0);
  while (power--) factor *= 10;  // this is precise up to factor == 22.
  switch (gribType)
  {
    case GRIB2_LTYPE_LANDDEPTH:
    case GRIB2_LTYPE_ISOBARIC:
    case GRIB2_LTYPE_SIGMA:
      return (double) storedValue
             * (1000.0 / factor);  // The evaluation order allows the factors of ten to cancel out before rounding.

    case 255: return 0;

    default: return (double) storedValue / factor;
  }
}

// The output values must be preinitialized, this function does not always write them.
static int
readLevel2(grib_handle *gribHandle, const char *levelTypeKey, const char *powerKey, const char *valueKey, double *outValue1,
           double *outValue2)
{
  assert(levelTypeKey && powerKey && valueKey && outValue1 && outValue2);

  long levelType = gribGetLongDefault(gribHandle, levelTypeKey, 255);  // 1 byte
  switch (levelType)
  {
    case 255: break;

    case 105:
    case 113:
    {
      unsigned long value = (unsigned long) gribGetLongDefault(gribHandle, valueKey, 0);
      unsigned long coordinateCount = (unsigned long) gribGetLongDefault(gribHandle, "numberOfCoordinatesValues", 0);
      if (value >= coordinateCount / 2)
      {
        Error("Invalid level coordinate: Level has the hybrid coordinate index %lu, but only %lu coordinate pairs are present.",
              value, coordinateCount / 2);
        return CDI_EUFSTRUCT;
      }
      int status;
      // XXX: I'm not 100% sure about how the coordinate pairs are stored in the file.
      //     I'm assuming an array of pairs due to the example code in grib_api-1.12.3/examples/F90/set_pv.f90, but that may be
      //     wrong.
      if ((status = grib_get_double_element(gribHandle, "pv", (int) value * 2, outValue1))) return status;
      if ((status = grib_get_double_element(gribHandle, "pv", (int) value * 2 + 1, outValue2))) return status;
      break;
    }

    default:
    {
      long power = 255 & gribGetLongDefault(gribHandle, powerKey, 0);  // 1 byte
      if (power == 255) power = 0;
      long value = gribGetLongDefault(gribHandle, valueKey, 0);  // 4 bytes
      *outValue1 = logicalLevelValue2(levelType, value, power);
    }
  }
  return CDI_NOERR;
}

int
cdiGribIterator_level(CdiIterator *super, int levelSelector, double *outValue1, double *outValue2)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;
  double trash;
  if (!outValue1) outValue1 = &trash;
  if (!outValue2) outValue2 = &trash;
  *outValue1 = *outValue2 = 0;

  if (gribEditionNumber(me->gribHandle) > 1)
  {
    if (levelSelector)
    {
      return readLevel2(me->gribHandle, "typeOfFirstFixedSurface", "scaleFactorOfFirstFixedSurface",
                        "scaledValueOfFirstFixedSurface", outValue1, outValue2);
    }
    else
    {
      return readLevel2(me->gribHandle, "typeOfSecondFixedSurface", "scaleFactorOfSecondFixedSurface",
                        "scaledValueOfSecondFixedSurface", outValue1, outValue2);
    }
  }
  else
  {
    long levelType = (uint8_t) gribGetLongDefault(me->gribHandle, "indicatorOfTypeOfLevel", -1);  // 1 byte
    if (levelType == 255) {}
    else if (isGrib1DualLevel((int) levelType))
    {
      *outValue1 = (double) (gribGetLongDefault(me->gribHandle, (levelSelector ? "bottomLevel" : "topLevel"), 0));
    }
    else if (levelType == 100)
    {
      *outValue1 = 100 * (double) (gribGetLongDefault(me->gribHandle, "level", 0));  // 2 bytes
    }
    else
    {
      *outValue1 = (double) (gribGetLongDefault(me->gribHandle, "level", 0));  // 2 bytes
    }
  }
  return CDI_NOERR;
}

int
cdiGribIterator_zaxisUuid(CdiIterator *super, int *outVgridNumber, int *outLevelCount, unsigned char outUuid[CDI_UUID_SIZE])
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  if (outVgridNumber)
  {
    long temp;
    if (grib_get_long(me->gribHandle, "numberOfVGridUsed", &temp)) return CDI_EINVAL;
    *outVgridNumber = (int) temp;
  }
  if (outLevelCount)
  {
    long temp;
    if (grib_get_long(me->gribHandle, "nlev", &temp)) return CDI_EINVAL;
    *outLevelCount = (int) temp;
  }
  if (outUuid)
  {
    size_t size = CDI_UUID_SIZE;
    if (grib_get_bytes(me->gribHandle, "uuidOfVGrid", outUuid, &size)) return CDI_EINVAL;
    if (size != CDI_UUID_SIZE) return CDI_EUFSTRUCT;
  }

  return CDI_NOERR;
}

int
cdiGribIterator_inqTile(CdiIterator *super, int *outTileIndex, int *outTileAttribute)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;
  int trash;
  if (!outTileIndex) outTileIndex = &trash;
  if (!outTileAttribute) outTileAttribute = &trash;

  // Get the values if possible.
  int error = CDI_NOERR;
  long value;
  if (grib_get_long(me->gribHandle, "tileIndex", &value)) error = CDI_EINVAL;
  *outTileIndex = (int) value;
  if (grib_get_long(me->gribHandle, "tileAttribute", &value)) error = CDI_EINVAL;
  *outTileAttribute = (int) value;

  // Ensure defined return values in case of failure.
  if (error) *outTileIndex = *outTileAttribute = -1;
  return error;
}

int
cdiGribIterator_inqTileCount(CdiIterator *super, int *outTileCount, int *outTileAttributeCount)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;
  int trash;
  if (!outTileCount) outTileCount = &trash;
  if (!outTileAttributeCount) outTileAttributeCount = &trash;

  // Get the values if possible.
  int error = CDI_NOERR;
  long value;
  if (grib_get_long(me->gribHandle, "numberOfTiles", &value)) error = CDI_EINVAL;
  *outTileCount = (int) value;
  if (grib_get_long(me->gribHandle, "numberOfTileAttributes", &value)) error = CDI_EINVAL;
  *outTileAttributeCount = (int) value;

  // Ensure defined return values in case of failure.
  if (error) *outTileCount = *outTileAttributeCount = 0;
  return error;
}

char *
cdiGribIterator_copyVariableName(CdiIterator *super)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;
  return gribCopyString(me->gribHandle, "shortName");
}

void
cdiGribIterator_readField(CdiIterator *super, double *buffer, size_t *numMissVals)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  GRIB_CHECK(my_grib_set_double(me->gribHandle, "missingValue", CDI_Default_Missval), 0);
  gribGetDoubleArray(me->gribHandle, "values", buffer);
  long gridType = gribGetLong(me->gribHandle, "gridDefinitionTemplateNumber");
  if (numMissVals)
  {
    // The condition excludes harmonic data.
    *numMissVals = (gridType >= 50 && gridType <= 53) ? (size_t) 0 : (size_t) gribGetLong(me->gribHandle, "numberOfMissing");
  }
}

void
cdiGribIterator_readFieldF(CdiIterator *super, float *buffer, size_t *numMissVals)
{
  CdiGribIterator *me = (CdiGribIterator *) (void *) super;

  size_t valueCount = gribGetArraySize(me->gribHandle, "values");
  double *temp = (double *) Malloc(valueCount * sizeof(*temp));
  cdiGribIterator_readField(super, temp, numMissVals);
  for (size_t i = valueCount; i--;) buffer[i] = (float) temp[i];
  Free(temp);
}
#endif

/*
@Function cdiGribIterator_delete
@Title Dispose off a CdiGribIterator instance.

@Prototype void cdiGribIterator_delete(CdiGribIterator *me)
@Parameter
    @item me The iterator to delete.

@Description
    Combined destructor and deallocator. Make sure to match every call to cdiGribIterator_clone() with a call to this function.
*/
void
cdiGribIterator_delete(CdiGribIterator *me)
{
#ifdef HAVE_LIBGRIB_API
  if (me) cdiGribIterator_condestruct(me, NULL, 0);
#else
  if (me)
    xabort(
        "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
#endif
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// callthroughs to provide direct access to the grib keys //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////

/*
@Function cdiGribIterator_inqEdition
@Title Get the version of the GRIB standard that is used

@Prototype int cdiGribIterator_inqEdition(CdiGribIterator *me)
@Parameter
    @item me The iterator to operate on.

@Result The GRIB version.

@Description
    Returns the version of the file format.
*/
int
cdiGribIterator_inqEdition(CdiGribIterator *me)
{
#ifdef HAVE_LIBGRIB_API
  return (int) gribEditionNumber(me->gribHandle);
#else
  (void) me;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_getLong
@Title Access to grib_get_long()

@Prototype int cdiGribIterator_getLong(CdiGribIterator *me, const char *key, long *result)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_long().
*/
int
cdiGribIterator_getLong(CdiGribIterator *me, const char *key, long *result)
{
#ifdef HAVE_LIBGRIB_API
  return grib_get_long(me->gribHandle, key, result);
#else
  (void) me;
  (void) key;
  (void) result;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_getLength
@Title Access to grib_get_length()

@Prototype int cdiGribIterator_getLength(CdiGribIterator *me, const char *key, size_t *result)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_length().
*/
int
cdiGribIterator_getLength(CdiGribIterator *me, const char *key, size_t *result)
{
#ifdef HAVE_LIBGRIB_API
#ifdef HAVE_GRIB_GET_LENGTH
  return grib_get_length(me->gribHandle, key, result);
#else
  (void) me;
  (void) key;
  (void) result;
  Error("grib_get_length() is not available, so cdiGribIterator_getLength() can't be used");
  return -1;
#endif
#else
  (void) me;
  (void) key;
  (void) result;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_getString
@Title Access to grib_get_string()

@Prototype int cdiGribIterator_getString(CdiGribIterator *me, const char *key, char *result, size_t *length)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_string().
*/
int
cdiGribIterator_getString(CdiGribIterator *me, const char *key, char *result, size_t *length)
{
#ifdef HAVE_LIBGRIB_API
  return grib_get_string(me->gribHandle, key, result, length);
#else
  (void) me;
  (void) key;
  (void) result;
  (void) length;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_inqLongValue
@Title Get the value of a GRIB-API key as a long

@Prototype long cdiGribIterator_inqLongValue(CdiGribIterator *me, const char *key)
@Parameter
    @item me The iterator to operate on.
    @item key The GRIB-API key to retrieve.

@Result The value of the key.

@Description
    Use this to fetch a grib value if you are certain that the given key must be present.
    This will abort the process if the key cannot be retrieved.
*/
long
cdiGribIterator_inqLongValue(CdiGribIterator *me, const char *key)
{
#ifdef HAVE_LIBGRIB_API
  return gribGetLong(me->gribHandle, key);
#else
  (void) me;
  (void) key;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_inqLongDefaultValue
@Title Get the value of a GRIB-API key as a long

@Prototype long cdiGribIterator_inqLongDefaultValue(CdiGribIterator *me, const char *key, long defaultValue)
@Parameter
    @item me The iterator to operate on.
    @item key The GRIB-API key to retrieve.
    @item defaultValue The value to return if the key is not present.

@Result The value of the key or the given default value.

@Description
    Use this if you can handle failure to fetch the key by supplying a default value.
    This function cannot fail.
*/
long
cdiGribIterator_inqLongDefaultValue(CdiGribIterator *me, const char *key, long defaultValue)
{
#ifdef HAVE_LIBGRIB_API
  return gribGetLongDefault(me->gribHandle, key, defaultValue);
#else
  (void) me;
  (void) key;
  (void) defaultValue;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_inqStringValue
@Title Safely retrieve a GRIB-API key with a string value

@Prototype char *cdiGribIterator_inqStringValue(CdiGribIterator *me, const char *key)
@Parameter
    @item me The iterator to operate on.
    @item key The GRIB-API key to retrieve.

@Result A malloc'ed string or NULL.

@Description
    This will first call grib_get_length() to inquire the actual size of the string,
    allocate memory accordingly, call grib_get_string(), and return the pointer to the new string.
    Returns NULL on failure.
*/
char *
cdiGribIterator_inqStringValue(CdiGribIterator *me, const char *key)
{
#ifdef HAVE_LIBGRIB_API
  return gribCopyString(me->gribHandle, key);
#else
  (void) me;
  (void) key;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return NULL;
#endif
}

/*
@Function cdiGribIterator_getDouble
@Title Access to grib_get_double()

@Prototype int cdiGribIterator_getDouble(CdiGribIterator *me, const char *key, double *result)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_double().
*/
int
cdiGribIterator_getDouble(CdiGribIterator *me, const char *key, double *result)
{
#ifdef HAVE_LIBGRIB_API
  return grib_get_double(me->gribHandle, key, result);
#else
  (void) me;
  (void) key;
  (void) result;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_getSize
@Title Access to grib_get_size()

@Prototype int cdiGribIterator_getSize(CdiGribIterator *me, const char *key, size_t *result)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_size().
*/
int
cdiGribIterator_getSize(CdiGribIterator *me, const char *key, size_t *result)
{
#ifdef HAVE_LIBGRIB_API
  return grib_get_size(me->gribHandle, key, result);
#else
  (void) me;
  (void) key;
  (void) result;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_getLongArray
@Title Access to grib_get_long_array()

@Prototype int cdiGribIterator_getLongArray(CdiGribIterator *me, const char *key, long *result, size_t *size)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_long_array().
*/
int
cdiGribIterator_getLongArray(CdiGribIterator *me, const char *key, long *result, size_t *size)
{
#ifdef HAVE_LIBGRIB_API
  return grib_get_long_array(me->gribHandle, key, result, size);
#else
  (void) me;
  (void) key;
  (void) result;
  (void) size;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_getDoubleArray
@Title Access to grib_get_double_array()

@Prototype int cdiGribIterator_getDoubleArray(CdiGribIterator *me, const char *key, double *result, size_t *size)
@Parameter
    @item me The iterator to operate on.
    @item ... The arguments to the underlying GRIB-API function.

@Result An error code.

@Description
    Callthrough to grib_get_double_array().
*/
int
cdiGribIterator_getDoubleArray(CdiGribIterator *me, const char *key, double *result, size_t *size)
{
#ifdef HAVE_LIBGRIB_API
  return grib_get_double_array(me->gribHandle, key, result, size);
#else
  (void) me;
  (void) key;
  (void) result;
  (void) size;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_inqDoubleValue
@Title Get the value of a GRIB-API key as a double

@Prototype double cdiGribIterator_inqDoubleValue(CdiGribIterator *me, const char *key)
@Parameter
    @item me The iterator to operate on.
    @item key The GRIB-API key to retrieve.

@Result The value of the key.

@Description
    Use this to fetch a grib value if you are certain that the given key must be present.
    This will abort the process if the key cannot be retrieved.
*/
double
cdiGribIterator_inqDoubleValue(CdiGribIterator *me, const char *key)
{
#ifdef HAVE_LIBGRIB_API
  return gribGetDouble(me->gribHandle, key);
#else
  (void) me;
  (void) key;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
@Function cdiGribIterator_inqDoubleDefaultValue
@Title Get the value of a GRIB-API key as a double

@Prototype double cdiGribIterator_inqDoubleDefaultValue(CdiGribIterator *me, const char *key, double defaultValue)
@Parameter
    @item me The iterator to operate on.
    @item key The GRIB-API key to retrieve.
    @item defaultValue The value to return if the key is not present.

@Result The value of the key or the given default value.

@Description
    Use this if you can handle failure to fetch the key by supplying a default value.
    This function cannot fail.
*/
double
cdiGribIterator_inqDoubleDefaultValue(CdiGribIterator *me, const char *key, double defaultValue)
{
#ifdef HAVE_LIBGRIB_API
  return gribGetDoubleDefault(me->gribHandle, key, defaultValue);
#else
  (void) me;
  (void) key;
  (void) defaultValue;
  xabort(
      "CDI was compiled without GribAPI support, so you can't possibly have a valid CdiGribIterator* to call this function with");
  return -4;
#endif
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <assert.h>
#include <math.h>

// convert Julian calendar day into year, months, day
static void
decode_julday(int calendar, int64_t julianDay,  // Julian day number to convert
              int *year,                        // Gregorian year (out)
              int *mon,                         // Gregorian month (1-12) (out)
              int *day)                         // Gregorian day (1-31) (out)
{
  double a = (double) julianDay;
  assert((int64_t) a == julianDay);
  double b = floor((a - 1867216.25) / 36524.25);
  double c = a + b - floor(b / 4) + 1525;

  if (calendar == CALENDAR_STANDARD || calendar == CALENDAR_GREGORIAN)
    if (a < 2299161) c = a + 1524.;

  double d = floor((c - 122.1) / 365.25);
  double e = floor(365.25 * d);
  double f = floor((c - e) / 30.6001);

  *day = (int) (c - e - floor(30.6001 * f));
  *mon = (int) (f - 1 - 12 * floor(f / 14));
  *year = (int) (d - 4715 - floor((7 + *mon) / 10));
}

// convert year, month, day into Julian calendar day
static int64_t
encode_julday(int calendar, int year, int month, int day)
{
  int iy = (month <= 2) ? year - 1 : year;
  int im = (month <= 2) ? month + 12 : month;
  int ib = (iy < 0) ? ((iy + 1) / 400 - (iy + 1) / 100) : (iy / 400 - iy / 100);

  if (calendar == CALENDAR_STANDARD || calendar == CALENDAR_GREGORIAN)
  {
    if (year > 1582 || (year == 1582 && (month > 10 || (month == 10 && day >= 15))))
    {
      // 15th October 1582 AD or later
    }
    else
    {
      // 4th October 1582 AD or earlier
      ib = -2;
    }
  }

  int64_t julianDay = (int64_t) (floor(365.25 * iy) + trunc(30.6001 * (im + 1)) + ib + 1720996.5 + day + 0.5);

  return julianDay;
}

int64_t
date_to_julday(int calendar, int64_t date)
{
  CdiDate date_struct = cdiDate_set(date);

  return encode_julday(calendar, date_struct.year, date_struct.month, date_struct.day);
}

int64_t
julday_to_date(int calendar, int64_t julianDay)
{
  int year, month, day;
  decode_julday(calendar, julianDay, &year, &month, &day);

  return cdiEncodeDate(year, month, day);
}

int
time_to_sec(int time)
{
  int hour, minute, second;
  cdiDecodeTime(time, &hour, &minute, &second);

  int seconds = hour * 3600 + minute * 60 + second;

  return seconds;
}

int
sec_to_time(int secofday)
{
  int hour = secofday / 3600;
  int minute = secofday / 60 - hour * 60;
  int second = secofday - hour * 3600 - minute * 60;

  return cdiEncodeTime(hour, minute, second);
}

double
secofday_encode(CdiTime time)
{
  int hour = time.hour;
  int minute = time.minute;
  int second = time.second;
  return hour * 3600 + minute * 60 + second + time.ms / 1000.0;
}

CdiTime
secofday_decode(double secondOfDay)
{
  CdiTime time;

  double secondOfDayIntegral;
  time.ms = (short) lround(modf(secondOfDay, &secondOfDayIntegral) * 1000);

  /* because of the limited value range this can be safely improved
   * to use multiplication with inverse */
  double hour = trunc(secondOfDayIntegral / 3600.), minute = trunc(secondOfDayIntegral / 60. - hour * 60.),
         second = secondOfDayIntegral - hour * 3600. - minute * 60.;

  time.hour = (short) hour;
  time.minute = (short) minute;
  time.second = (short) second;

  return time;
}

static int64_t
calendarDay_encode(int calendar, CdiDate date)
{
  int dpy = calendar_dpy(calendar);

  if (dpy == 360 || dpy == 365 || dpy == 366)
    return encode_calday(dpy, date.year, date.month, date.day);
  else
    return encode_julday(calendar, date.year, date.month, date.day);
}

static CdiDate
calendarDay_decode(int calendar, int64_t julday)
{
  int year, month, day;
  int dpy = calendar_dpy(calendar);

  if (dpy == 360 || dpy == 365 || dpy == 366)
    decode_calday(dpy, julday, &year, &month, &day);
  else
    decode_julday(calendar, julday, &year, &month, &day);

  CdiDate date;
  date.year = year;
  date.month = (short) month;
  date.day = (short) day;

  return date;
}

JulianDate
julianDate_encode(int calendar, CdiDateTime dt)
{
  JulianDate julianDate;

  julianDate.julianDay = calendarDay_encode(calendar, dt.date);
  julianDate.secondOfDay = secofday_encode(dt.time);

  return julianDate;
}

CdiDateTime
julianDate_decode(int calendar, JulianDate julianDate)
{
  CdiDateTime dt;

  dt.date = calendarDay_decode(calendar, julianDate.julianDay);
  dt.time = secofday_decode(julianDate.secondOfDay);

  return dt;
}

static void
adjust_seconds(JulianDate *julianDate)
{
  double SecondsPerDay = 86400.0;

  while (julianDate->secondOfDay >= SecondsPerDay)
  {
    julianDate->secondOfDay -= SecondsPerDay;
    julianDate->julianDay++;
  }

  while (julianDate->secondOfDay < 0.0)
  {
    julianDate->secondOfDay += SecondsPerDay;
    julianDate->julianDay--;
  }
}

// add seconds to julianDate
JulianDate
julianDate_add_seconds(JulianDate julianDate, int64_t seconds)
{
  assert(seconds == (int64_t) (double) seconds);
  julianDate.secondOfDay += (double) seconds;

  adjust_seconds(&julianDate);

  return julianDate;
}

// add julianDate1 and julianDate2
JulianDate
julianDate_add(JulianDate julianDate1, JulianDate julianDate2)
{
  JulianDate julianDate;
  julianDate.julianDay = julianDate1.julianDay + julianDate2.julianDay;
  julianDate.secondOfDay = julianDate1.secondOfDay + julianDate2.secondOfDay;

  adjust_seconds(&julianDate);

  return julianDate;
}

// subtract julianDate2 from julianDate1
JulianDate
julianDate_sub(JulianDate julianDate1, JulianDate julianDate2)
{
  JulianDate julianDate;
  julianDate.julianDay = julianDate1.julianDay - julianDate2.julianDay;
  julianDate.secondOfDay = julianDate1.secondOfDay - julianDate2.secondOfDay;

  adjust_seconds(&julianDate);

  return julianDate;
}

double
julianDate_to_seconds(JulianDate julianDate)
{
  assert(julianDate.julianDay == (int64_t) (double) julianDate.julianDay);
  return (double) julianDate.julianDay * 86400.0 + julianDate.secondOfDay;
}

#ifdef TEST2
int
main(void)
{
  int calendar = CALENDAR_STANDARD;
  int factor = 86400;
  int value = 30;

  int year = 1979;
  int month = 1;
  int day = 15;
  int hour = 12;
  int minute = 30;
  int second = 17;
  int ms = 0;

  CdiDateTime dt;
  dt.date = cdiDate_encode(year, month, day);
  dt.time = cdiTime_encode(hour, minute, second, ms);
  printf("%d/%02d/%02d %02d:%02d:%02d.%03d\n", dt.date.year, dt.date.month, dt.date.day, dt.time.hour, dt.time.minute,
         dt.time.second, dt.time.ms);

  JulianDate julianDate = julianDate_encode(calendar, dt);

  dt = julianDate_decode(calendar, julianDate);
  /* FIXME: using PRI64 seems more appropriate */
  printf("%d/%02d/%02d %02d:%02d:%02d.%03d   %d %g\n", dt.date.year, dt.date.month, dt.date.day, dt.time.hour, dt.time.minute,
         dt.time.second, dt.time.ms, (int) julianDate.julianDay, julianDate.secondOfDay);

  for (int i = 0; i < 420; i++)
  {
    dt = julianDate_decode(calendar, julianDate);
    printf("%2d %d/%02d/%02d %02d:%02d:%02d.%03d\n", i, dt.date.year, dt.date.month, dt.date.day, dt.time.hour, dt.time.minute,
           dt.time.second, dt.time.ms);
    julianDate = julianDate_add_seconds(julianDate, value * factor);
  }

  return 0;
}
#endif
#ifndef MODEL_H
#define MODEL_H

int modelUnpack(void *buf, int size, int *position, int originNamespace, void *context, int force_id);

void modelDefaultEntries(void);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <limits.h>
#include <stdlib.h>


#undef CDI_UNDEFID
#define CDI_UNDEFID -1

typedef struct
{
  int self;
  int instID;
  int modelgribID;
  char *name;
} model_t;

static void modelInit(void);

static int modelCompareP(void *modelptr1, void *modelptr2);
static void modelDestroyP(void *modelptr);
static void modelPrintP(void *modelptr, FILE *fp);
static int modelGetSizeP(void *modelptr, void *context);
static void modelPackP(void *modelptr, void *buff, int size, int *position, void *context);
static int modelTxCode(void *modelptr);

static const resOps modelOps = { modelCompareP, modelDestroyP, modelPrintP, modelGetSizeP, modelPackP, modelTxCode };

static void
modelDefaultValue(model_t *modelptr)
{
  modelptr->self = CDI_UNDEFID;
  modelptr->instID = CDI_UNDEFID;
  modelptr->modelgribID = CDI_UNDEFID;
  modelptr->name = NULL;
}

static model_t *
modelNewEntry(cdiResH resH, int instID, int modelgribID, const char *name)
{
  model_t *modelptr = (model_t *) malloc(sizeof(model_t));
  modelDefaultValue(modelptr);
  if (resH == CDI_UNDEFID)
    modelptr->self = reshPut(modelptr, &modelOps);
  else
  {
    modelptr->self = resH;
    reshReplace(resH, modelptr, &modelOps);
  }
  modelptr->instID = instID;
  modelptr->modelgribID = modelgribID;
  if (name && *name) modelptr->name = strdup(name);

  return (modelptr);
}

void
modelDefaultEntries(void)
{
  int instID;
  enum
  {
    nDefModels = 10
  };
  cdiResH resH[nDefModels];

  instID = institutInq(0, 0, "ECMWF", NULL);
  /* (void)    modelDef(instID, 131, "ERA15"); */
  /* (void)    modelDef(instID, 199, "ERA40"); */

  instID = institutInq(98, 232, "MPIMET", NULL);
  resH[0] = modelDef(instID, 64, "ECHAM5.4");
  resH[1] = modelDef(instID, 63, "ECHAM5.3");
  resH[2] = modelDef(instID, 62, "ECHAM5.2");
  resH[3] = modelDef(instID, 61, "ECHAM5.1");

  instID = institutInq(98, 255, "MPIMET", NULL);
  resH[4] = modelDef(instID, 60, "ECHAM5.0");
  resH[5] = modelDef(instID, 50, "ECHAM4");
  resH[6] = modelDef(instID, 110, "MPIOM1");

  instID = institutInq(0, 0, "DWD", NULL);
  resH[7] = modelDef(instID, 149, "GME");

  instID = institutInq(0, 0, "MCH", NULL);
  //(void)  = modelDef(instID, 137, "COSMO");
  resH[8] = modelDef(instID, 255, "COSMO");

  instID = institutInq(0, 1, "NCEP", NULL);
  resH[9] = modelDef(instID, 80, "T62L28MRF");

  /* pre-defined models are not synchronized */
  for (int i = 0; i < nDefModels; i++) reshSetStatus(resH[i], &modelOps, RESH_IN_USE);
}

static void
modelInit(void)
{
  static bool modelInitialized = false;
  if (modelInitialized) return;
}

struct modelLoc
{
  const char *name;
  int instID, modelgribID, resID;
};

static enum cdiApplyRet
findModelByID(int resID, void *res, void *data)
{
  model_t *modelptr = (model_t *) res;
  struct modelLoc *ret = (struct modelLoc *) data;
  int instID = ret->instID, modelgribID = ret->modelgribID;
  if (modelptr->instID == instID && modelptr->modelgribID == modelgribID)
  {
    ret->resID = resID;
    return CDI_APPLY_STOP;
  }
  else
    return CDI_APPLY_GO_ON;
}

static enum cdiApplyRet
findModelByName(int resID, void *res, void *data)
{
  model_t *modelptr = (model_t *) res;
  struct modelLoc *ret = (struct modelLoc *) data;
  int instID = ret->instID, modelgribID = ret->modelgribID;
  const char *name = ret->name;
  if ((instID == -1 || modelptr->instID == instID) && (modelgribID == 0 || modelptr->modelgribID == modelgribID) && modelptr->name)
  {
    const char *p = name, *q = modelptr->name;
    while (*p != '\0' && *p == *q) ++p, ++q;
    if (*p == '\0' || *q == '\0')
    {
      ret->resID = resID;
      return CDI_APPLY_STOP;
    }
  }
  return CDI_APPLY_GO_ON;
}

int
modelInq(int instID, int modelgribID, const char *name)
{
  modelInit();

  struct modelLoc searchState = { .name = name, .instID = instID, .modelgribID = modelgribID, .resID = CDI_UNDEFID };
  if (name && *name)
    cdiResHFilterApply(&modelOps, findModelByName, &searchState);
  else
    cdiResHFilterApply(&modelOps, findModelByID, &searchState);
  return searchState.resID;
}

int
modelDef(int instID, int modelgribID, const char *name)
{
  model_t *modelptr;

  modelInit();

  modelptr = modelNewEntry(CDI_UNDEFID, instID, modelgribID, name);

  return modelptr->self;
}

int
modelInqInstitut(int modelID)
{
  model_t *modelptr = NULL;

  modelInit();

  if (modelID != CDI_UNDEFID) modelptr = (model_t *) reshGetVal(modelID, &modelOps);

  return modelptr ? modelptr->instID : CDI_UNDEFID;
}

int
modelInqGribID(int modelID)
{
  model_t *modelptr = NULL;

  modelInit();

  if (modelID != CDI_UNDEFID) modelptr = (model_t *) reshGetVal(modelID, &modelOps);

  return modelptr ? modelptr->modelgribID : CDI_UNDEFID;
}

const char *
modelInqNamePtr(int modelID)
{
  model_t *modelptr = NULL;

  modelInit();

  if (modelID != CDI_UNDEFID) modelptr = (model_t *) reshGetVal(modelID, &modelOps);

  return modelptr ? modelptr->name : NULL;
}

static int
modelCompareP(void *modelptr1, void *modelptr2)
{
  model_t *model1 = (model_t *) modelptr1, *model2 = (model_t *) modelptr2;
  int diff = (namespaceResHDecode(model1->instID).idx != namespaceResHDecode(model2->instID).idx)
             | (model1->modelgribID != model2->modelgribID) | !str_is_equal(model1->name, model2->name);
  return diff;
}

void
modelDestroyP(void *modelptr)
{
  model_t *mp = (model_t *) modelptr;
  if (mp->name) free(mp->name);
  free(mp);
}

void
modelPrintP(void *modelptr, FILE *fp)
{
  model_t *mp = (model_t *) modelptr;
  fprintf(fp,
          "#\n"
          "# modelID %d\n"
          "#\n"
          "self          = %d\n"
          "instID        = %d\n"
          "modelgribID   = %d\n"
          "name          = %s\n",
          mp->self, mp->self, mp->instID, mp->modelgribID, mp->name ? mp->name : "NN");
}

static int
modelTxCode(void *modelptr)
{
  (void) modelptr;
  return MODEL;
}

enum
{
  MODEL_PACK_INT_SELF,
  MODEL_PACK_INT_INSTID,
  MODEL_PACK_INT_MODELGRIBID,
  MODEL_PACK_INT_NAMELEN,
  modelNints,
};

static int
modelGetSizeP(void *modelptr, void *context)
{
  model_t *p = (model_t *) modelptr;
  size_t txsize = (size_t) serializeGetSize(modelNints, CDI_DATATYPE_INT, context)
                  + (size_t) serializeGetSize(p->name ? (int) strlen(p->name) : 0, CDI_DATATYPE_TXT, context);
  xassert(txsize <= INT_MAX);
  return (int) txsize;
}

static void
modelPackP(void *modelptr, void *buf, int size, int *position, void *context)
{
  model_t *p = (model_t *) modelptr;
  int tempbuf[modelNints];
  tempbuf[MODEL_PACK_INT_SELF] = p->self;
  tempbuf[MODEL_PACK_INT_INSTID] = p->instID;
  tempbuf[MODEL_PACK_INT_MODELGRIBID] = p->modelgribID;
  tempbuf[MODEL_PACK_INT_NAMELEN] = p->name ? (int) strlen(p->name) : 0;
  serializePack(tempbuf, modelNints, CDI_DATATYPE_INT, buf, size, position, context);
  if (p->name) serializePack(p->name, tempbuf[MODEL_PACK_INT_NAMELEN], CDI_DATATYPE_TXT, buf, size, position, context);
}

int
modelUnpack(void *buf, int size, int *position, int originNamespace, void *context, int force_id)
{
#define adaptKey(key) (namespaceAdaptKey((key), originNamespace))
  int tempbuf[modelNints];
  char *name;
  serializeUnpack(buf, size, position, tempbuf, modelNints, CDI_DATATYPE_INT, context);
  if (tempbuf[MODEL_PACK_INT_NAMELEN] != 0)
  {
    size_t len = (size_t) tempbuf[MODEL_PACK_INT_NAMELEN];
    name = (char *) malloc(len + 1);
    serializeUnpack(buf, size, position, name, tempbuf[MODEL_PACK_INT_NAMELEN], CDI_DATATYPE_TXT, context);
    name[len] = '\0';
  }
  else { name = (char *) ""; }
  int targetID = adaptKey(tempbuf[MODEL_PACK_INT_SELF]);
  model_t *mp = modelNewEntry(force_id ? targetID : CDI_UNDEFID, adaptKey(tempbuf[MODEL_PACK_INT_INSTID]),
                              tempbuf[MODEL_PACK_INT_MODELGRIBID], name);
  if (tempbuf[MODEL_PACK_INT_NAMELEN] != 0) free(name);
  xassert(!force_id || (mp->self == adaptKey(tempbuf[0])));
  reshSetStatus(mp->self, &modelOps, reshGetStatus(mp->self, &modelOps) & ~RESH_SYNC_BIT);
#undef adaptKey
  return mp->self;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef STREAM_CDF_POSTDEF_H
#define STREAM_CDF_POSTDEF_H

#include <stdlib.h>


struct cdfPostDefAction
{
  void *data;
  void (*action)(void *data);
  void (*cleanup)(void *data);
};

struct cdfPostDefActionList
{
  size_t size, len;
  struct cdfPostDefAction actions[];
};

void cdfPostDefActionGridProp(stream_t *streamptr, int gridID, int ncvarid, enum gridPropInq gridProp,
                              struct cdfPostDefActionList **delayed);

typedef void (*cdfFuncPtrPostDefActionGridProp)(stream_t *streamptr, int gridID, int ncvarid, enum gridPropInq gridProp,
                                                struct cdfPostDefActionList **delayed);

struct cdfPostDefActionList *cdfPostDefActionAdd(struct cdfPostDefActionList *list, struct cdfPostDefAction addendum);

void cdfDelayedPutVarDeepCleanup(void *data);

void cdfPostDefActionAddPutVal(struct cdfPostDefActionList **delayed, int fileID, int ncvarid, const double *values,
                               void (*cleanup)(void *));

#endif
#ifdef HAVE_CONFIG_H
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 /* PTHREAD_MUTEX_RECURSIVE */
#endif

#include <limits.h>
#include <stdio.h>

#ifdef HAVE_LIBNETCDF
#endif

static unsigned nNamespaces = 1;
static int activeNamespace = 0;

#ifdef HAVE_LIBNETCDF
// clang-format off
#define CDI_NETCDF_SWITCHES                              \
  { .func = (void (*)(void)) nc__create },               \
  { .func = (void (*)(void)) cdf_def_var_serial },       \
  { .func = (void (*)(void)) cdi_nc_enddef_serial },     \
  { .func = (void (*)(void)) cdi_nc__enddef_serial },    \
  { .func = (void (*)(void)) cdfDefTimestep },           \
  { .func = (void (*)(void)) cdfDefCoordinateVars },     \
  { .func = (void (*)(void)) cdfPostDefActionGridProp }
// clang-format on
#else
#define CDI_NETCDF_SWITCHES
#endif

// clang-format off
#define defaultSwitches {                                       \
    { .func = (void (*)(void)) cdiAbortC_serial },              \
    { .func = (void (*)(void)) cdiWarning },                    \
    { .func = (void (*)(void)) serializeGetSizeInCore },        \
    { .func = (void (*)(void)) serializePackInCore },           \
    { .func = (void (*)(void)) serializeUnpackInCore },         \
    { .func = (void (*)(void)) fileOpen_serial },               \
    { .func = (void (*)(void)) fileWrite },                     \
    { .func = (void (*)(void)) fileClose_serial },              \
    { .func = (void (*)(void)) cdiStreamOpenDefaultDelegate },  \
    { .func = (void (*)(void)) cdiStreamDefVlist_ },            \
    { .func = (void (*)(void)) cdiStreamSetupVlist_ },          \
    { .func = (void (*)(void)) cdiStreamWriteVar_ },            \
    { .func = (void (*)(void)) cdiStreamWriteVarChunk_ },       \
    { .func = (void (*)(void)) 0 },                             \
    { .func = (void (*)(void)) 0 },                             \
    { .func = (void (*)(void)) cdiStreamCloseDefaultDelegate }, \
    { .func = (void (*)(void)) cdiStreamDefTimestep_ },         \
    { .func = (void (*)(void)) cdiStreamSync_ },                \
    { .func = (void (*)(void)) cdiVlistDestroy_ },              \
    CDI_NETCDF_SWITCHES                                         \
    }
// clang-format on

#if defined(SX) || defined(__cplusplus)
static const union namespaceSwitchValue defaultSwitches_[NUM_NAMESPACE_SWITCH] = defaultSwitches;
#endif

enum namespaceStatus
{
  NAMESPACE_STATUS_INUSE,
  NAMESPACE_STATUS_UNUSED,
};

static union namespaceSwitchValue initialSwitches[NUM_NAMESPACE_SWITCH] = defaultSwitches;

static struct Namespace
{
  enum namespaceStatus resStage;
  unsigned numSwitches;
  union namespaceSwitchValue *switches;
} initialNamespace = { .resStage = NAMESPACE_STATUS_INUSE, .numSwitches = NUM_NAMESPACE_SWITCH, .switches = initialSwitches };

static struct Namespace *namespaces = &initialNamespace;

static unsigned namespacesSize = 1;

#if defined(HAVE_LIBPTHREAD)
#include <pthread.h>

static pthread_once_t namespaceOnce = PTHREAD_ONCE_INIT;
static pthread_mutex_t namespaceMutex;

static void
namespaceInitialize(void)
{
  pthread_mutexattr_t ma;
  pthread_mutexattr_init(&ma);
  pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutex_init(&namespaceMutex, &ma);
  pthread_mutexattr_destroy(&ma);
}

#define NAMESPACE_LOCK() pthread_mutex_lock(&namespaceMutex)
#define NAMESPACE_UNLOCK() pthread_mutex_unlock(&namespaceMutex)
#define NAMESPACE_INIT() pthread_once(&namespaceOnce, namespaceInitialize)

#else

#define NAMESPACE_INIT() \
  do {                   \
  } while (0)
#define NAMESPACE_LOCK()
#define NAMESPACE_UNLOCK()

#endif

enum
{
  intbits = sizeof(int) * CHAR_BIT,
  nspbits = 4,
  idxbits = intbits - nspbits,
  nspmask = (int) ((((unsigned) 1 << nspbits) - 1) << idxbits),
  idxmask = (1 << idxbits) - 1,
};

enum
{
  NUM_NAMESPACES = 1 << nspbits,
  NUM_IDX = 1 << idxbits,
};

int
namespaceIdxEncode(namespaceTuple_t tin)
{
  xassert(tin.nsp < NUM_NAMESPACES && tin.idx < NUM_IDX);
  return (tin.nsp << idxbits) + tin.idx;
}

int
namespaceIdxEncode2(int nsp, int idx)
{
  xassert(nsp < NUM_NAMESPACES && idx < NUM_IDX);
  return (nsp << idxbits) + idx;
}

namespaceTuple_t
namespaceResHDecode(int resH)
{
  namespaceTuple_t tin;

  tin.idx = resH & idxmask;
  tin.nsp = (int) (((unsigned) (resH & nspmask)) >> idxbits);

  return tin;
}

int
namespaceNew(void)
{
  int newNamespaceID = -1;
  NAMESPACE_INIT();
  NAMESPACE_LOCK();
  if (namespacesSize > nNamespaces)
  {
    /* namespace is already available and only needs reinitialization */
    for (unsigned i = 0; i < namespacesSize; ++i)
      if (namespaces[i].resStage == NAMESPACE_STATUS_UNUSED)
      {
        newNamespaceID = (int) i;
        break;
      }
  }
  else if (namespacesSize == 1)
  {
    /* make room for additional namespace */
    struct Namespace *newNameSpaces = (struct Namespace *) Malloc(((size_t) namespacesSize + 1) * sizeof(namespaces[0]));
    memcpy(newNameSpaces, namespaces, sizeof(namespaces[0]));
    namespaces = newNameSpaces;
    ++namespacesSize;
    newNamespaceID = 1;
  }
  else if (namespacesSize < NUM_NAMESPACES)
  {
    /* make room for additional namespace */
    newNamespaceID = (int) namespacesSize;
    namespaces = (struct Namespace *) Realloc(namespaces, ((size_t) namespacesSize + 1) * sizeof(namespaces[0]));
    ++namespacesSize;
  }
  else /* implicit: namespacesSize >= NUM_NAMESPACES */
  {
    NAMESPACE_UNLOCK();
    return -1;
  }
  xassert(newNamespaceID >= 0 && newNamespaceID < NUM_NAMESPACES);
  ++nNamespaces;
  namespaces[newNamespaceID].resStage = NAMESPACE_STATUS_INUSE;
  namespaces[newNamespaceID].numSwitches = NUM_NAMESPACE_SWITCH;
  enum
  {
    initialNSSWSize = sizeof(union namespaceSwitchValue) * NUM_NAMESPACE_SWITCH
  };
  namespaces[newNamespaceID].switches = (union namespaceSwitchValue *) Malloc(initialNSSWSize);
#if defined(SX) || defined(__cplusplus)
  memcpy(namespaces[newNamespaceID].switches, defaultSwitches_, initialNSSWSize);
#else
  memcpy(namespaces[newNamespaceID].switches, (union namespaceSwitchValue[NUM_NAMESPACE_SWITCH]) defaultSwitches, initialNSSWSize);
#endif
  reshListCreate(newNamespaceID);
  NAMESPACE_UNLOCK();
  return newNamespaceID;
}

void
namespaceDelete(int namespaceID)
{
  NAMESPACE_INIT();
  NAMESPACE_LOCK();
  xassert(namespaceID >= 0 && (unsigned) namespaceID < namespacesSize && nNamespaces);
  reshListDestruct(namespaceID);
  if (namespaces[namespaceID].switches != initialSwitches) Free(namespaces[namespaceID].switches);
  namespaces[namespaceID].resStage = NAMESPACE_STATUS_UNUSED;
  --nNamespaces;
  NAMESPACE_UNLOCK();
}

int
namespaceGetNumber(void)
{
  return (int) nNamespaces;
}

void
namespaceSetActive(int nId)
{
  xassert((unsigned) nId < namespacesSize && namespaces[nId].resStage != NAMESPACE_STATUS_UNUSED);
  activeNamespace = nId;
}

int
namespaceGetActive(void)
{
  return activeNamespace;
}

int
namespaceAdaptKey(int originResH, int originNamespace)
{
  if (originResH == CDI_UNDEFID) return CDI_UNDEFID;

  namespaceTuple_t tin = { .idx = originResH & idxmask, .nsp = (int) (((unsigned) (originResH & nspmask)) >> idxbits) };
  xassert(tin.nsp == originNamespace);

  int nsp = namespaceGetActive();

  return namespaceIdxEncode2(nsp, tin.idx);
}

int
namespaceAdaptKey2(int originResH)
{

  if (originResH == CDI_UNDEFID) return CDI_UNDEFID;

  namespaceTuple_t tin = { .idx = originResH & idxmask, .nsp = (int) (((unsigned) (originResH & nspmask)) >> idxbits) };
  int nsp = namespaceGetActive();
  return namespaceIdxEncode2(nsp, tin.idx);
}

void
namespaceSwitchSet(int sw, union namespaceSwitchValue value)
{
  xassert(sw > NSSWITCH_NO_SUCH_SWITCH);
  int nsp = namespaceGetActive();
  NAMESPACE_LOCK();
  if (namespaces[nsp].numSwitches <= (unsigned) sw)
  {
    if (namespaces[nsp].switches != initialSwitches)
      namespaces[nsp].switches
          = (union namespaceSwitchValue *) Realloc(namespaces[nsp].switches, ((unsigned) sw + 1) * sizeof value);
    else
    {
      void *temp = Malloc(((unsigned) sw + 1) * sizeof value);
      memcpy(temp, (void *) namespaces[nsp].switches, namespaces[nsp].numSwitches * sizeof value);
      namespaces[nsp].switches = (union namespaceSwitchValue *) temp;
    }
    namespaces[nsp].numSwitches = (unsigned) sw + 1;
  }
  namespaces[nsp].switches[sw] = value;
  NAMESPACE_UNLOCK();
}

union namespaceSwitchValue
namespaceSwitchGet(int sw)
{
  int nsp = namespaceGetActive();
  xassert(sw > NSSWITCH_NO_SUCH_SWITCH && (unsigned) sw < namespaces[nsp].numSwitches);
  NAMESPACE_LOCK();
  union namespaceSwitchValue sw_val = namespaces[nsp].switches[sw];
  NAMESPACE_UNLOCK();
  return sw_val;
}

int
cdiNamespaceSwitchNewKey(void)
{
  static unsigned reservedKeys = 0;
#if defined(HAVE_LIBPTHREAD)
  static pthread_mutex_t keyMutex = PTHREAD_MUTEX_INITIALIZER;
  pthread_mutex_lock(&keyMutex);
#endif
  if (reservedKeys >= INT_MAX - NUM_NAMESPACE_SWITCH - 1) Error("pool of available namespace switch keys exhausted!");
  int newKey = (int) (reservedKeys++) + NUM_NAMESPACE_SWITCH;
#if defined(HAVE_LIBPTHREAD)
  pthread_mutex_unlock(&keyMutex);
#endif
  return newKey;
}

void
cdiReset(void)
{
  NAMESPACE_INIT();
  NAMESPACE_LOCK();
  for (unsigned namespaceID = 0; namespaceID < namespacesSize; ++namespaceID)
    if (namespaces[namespaceID].resStage != NAMESPACE_STATUS_UNUSED) namespaceDelete((int) namespaceID);
  if (namespaces != &initialNamespace)
  {
    Free(namespaces);
    namespaces = &initialNamespace;
    namespaces[0].resStage = NAMESPACE_STATUS_UNUSED;
  }
  namespacesSize = 1;
  nNamespaces = 0;
  NAMESPACE_UNLOCK();
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */



void
cdiRefObject_construct(CdiReferencedObject *me)
{
  me->destructor = cdiRefObject_destruct;
  me->refCount = 1;
}

void
cdiRefObject_retain(CdiReferencedObject *me)
{
  size_t oldCount = me->refCount++;
  xassert(oldCount && "A reference counted object was used after it was destructed.");
}

void
cdiRefObject_release(CdiReferencedObject *me)
{
  size_t oldCount = me->refCount--;
  xassert(oldCount && "A reference counted object was released too often.");
  if (oldCount == 1)
  {
    me->destructor(me);
    Free(me);
  }
}

void
cdiRefObject_destruct(CdiReferencedObject *me)
{
  (void) me;
  /* Empty for now, but that's no reason not to call it! */
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 /* PTHREAD_MUTEX_RECURSIVE */
#endif

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

#if defined(HAVE_EXECINFO_H)
#include <execinfo.h>
#endif

static void
show_stackframe(void)
{
#if defined HAVE_EXECINFO_H && defined backtrace_size_t && defined HAVE_BACKTRACE
  void *trace[16];
  backtrace_size_t trace_size = backtrace(trace, 16);
  char **messages = backtrace_symbols(trace, trace_size);

  fprintf(stderr, "[bt] Execution path:\n");
  if (messages)
  {
    for (backtrace_size_t i = 0; i < trace_size; ++i) fprintf(stderr, "[bt] %s\n", messages[i]);
    free(messages);
  }
#endif
}


enum
{
  MIN_LIST_SIZE = 128
};

static void listInitialize(void);

typedef struct listElem
{
  union
  {
    /* free-list management data */
    struct
    {
      int next, prev;
    } free;
    /* holding an actual value */
    struct
    {
      const resOps *ops;
      void *val;  // ptr
    } v;
  } res;
  int status;
} listElem_t;

struct resHList_t
{
  int size, freeHead, hasDefaultRes;
  listElem_t *resources;
};

static struct resHList_t *resHList;

static int resHListSize = 0;

#if defined(HAVE_LIBPTHREAD)
#include <pthread.h>

static pthread_once_t listInitThread = PTHREAD_ONCE_INIT;
static pthread_mutex_t listMutex;

#define LIST_LOCK() pthread_mutex_lock(&listMutex)
#define LIST_UNLOCK() pthread_mutex_unlock(&listMutex)
#define LIST_INIT(init0)                                                     \
  do {                                                                       \
    pthread_once(&listInitThread, listInitialize);                           \
    pthread_mutex_lock(&listMutex);                                          \
    if ((init0) && (!resHList || !resHList[0].resources)) reshListCreate(0); \
    pthread_mutex_unlock(&listMutex);                                        \
  } while (0)

#else

static int listInit = 0;

#define LIST_LOCK()
#define LIST_UNLOCK()
#define LIST_INIT(init0)                                                       \
  do {                                                                         \
    if (!listInit)                                                             \
    {                                                                          \
      listInitialize();                                                        \
      if ((init0) && (!resHList || !resHList[0].resources)) reshListCreate(0); \
      listInit = 1;                                                            \
    }                                                                          \
  } while (0)

#endif

#ifdef CDI_CHECK_RESOURCE_LISTS
static void
checkListLinear(const char *caller, int nsp)
{
  const listElem_t *restrict r = resHList[nsp].resources;
  for (int i = 0; i < resHList[nsp].size; i++)
  {
    if (r[i].status != RESH_UNUSED) continue;

    int prev = r[i].res.free.prev;
    int next = r[i].res.free.next;

    if (prev < -1 || prev >= resHList[nsp].size) xabortC(caller, "error: found invalid back-link in free-list!");
    if (prev != -1)
    {
      if (r[prev].res.free.next != i) xabortC(caller, "error: found incomplete back link in free-list!");
      if (r[prev].status & RESH_IN_USE_BIT) xabortC(caller, "error: found in-use back link element in free-list!");
    }

    if (next < -1 || next >= resHList[nsp].size) xabortC(caller, "error: found invalid forward-link in free-list!");
    if (next != -1)
    {
      if (r[next].res.free.prev != i) xabortC(caller, "error: found incomplete forward link in free-list!");
      if (r[next].status & RESH_IN_USE_BIT) xabortC(caller, "error: found in-use next element in free-list!");
    }
  }
}

static void
checkListFwdBwd(const char *caller, int nsp)
{
  const listElem_t *restrict r = resHList[nsp].resources;

  int next = resHList[nsp].freeHead, loopLimit = resHList[nsp].size + 1;
  int i = 0, cur = -1;
  while (next != -1)
  {
    if (next < 0 || next >= resHList[nsp].size) xabortC(caller, "error: found invalid index in free-list!");
    if (r[next].status & RESH_IN_USE_BIT) xabortC(caller, "error: found in-use next element in free-list!");

    cur = next;
    next = r[next].res.free.next;

    if (++i > loopLimit) xabortC(caller, "error: found loop in free-list!");
  }

  i = 0;
  int prev = cur;
  while (prev != -1)
  {
    if (prev < 0 || prev >= resHList[nsp].size) xabortC(caller, "error: found invalid index in free-list!");
    if (r[prev].status & RESH_IN_USE_BIT) xabortC(caller, "error: found in-use prev element in free-list!");

    cur = prev;
    prev = r[prev].res.free.prev;

    if (prev == -1) break;

    if (++i > loopLimit) xabortC(caller, "error: found loop in free-list!");
  }
}

static void
checkList(const char *caller, int nsp)
{
  checkListLinear(caller, nsp);
  checkListFwdBwd(caller, nsp);
}
#else
#define checkList(caller, nsp)
#endif

/**************************************************************/

static void
listInitResources(int nsp)
{
  xassert(nsp < resHListSize && nsp >= 0);
  int size = resHList[nsp].size = MIN_LIST_SIZE;
  xassert(resHList[nsp].resources == NULL);
  resHList[nsp].resources = (listElem_t *) calloc(MIN_LIST_SIZE, sizeof(listElem_t));
  listElem_t *p = resHList[nsp].resources;

  for (int i = 0; i < size; i++)
  {
    p[i].res.free.next = i + 1;
    p[i].res.free.prev = i - 1;
    p[i].status = RESH_UNUSED;
  }
  p[size - 1].res.free.next = -1;
  resHList[nsp].freeHead = 0;

  checkList(__func__, nsp);

  int oldNsp = namespaceGetActive();
  namespaceSetActive(nsp);
  instituteDefaultEntries();
  modelDefaultEntries();
  namespaceSetActive(oldNsp);
}

static inline void
reshListClearEntry(int i)
{
  resHList[i].size = 0;
  resHList[i].resources = NULL;
  resHList[i].freeHead = -1;
}

void
reshListCreate(int namespaceID)
{
  LIST_INIT(namespaceID != 0);
  LIST_LOCK();
  if (resHListSize <= namespaceID)
  {
    resHList = (struct resHList_t *) realloc(resHList, (size_t) (namespaceID + 1) * sizeof(resHList[0]));
    for (int i = resHListSize; i <= namespaceID; ++i) reshListClearEntry(i);
    resHListSize = namespaceID + 1;
  }
  listInitResources(namespaceID);
  LIST_UNLOCK();
}

/**************************************************************/
static void reshRemove_(int nsp, int idx, const char *caller);

void
reshListDestruct(int namespaceID)
{
  LIST_LOCK();
  xassert(resHList && namespaceID >= 0 && namespaceID < resHListSize);
  int callerNamespaceID = namespaceGetActive();
  namespaceSetActive(namespaceID);
  if (resHList[namespaceID].resources)
  {
    for (int j = 0; j < resHList[namespaceID].size; j++)
    {
      listElem_t *listElem = resHList[namespaceID].resources + j;
      if (listElem->status & RESH_IN_USE_BIT)
      {
        listElem->res.v.ops->valDestroy(listElem->res.v.val);
        reshRemove_(namespaceID, j, __func__);
      }
    }
    free(resHList[namespaceID].resources);
    resHList[namespaceID].resources = NULL;
    reshListClearEntry(namespaceID);
  }
  if (resHList[callerNamespaceID].resources) namespaceSetActive(callerNamespaceID);
  LIST_UNLOCK();
}

static void
listDestroy(void)
{
  LIST_LOCK();
  for (int i = resHListSize; i > 0; --i)
    if (resHList[i - 1].resources) namespaceDelete(i - 1);
  resHListSize = 0;
  free(resHList);
  resHList = NULL;
  cdiReset();
  LIST_UNLOCK();
}

/**************************************************************/

static void
listInitialize(void)
{
#if defined(HAVE_LIBPTHREAD)
  pthread_mutexattr_t ma;
  pthread_mutexattr_init(&ma);
  pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_RECURSIVE);
  /* initialize global API mutex lock */
  pthread_mutex_init(&listMutex, &ma);
  pthread_mutexattr_destroy(&ma);
#endif
  /* file is special and has its own table, which needs to be
   * created, before we register the listDestroy exit handler */
  {
    int null_id;
    null_id = fileOpen_serial("/dev/null", "r");
    if (null_id != -1) fileClose_serial(null_id);
  }
  atexit(listDestroy);
}

/**************************************************************/

static void
listSizeExtend(void)
{
  int nsp = namespaceGetActive();
  checkList(__func__, nsp);
  int oldSize = resHList[nsp].size;
  size_t newListSize = (size_t) oldSize + MIN_LIST_SIZE;

  resHList[nsp].resources = (listElem_t *) realloc(resHList[nsp].resources, newListSize * sizeof(listElem_t));

  listElem_t *r = resHList[nsp].resources;
  for (size_t i = (size_t) oldSize; i < newListSize; ++i)
  {
    r[i].res.free.next = (int) i + 1;
    r[i].res.free.prev = (int) i - 1;
    r[i].status = RESH_UNUSED;
  }

  if (resHList[nsp].freeHead != -1) r[resHList[nsp].freeHead].res.free.prev = (int) newListSize - 1;
  r[newListSize - 1].res.free.next = resHList[nsp].freeHead;
  r[oldSize].res.free.prev = -1;
  resHList[nsp].freeHead = oldSize;
  resHList[nsp].size = (int) newListSize;

  checkList(__func__, nsp);
}

/**************************************************************/

static void
reshPut_(int nsp, int entry, void *p, const resOps *ops)
{
  checkList(__func__, nsp);

  listElem_t *newListElem = resHList[nsp].resources + entry;
  int next = newListElem->res.free.next, prev = newListElem->res.free.prev;
  if (next != -1) resHList[nsp].resources[next].res.free.prev = prev;
  if (prev != -1)
    resHList[nsp].resources[prev].res.free.next = next;
  else
    resHList[nsp].freeHead = next;
  newListElem->res.v.val = p;
  newListElem->res.v.ops = ops;
  newListElem->status = RESH_DESYNC_IN_USE;

  checkList(__func__, nsp);
}

int
reshPut(void *p, const resOps *ops)
{
  xassert(p && ops);

  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();

  if (resHList[nsp].freeHead == -1) listSizeExtend();
  int entry = resHList[nsp].freeHead;
  cdiResH resH = namespaceIdxEncode2(nsp, entry);
  reshPut_(nsp, entry, p, ops);

  LIST_UNLOCK();

  return resH;
}

/**************************************************************/

static void
reshRemove_(int nsp, int idx, const char *caller)
{
  checkList(__func__, nsp);

  int curFree = resHList[nsp].freeHead;
  listElem_t *r = resHList[nsp].resources;
  if (!(r[idx].status & RESH_IN_USE_BIT)) xabortC(caller, "Attempting to remove an item that is already removed.");
  r[idx].res.free.next = curFree;
  r[idx].res.free.prev = -1;
  if (curFree != -1) r[curFree].res.free.prev = idx;
  r[idx].status = RESH_DESYNC_DELETED;
  resHList[nsp].freeHead = idx;

  checkList(__func__, nsp);
}

void
reshDestroy(cdiResH resH)
{
  int nsp;
  namespaceTuple_t nspT;

  LIST_LOCK();

  nsp = namespaceGetActive();

  nspT = namespaceResHDecode(resH);

  listElem_t *r = resHList[nsp].resources + nspT.idx;

  xassert(nspT.nsp == nsp && nspT.idx >= 0 && nspT.idx < resHList[nsp].size && r->res.v.ops);

  if (r->status & RESH_IN_USE_BIT)
  {
    r->res.v.ops->valDestroy(r->res.v.val);
    reshRemove_(nsp, nspT.idx, __func__);
  }

  LIST_UNLOCK();
}

void
reshRemove(cdiResH resH, const resOps *ops)
{
  int nsp;
  namespaceTuple_t nspT;

  LIST_LOCK();

  nsp = namespaceGetActive();

  nspT = namespaceResHDecode(resH);

  xassert(nspT.nsp == nsp && nspT.idx >= 0 && nspT.idx < resHList[nsp].size
          && (resHList[nsp].resources[nspT.idx].status & RESH_IN_USE_BIT) && resHList[nsp].resources[nspT.idx].res.v.ops
          && resHList[nsp].resources[nspT.idx].res.v.ops == ops);

  reshRemove_(nsp, nspT.idx, __func__);

  LIST_UNLOCK();
}

/**************************************************************/

void
reshReplace(cdiResH resH, void *p, const resOps *ops)
{
  xassert(p && ops);
  LIST_INIT(1);
  LIST_LOCK();
  int nsp = namespaceGetActive();

  checkList(__func__, nsp);

  namespaceTuple_t nspT = namespaceResHDecode(resH);
  while (resHList[nsp].size <= nspT.idx) listSizeExtend();
  listElem_t *q = resHList[nsp].resources + nspT.idx;
  if (q->status & RESH_IN_USE_BIT)
  {
    q->res.v.ops->valDestroy(q->res.v.val);
    reshRemove_(nsp, nspT.idx, __func__);
  }
  reshPut_(nsp, nspT.idx, p, ops);
  LIST_UNLOCK();

  checkList(__func__, nsp);
}

static listElem_t *
reshGetElem(const char *caller, const char *expressionString, cdiResH resH, const resOps *ops)
{
  listElem_t *listElem;
  xassert(ops);

  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();

  namespaceTuple_t nspT = namespaceResHDecode(resH);
  assert(nspT.idx >= 0);

  if (nspT.nsp == nsp && nspT.idx < resHList[nsp].size)
  {
    listElem = resHList[nsp].resources + nspT.idx;
    LIST_UNLOCK();
  }
  else
  {
    LIST_UNLOCK();
    show_stackframe();

    if (resH == CDI_UNDEFID)
    {
      xabortC(caller,
              "Error while trying to resolve the ID \"%s\" in `%s()`: the value is CDI_UNDEFID (= %d).\n\tThis is most likely "
              "the result of a failed earlier call. Please check the IDs returned by CDI.",
              expressionString, caller, resH);
    }
    else
    {
      xabortC(caller,
              "Error while trying to resolve the ID \"%s\" in `%s()`: the value is garbage (= %d, which resolves to namespace "
              "= %d, index = %d).\n\tThis is either the result of using an uninitialized variable,\n\tof using a value as an "
              "ID that is not an ID,\n\tor of using an ID after it has been invalidated.",
              expressionString, caller, resH, nspT.nsp, nspT.idx);
    }
  }

  if (!(listElem && listElem->res.v.ops == ops))
  {
    show_stackframe();

    xabortC(caller, "Error while trying to resolve the ID \"%s\" in `%s()`: list element not found. The failed ID is %d",
            expressionString, caller, (int) resH);
  }

  return listElem;
}

void *
reshGetValue(const char *caller, const char *expressionString, cdiResH resH, const resOps *ops)
{
  return reshGetElem(caller, expressionString, resH, ops)->res.v.val;
}

/**************************************************************/

void
reshGetResHListOfType(unsigned numIDs, int resHs[], const resOps *ops)
{
  xassert(resHs && ops);

  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();
  unsigned j = 0;
  for (int i = 0; i < resHList[nsp].size && j < numIDs; i++)
    if ((resHList[nsp].resources[i].status & RESH_IN_USE_BIT) && resHList[nsp].resources[i].res.v.ops == ops)
      resHs[j++] = namespaceIdxEncode2(nsp, i);

  LIST_UNLOCK();
}

enum cdiApplyRet
cdiResHApply(enum cdiApplyRet (*func)(int id, void *res, const resOps *p, void *data), void *data)
{
  xassert(func);

  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();
  enum cdiApplyRet ret = CDI_APPLY_GO_ON;
  for (int i = 0; i < resHList[nsp].size && ret > 0; ++i)
    if (resHList[nsp].resources[i].status & RESH_IN_USE_BIT)
      ret = func(namespaceIdxEncode2(nsp, i), resHList[nsp].resources[i].res.v.val, resHList[nsp].resources[i].res.v.ops, data);
  LIST_UNLOCK();
  return ret;
}

enum cdiApplyRet
cdiResHFilterApply(const resOps *p, enum cdiApplyRet (*func)(int id, void *res, void *data), void *data)
{
  xassert(p && func);

  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();
  enum cdiApplyRet ret = CDI_APPLY_GO_ON;
  listElem_t *r = resHList[nsp].resources;
  for (int i = 0; i < resHList[nsp].size && ret > 0; ++i)
    if ((r[i].status & RESH_IN_USE_BIT) && r[i].res.v.ops == p) ret = func(namespaceIdxEncode2(nsp, i), r[i].res.v.val, data);
  LIST_UNLOCK();
  return ret;
}

/**************************************************************/

unsigned
reshCountType(const resOps *ops)
{
  unsigned countType = 0;

  xassert(ops);

  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();

  listElem_t *r = resHList[nsp].resources;
  size_t len = (size_t) resHList[nsp].size;
  for (size_t i = 0; i < len; i++) countType += ((r[i].status & RESH_IN_USE_BIT) && r[i].res.v.ops == ops);

  LIST_UNLOCK();

  return countType;
}

/**************************************************************/

int
reshResourceGetPackSize_intern(int resH, const resOps *ops, void *context, const char *caller, const char *expressionString)
{
  listElem_t *curr = reshGetElem(caller, expressionString, resH, ops);
  return curr->res.v.ops->valGetPackSize(curr->res.v.val, context);
}

void
reshPackResource_intern(int resH, const resOps *ops, void *buf, int buf_size, int *position, void *context, const char *caller,
                        const char *expressionString)
{
  listElem_t *curr = reshGetElem(caller, expressionString, resH, ops);
  curr->res.v.ops->valPack(curr->res.v.val, buf, buf_size, position, context);
}

enum
{
  resHPackHeaderNInt = 2,
  resHDeleteNInt = 2,
};

static int
getPackBufferSize(void *context)
{
  int intpacksize, packBufferSize = 0;

  int nsp = namespaceGetActive();

  /* pack start marker, namespace and sererator marker */
  packBufferSize += resHPackHeaderNInt * (intpacksize = serializeGetSize(1, CDI_DATATYPE_INT, context));

  /* pack resources, type marker and seperator marker */
  listElem_t *r = resHList[nsp].resources;
  for (int i = 0; i < resHList[nsp].size; i++)
    if (r[i].status & RESH_SYNC_BIT)
    {
      if (r[i].status == RESH_DESYNC_DELETED) { packBufferSize += resHDeleteNInt * intpacksize; }
      else if (r[i].status == RESH_DESYNC_IN_USE)
      {
        xassert(r[i].res.v.ops);
        /* packed resource plus 1 int for type */
        packBufferSize += r[i].res.v.ops->valGetPackSize(r[i].res.v.val, context) + intpacksize;
      }
    }
  /* end marker */
  packBufferSize += intpacksize;

  return packBufferSize;
}

/**************************************************************/

void
reshPackBufferDestroy(char **buffer)
{
  if (buffer) free(*buffer);
}

/**************************************************************/

int
reshGetTxCode(cdiResH resH)
{
  int type = 0;

  LIST_LOCK();

  int nsp = namespaceGetActive();

  namespaceTuple_t nspT = namespaceResHDecode(resH);
  assert(nspT.idx >= 0);

  if (nspT.nsp == nsp && nspT.idx < resHList[nsp].size)
  {
    listElem_t *listElem = resHList[nsp].resources + nspT.idx;
    xassert(listElem->res.v.ops);
    type = listElem->res.v.ops->valTxCode(listElem->res.v.val);
  }

  LIST_UNLOCK();

  return type;
}

/**************************************************************/

int
reshPackBufferCreate(char **packBuffer, int *packBufferSize, void *context)
{
  int packBufferPos = 0;
  int end = END;

  xassert(packBuffer);

  LIST_LOCK();

  int nsp = namespaceGetActive();

  int pBSize = *packBufferSize = getPackBufferSize(context);
  char *pB = *packBuffer = (char *) malloc((size_t) pBSize);

  {
    int header[resHPackHeaderNInt] = { START, nsp };
    serializePack(header, resHPackHeaderNInt, CDI_DATATYPE_INT, pB, pBSize, &packBufferPos, context);
  }

  listElem_t *r = resHList[nsp].resources;
  for (int i = 0; i < resHList[nsp].size; i++)
    if (r[i].status & RESH_SYNC_BIT)
    {
      if (r[i].status == RESH_DESYNC_DELETED)
      {
        int temp[resHDeleteNInt] = { RESH_DELETE, namespaceIdxEncode2(nsp, i) };
        serializePack(temp, resHDeleteNInt, CDI_DATATYPE_INT, pB, pBSize, &packBufferPos, context);
      }
      else
      {
        listElem_t *curr = r + i;
        xassert(curr->res.v.ops);
        int type = curr->res.v.ops->valTxCode(curr->res.v.val);
        if (!type) continue;
        serializePack(&type, 1, CDI_DATATYPE_INT, pB, pBSize, &packBufferPos, context);
        curr->res.v.ops->valPack(curr->res.v.val, pB, pBSize, &packBufferPos, context);
      }
      r[i].status &= ~RESH_SYNC_BIT;
    }

  LIST_UNLOCK();

  serializePack(&end, 1, CDI_DATATYPE_INT, pB, pBSize, &packBufferPos, context);

  return packBufferPos;
}

/**************************************************************/

/* for thread safety this feature would have to be integrated in reshPut */

void
reshSetStatus(cdiResH resH, const resOps *ops, int status)
{
  int nsp;
  namespaceTuple_t nspT;
  listElem_t *listElem;

  LIST_INIT(1);

  LIST_LOCK();

  nsp = namespaceGetActive();

  nspT = namespaceResHDecode(resH);

  xassert(nspT.nsp == nsp && nspT.idx >= 0 && nspT.idx < resHList[nsp].size);

  xassert(resHList[nsp].resources);
  listElem = resHList[nsp].resources + nspT.idx;

  xassert((!ops || (listElem->res.v.ops == ops)) && (listElem->status & RESH_IN_USE_BIT) == (status & RESH_IN_USE_BIT));

  listElem->status = status;

  LIST_UNLOCK();
}

/**************************************************************/

int
reshGetStatus(cdiResH resH, const resOps *ops)
{
  LIST_INIT(1);

  LIST_LOCK();

  int nsp = namespaceGetActive();

  namespaceTuple_t nspT = namespaceResHDecode(resH);

  xassert(nspT.nsp == nsp && nspT.idx >= 0);

  int status = RESH_UNUSED;
  if (nspT.idx < resHList[nsp].size)
  {
    listElem_t *listElem = resHList[nsp].resources + nspT.idx;
    const resOps *elemOps = listElem->res.v.ops;
    xassert(listElem && (!(listElem->status & RESH_IN_USE_BIT) || elemOps == ops || !ops));
    status = listElem->status;
  }

  LIST_UNLOCK();

  return status;
}

/**************************************************************/

void
reshLock(void)
{
  LIST_LOCK();
}

/**************************************************************/

void
reshUnlock(void)
{
  LIST_UNLOCK();
}

/**************************************************************/

int
reshListCompare(int nsp0, int nsp1)
{
  LIST_INIT(1);
  LIST_LOCK();

  xassert(resHListSize > nsp0 && resHListSize > nsp1 && nsp0 >= 0 && nsp1 >= 0);

  int valCompare = 0;
  int i, listSizeMin = (resHList[nsp0].size <= resHList[nsp1].size) ? resHList[nsp0].size : resHList[nsp1].size;
  listElem_t *resources0 = resHList[nsp0].resources, *resources1 = resHList[nsp1].resources;
  for (i = 0; i < listSizeMin; i++)
  {
    int occupied0 = (resources0[i].status & RESH_IN_USE_BIT) != 0, occupied1 = (resources1[i].status & RESH_IN_USE_BIT) != 0;
    /* occupation mismatch ? */
    int diff = occupied0 ^ occupied1;
    valCompare |= (diff << cdiResHListOccupationMismatch);
    if (!diff && occupied0)
    {
      /* both occupied, do resource types match? */
      diff = (resources0[i].res.v.ops != resources1[i].res.v.ops || resources0[i].res.v.ops == NULL);
      valCompare |= (diff << cdiResHListResourceTypeMismatch);
      if (!diff)
      {
        /* types match, does content match also? */
        diff = resources0[i].res.v.ops->valCompare(resources0[i].res.v.val, resources1[i].res.v.val);
        valCompare |= (diff << cdiResHListResourceContentMismatch);
      }
    }
  }
  /* find resources in nsp 0 beyond end of nsp 1 */
  for (int j = listSizeMin; j < resHList[nsp0].size; ++j)
    valCompare |= (((resources0[j].status & RESH_IN_USE_BIT) != 0) << cdiResHListOccupationMismatch);
  /* find resources in nsp 1 beyond end of nsp 0 */
  for (; i < resHList[nsp1].size; ++i)
    valCompare |= (((resources1[i].status & RESH_IN_USE_BIT) != 0) << cdiResHListOccupationMismatch);

  LIST_UNLOCK();

  return valCompare;
}

/**************************************************************/

void
reshListPrint(FILE *fp)
{
  int i, j, temp;
  listElem_t *curr;

  LIST_INIT(1);

  temp = namespaceGetActive();

  fprintf(fp, "\n\n##########################################\n#\n#  print "
              "global resource list \n#\n");

  for (i = 0; i < namespaceGetNumber(); i++)
  {
    namespaceSetActive(i);

    fprintf(fp, "\n");
    fprintf(fp, "##################################\n");
    fprintf(fp, "#\n");
    fprintf(fp, "# namespace=%d\n", i);
    fprintf(fp, "#\n");
    fprintf(fp, "##################################\n\n");

    fprintf(fp, "resHList[%d].size=%d\n", i, resHList[i].size);

    for (j = 0; j < resHList[i].size; j++)
    {
      curr = resHList[i].resources + j;
      if (!(curr->status & RESH_IN_USE_BIT))
      {
        curr->res.v.ops->valPrint(curr->res.v.val, fp);
        fprintf(fp, "\n");
      }
    }
  }
  fprintf(fp, "#\n#  end global resource list"
              "\n#\n##########################################\n\n");

  namespaceSetActive(temp);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <inttypes.h>
#include <limits.h>
#include <string.h>


int
serializeGetSize(int count, int datatype, void *context)
{
  int (*serialize_get_size_p)(int count, int datatype, void *context)
      = (int (*)(int, int, void *)) namespaceSwitchGet(NSSWITCH_SERIALIZE_GET_SIZE).func;
  return serialize_get_size_p(count, datatype, context);
}

void
serializePack(const void *data, int count, int datatype, void *buf, int buf_size, int *position, void *context)
{
  void (*serialize_pack_p)(const void *data, int count, int datatype, void *buf, int buf_size, int *position, void *context)
      = (void (*)(const void *, int, int, void *, int, int *, void *)) namespaceSwitchGet(NSSWITCH_SERIALIZE_PACK).func;
  serialize_pack_p(data, count, datatype, buf, buf_size, position, context);
}

void
serializeUnpack(const void *buf, int buf_size, int *position, void *data, int count, int datatype, void *context)
{
  void (*serialize_unpack_p)(const void *buf, int buf_size, int *position, void *data, int count, int datatype, void *context)
      = (void (*)(const void *, int, int *, void *, int, int, void *)) namespaceSwitchGet(NSSWITCH_SERIALIZE_UNPACK).func;
  serialize_unpack_p(buf, buf_size, position, data, count, datatype, context);
}

int
serializeGetSizeInCore(int count, int datatype, void *context)
{
  int elemSize;
  (void) context;
  switch (datatype)
  {
    case CDI_DATATYPE_INT8: elemSize = sizeof(int8_t); break;
    case CDI_DATATYPE_INT16: elemSize = sizeof(int16_t); break;
    case CDI_DATATYPE_UINT32: elemSize = sizeof(uint32_t); break;
    case CDI_DATATYPE_INT: elemSize = sizeof(int); break;
    case CDI_DATATYPE_UINT: elemSize = sizeof(unsigned); break;
    case CDI_DATATYPE_FLT:
    case CDI_DATATYPE_FLT64: elemSize = sizeof(double); break;
    case CDI_DATATYPE_TXT:
    case CDI_DATATYPE_UCHAR: elemSize = 1; break;
    case CDI_DATATYPE_LONG: elemSize = sizeof(long); break;
    default: xabort("Unexpected datatype");
  }
  return count * elemSize;
}

void
serializePackInCore(const void *data, int count, int datatype, void *buf, int buf_size, int *position, void *context)
{
  int size = serializeGetSize(count, datatype, context);
  int pos = *position;
  xassert(INT_MAX - pos >= size && buf_size - pos >= size);
  memcpy((unsigned char *) buf + pos, data, (size_t) size);
  pos += size;
  *position = pos;
}

void
serializeUnpackInCore(const void *buf, int buf_size, int *position, void *data, int count, int datatype, void *context)
{
  int size = serializeGetSize(count, datatype, context);
  int pos = *position;
  xassert(INT_MAX - pos >= size && buf_size - pos >= size);
  memcpy(data, (unsigned char *) buf + pos, (size_t) size);
  pos += size;
  *position = pos;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>


enum
{
  SRV_HEADER_LEN = 8,
};

union SRV_HEADER
{
  int32_t i32[SRV_HEADER_LEN];
  int64_t i64[SRV_HEADER_LEN];
};

static int initSrvLib = 0;
static int srvDefaultHprec = 0;
static int srvDefaultDprec = 0;

// A version string.
#undef LIBVERSION
#define LIBVERSION 2.0.0
#define XSTRING(x) #x
#define STRING(x) XSTRING(x)
static const char srv_libvers[] = STRING(LIBVERSION);

const char *
srvLibraryVersion(void)
{
  return srv_libvers;
}

static int SRV_Debug = 0;  // If set to 1, debugging

void
srvDebug(int debug)
{
  if (debug) Message("debug level %d", debug);
  SRV_Debug = debug;
}

static void
srvLibInit(void)
{
  const char *envName = "SRV_PRECISION";

  char *envString = getenv(envName);
  if (envString)
  {
    int nrun = (strlen(envString) == 2) ? 1 : 2;
    int pos = 0;
    while (nrun--)
    {
      switch (tolower((int) envString[pos]))
      {
        case 'i':
        {
          switch ((int) envString[pos + 1])
          {
            case '4': srvDefaultHprec = EXSE_PREC_FP32; break;
            case '8': srvDefaultHprec = EXSE_PREC_FP64; break;
            default: Warning("Invalid digit in %s: %s", envName, envString);
          }
          break;
        }
        case 'r':
        {
          switch ((int) envString[pos + 1])
          {
            case '4': srvDefaultDprec = EXSE_PREC_FP32; break;
            case '8': srvDefaultDprec = EXSE_PREC_FP64; break;
            default: Warning("Invalid digit in %s: %s", envName, envString);
          }
          break;
        }
        default:
        {
          Warning("Invalid character in %s: %s", envName, envString);
          break;
        }
      }
      pos += 2;
    }
  }

  initSrvLib = 1;
}

static void
srvInit(srvrec_t *srvp)
{
  srvp->checked = 0;
  srvp->byteswap = 0;
  srvp->hprec = 0;
  srvp->dprec = 0;
  srvp->datasize = 0;
  srvp->buffersize = 0;
  srvp->buffer = NULL;
}

void *
srvNew(void)
{
  if (!initSrvLib) srvLibInit();

  srvrec_t *srvp = (srvrec_t *) Malloc(sizeof(srvrec_t));
  srvInit(srvp);

  return (void *) srvp;
}

void
srvDelete(void *srv)
{
  srvrec_t *srvp = (srvrec_t *) srv;

  if (srvp)
  {
    if (srvp->buffer) Free(srvp->buffer);
    Free(srvp);
  }
}

int
srvCheckFiletype(int fileID, int *swap)
{
  size_t data = 0;
  size_t dimx = 0, dimy = 0;
  size_t fact = 0;
  unsigned char buffer[72], *pbuf;

  if (fileRead(fileID, buffer, 4) != 4) return 0;

  size_t blocklen = (size_t) get_uint32(buffer);
  size_t sblocklen = (size_t) get_swap_uint32(buffer);

  if (SRV_Debug) Message("blocklen = %d sblocklen = %d", blocklen, sblocklen);

  // clang-format off
  if (blocklen == 32)
    {
     *swap = 0;
      fact = blocklen>>3;
      if (fileRead(fileID, buffer, blocklen+8) != blocklen+8) return 0;
      pbuf = buffer+4*fact;      dimx = (size_t) get_uint32(pbuf);
      pbuf = buffer+5*fact;      dimy = (size_t) get_uint32(pbuf);
      pbuf = buffer+blocklen+4;  data = (size_t) get_uint32(pbuf);
    }
  else if (blocklen == 64)
    {
     *swap = 0;
      fact = blocklen>>3;
      if (fileRead(fileID, buffer, blocklen+8) != blocklen+8) return 0;
      pbuf = buffer+4*fact;      dimx = (size_t) get_uint64(pbuf);
      pbuf = buffer+5*fact;      dimy = (size_t) get_uint64(pbuf);
      pbuf = buffer+blocklen+4;  data = (size_t) get_uint32(pbuf);
    }
  else if (sblocklen == 32)
    {
     *swap = 1;
      fact = sblocklen>>3;
      if (fileRead(fileID, buffer, sblocklen+8) != sblocklen+8) return 0;
      pbuf = buffer+4*fact;       dimx = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+5*fact;       dimy = (size_t) get_swap_uint32(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_swap_uint32(pbuf);
    }
  else if (sblocklen == 64)
    {
     *swap = 1;
      fact = sblocklen>>3;
      if (fileRead(fileID, buffer, sblocklen+8) != sblocklen+8) return 0;
      pbuf = buffer+4*fact;       dimx = (size_t) get_swap_uint64(pbuf);
      pbuf = buffer+5*fact;       dimy = (size_t) get_swap_uint64(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_swap_uint32(pbuf);
    }
  // clang-format on

  fileRewind(fileID);

  if (SRV_Debug) Message("swap = %d fact = %d", *swap, fact);
  if (SRV_Debug) Message("dimx = %lu dimy = %lu data = %lu", dimx, dimy, data);

  int found = data && (dimx * dimy * fact == data || dimx * dimy * 8 == data);
  return found;
}

int
srvInqHeader(void *srv, int *header)
{
  srvrec_t *srvp = (srvrec_t *) srv;

  for (int i = 0; i < SRV_HEADER_LEN; i++) header[i] = srvp->header[i];

  if (SRV_Debug) Message("datasize = %lu", srvp->datasize);

  return 0;
}

int
srvDefHeader(void *srv, const int *header)
{
  srvrec_t *srvp = (srvrec_t *) srv;

  for (int i = 0; i < SRV_HEADER_LEN; i++) srvp->header[i] = header[i];

  srvp->datasize = (size_t) header[4] * (size_t) header[5];

  if (SRV_Debug) Message("datasize = %zu", srvp->datasize);

  return 0;
}

static int
srvInqData(srvrec_t *srvp, int prec, void *data)
{
  int ierr = 0;
  int byteswap = srvp->byteswap;
  size_t datasize = srvp->datasize;
  void *buffer = srvp->buffer;
  int dprec = srvp->dprec;

  switch (dprec)
  {
    case EXSE_PREC_FP32:
    {
      if (byteswap) swap4byte(buffer, datasize);

      if (dprec == prec)
        memcpy(data, buffer, datasize * sizeof(float));
      else
        for (size_t i = 0; i < datasize; i++) ((double *) data)[i] = (double) ((float *) buffer)[i];

      break;
    }
    case EXSE_PREC_FP64:
    {
      if (byteswap) swap8byte(buffer, datasize);

      if (dprec == prec)
        memcpy(data, buffer, datasize * sizeof(double));
      else
        for (size_t i = 0; i < datasize; i++) ((float *) data)[i] = (float) ((double *) buffer)[i];

      break;
    }
    default:
    {
      Error("unexpected data precision %d", dprec);
      break;
    }
  }

  return ierr;
}

int
srvInqDataFP32(void *srv, float *data)
{
  return srvInqData((srvrec_t *) srv, EXSE_PREC_FP32, (void *) data);
}

int
srvInqDataFP64(void *srv, double *data)
{
  return srvInqData((srvrec_t *) srv, EXSE_PREC_FP64, (void *) data);
}

static int
srvDefData(void *srv, int prec, const void *data)
{
  srvrec_t *srvp = (srvrec_t *) srv;

  int dprec = srvDefaultDprec ? srvDefaultDprec : srvp->dprec;
  srvp->dprec = dprec ? dprec : prec;

  int hprec = srvDefaultHprec ? srvDefaultHprec : srvp->hprec;
  srvp->hprec = hprec ? hprec : dprec;

  int *header = srvp->header;

  size_t datasize = (size_t) header[4] * (size_t) header[5];
  size_t blocklen = datasize * (size_t) dprec;

  srvp->datasize = datasize;

  if (srvp->buffersize != blocklen)
  {
    srvp->buffersize = blocklen;
    srvp->buffer = Realloc(srvp->buffer, srvp->buffersize);
  }

  switch (dprec)
  {
    case EXSE_PREC_FP32:
    {
      if (dprec == prec)
        memcpy(srvp->buffer, data, datasize * sizeof(float));
      else
        for (size_t i = 0; i < datasize; i++) ((float *) srvp->buffer)[i] = (float) ((double *) data)[i];

      break;
    }
    case EXSE_PREC_FP64:
    {
      if (dprec == prec)
        memcpy(srvp->buffer, data, datasize * sizeof(double));
      else
        for (size_t i = 0; i < datasize; i++) ((double *) srvp->buffer)[i] = (double) ((float *) data)[i];

      break;
    }
    default:
    {
      Error("unexpected data precision %d", dprec);
      break;
    }
  }

  return 0;
}

int
srvDefDataFP32(void *srv, const float *data)
{
  return srvDefData(srv, EXSE_PREC_FP32, (void *) data);
}

int
srvDefDataFP64(void *srv, const double *data)
{
  return srvDefData(srv, EXSE_PREC_FP64, (void *) data);
}

int
srvRead(int fileID, void *srv)
{
  srvrec_t *srvp = (srvrec_t *) srv;
  union SRV_HEADER tempheader;

  if (!srvp->checked)
  {
    int status = srvCheckFiletype(fileID, &srvp->byteswap);
    if (status == 0) Error("Not a SERVICE file!");
    srvp->checked = 1;
  }

  int byteswap = srvp->byteswap;

  // read header record
  size_t blocklen = binReadF77Block(fileID, byteswap);

  if (fileEOF(fileID)) return -1;

  if (SRV_Debug) Message("blocklen = %lu", blocklen);

  size_t hprec = blocklen / SRV_HEADER_LEN;

  srvp->hprec = (int) hprec;

  switch (hprec)
  {
    case EXSE_PREC_FP32:
    {
      binReadInt32(fileID, byteswap, SRV_HEADER_LEN, tempheader.i32);
      for (int i = 0; i < SRV_HEADER_LEN; i++) srvp->header[i] = (int) tempheader.i32[i];
      break;
    }
    case EXSE_PREC_FP64:
    {
      binReadInt64(fileID, byteswap, SRV_HEADER_LEN, tempheader.i64);
      for (int i = 0; i < SRV_HEADER_LEN; i++) srvp->header[i] = (int) tempheader.i64[i];
      break;
    }
    default:
    {
      Error("Unexpected header precision %d", hprec);
      break;
    }
  }

  size_t blocklen2 = binReadF77Block(fileID, byteswap);

  if (blocklen2 != blocklen)
  {
    Warning("Header blocklen differ (blocklen1=%d; blocklen2=%d)!", blocklen, blocklen2);
    if (blocklen2 != 0) return -1;
  }

  srvp->datasize = (size_t) srvp->header[4] * (size_t) srvp->header[5];

  if (SRV_Debug) Message("datasize = %zu", srvp->datasize);

  blocklen = binReadF77Block(fileID, byteswap);

  if (srvp->buffersize < blocklen)
  {
    srvp->buffersize = blocklen;
    srvp->buffer = Realloc(srvp->buffer, srvp->buffersize);
  }

  size_t dprec = blocklen / srvp->datasize;

  srvp->dprec = (int) dprec;

  if (dprec != EXSE_PREC_FP32 && dprec != EXSE_PREC_FP64)
  {
    Warning("Unexpected data precision %d", dprec);
    return -1;
  }

  fileRead(fileID, srvp->buffer, blocklen);

  blocklen2 = binReadF77Block(fileID, byteswap);

  if (blocklen2 != blocklen)
  {
    Warning("Data blocklen differ (blocklen1=%d; blocklen2=%d)!", blocklen, blocklen2);
    if (blocklen2 != 0) return -1;
  }

  return 0;
}

void
srvWrite(int fileID, void *srv)
{
  srvrec_t *srvp = (srvrec_t *) srv;
  union SRV_HEADER tempheader;
  int byteswap = srvp->byteswap;
  int dprec = srvp->dprec;
  int hprec = srvp->hprec;
  int *restrict header = srvp->header;

  // write header record
  size_t blocklen = SRV_HEADER_LEN * (size_t) hprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  switch (hprec)
  {
    case EXSE_PREC_FP32:
    {
      for (int i = 0; i < SRV_HEADER_LEN; i++) tempheader.i32[i] = (int32_t) header[i];
      binWriteInt32(fileID, byteswap, SRV_HEADER_LEN, tempheader.i32);
      break;
    }
    case EXSE_PREC_FP64:
    {
      for (int i = 0; i < SRV_HEADER_LEN; i++) tempheader.i64[i] = (int64_t) header[i];
      binWriteInt64(fileID, byteswap, SRV_HEADER_LEN, tempheader.i64);
      break;
    }
    default:
    {
      Error("unexpected header precision %d", hprec);
      break;
    }
  }

  binWriteF77Block(fileID, byteswap, blocklen);

  srvp->datasize = (size_t) header[4] * (size_t) header[5];
  blocklen = srvp->datasize * (size_t) dprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  switch (dprec)
  {
    case EXSE_PREC_FP32:
    {
      binWriteFlt32(fileID, byteswap, srvp->datasize, (float *) srvp->buffer);
      break;
    }
    case EXSE_PREC_FP64:
    {
      binWriteFlt64(fileID, byteswap, srvp->datasize, (double *) srvp->buffer);
      break;
    }
    default:
    {
      Error("unexpected data precision %d", dprec);
      break;
    }
  }

  binWriteF77Block(fileID, byteswap, blocklen);
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef STREAM_SCAN_H
#define STREAM_SCAN_H


void streamScanResizeRecords1(stream_t *streamptr);
int streamScanInitRecords2(stream_t *streamptr);
int streamScanInitRecords(stream_t *streamptr, int tsID);
void streamScanTimeConstAdjust(stream_t *streamptr, const taxis_t *taxis);
void streamScanTsFixNtsteps(stream_t *streamptr, off_t recpos);

#endif /* STREAM_SCAN_H */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */


void
streamScanResizeRecords1(stream_t *streamptr)
{
  int nrecords = streamptr->tsteps[0].nallrecs;
  if (nrecords < streamptr->tsteps[0].recordSize)
  {
    streamptr->tsteps[0].recordSize = nrecords;
    streamptr->tsteps[0].recinfo = (recinfo_t *) Realloc(streamptr->tsteps[0].recinfo, (size_t) nrecords * sizeof(recinfo_t));
    streamptr->tsteps[0].records = (record_t *) Realloc(streamptr->tsteps[0].records, (size_t) nrecords * sizeof(record_t));
  }

  streamptr->tsteps[0].recIDs = (int *) Malloc((size_t) nrecords * sizeof(int));
  streamptr->tsteps[0].nrecs = nrecords;
  for (int recID = 0; recID < nrecords; ++recID) streamptr->tsteps[0].recIDs[recID] = recID;
}

int
streamScanInitRecords2(stream_t *streamptr)
{
  int nrecords = streamptr->tsteps[1].nallrecs;
  streamptr->tsteps[1].recIDs = (int *) Malloc((size_t) nrecords * sizeof(int));
  streamptr->tsteps[1].nrecs = 0;

  for (int recID = 0; recID < nrecords; ++recID)
  {
    streamptr->tsteps[1].recIDs[recID] = -1;
    streamptr->tsteps[1].records[recID].position = streamptr->tsteps[0].records[recID].position;
    streamptr->tsteps[1].records[recID].size = streamptr->tsteps[0].records[recID].size;
  }

  return nrecords;
}

int
streamScanInitRecords(stream_t *streamptr, int tsID)
{
  int nrecs = streamptr->tsteps[1].nrecs;

  streamptr->tsteps[tsID].nrecs = nrecs;
  streamptr->tsteps[tsID].recIDs = (int *) Malloc((size_t) nrecs * sizeof(int));

  for (int recID = 0; recID < nrecs; ++recID) streamptr->tsteps[tsID].recIDs[recID] = streamptr->tsteps[1].recIDs[recID];

  return nrecs;
}

void
streamScanTimeConstAdjust(stream_t *streamptr, const taxis_t *taxis)
{
  int vlistID = streamptr->vlistID;
  if (streamptr->ntsteps == 1 && cdiDateTime_isNull(taxis->vDateTime))
  {
    streamptr->ntsteps = 0;
    for (int varID = 0; varID < streamptr->nvars; ++varID) vlistDefVarTimetype(vlistID, varID, TIME_CONSTANT);
  }
}

void
streamScanTsFixNtsteps(stream_t *streamptr, off_t recpos)
{
  if (streamptr->ntsteps == -1)
  {
    int tsID = tstepsNewEntry(streamptr);
    if (tsID != streamptr->rtsteps) Error("Internal error. tsID = %d", tsID);

    streamptr->tsteps[tsID - 1].next = true;
    streamptr->tsteps[tsID].position = recpos;
  }
}
#ifndef CDI_ACROSS_H
#define CDI_ACROSS_H

#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_ACROSS


#define ACROSS_DEFAULT_PORT "13859"

typedef struct
{
  char *expid;
  int expver;
} across_info_t;

int across_connect(const char *path, char filemode, stream_t *streamptr);
void across_disconnect(int sock);
int across_write_grib_message(stream_t *streamptr, const void *gribbuffer, size_t nbytes);

#endif

#endif /* CDI_ACROSS_H */
#ifndef STREAM_CGRIBEX_H
#define STREAM_CGRIBEX_H

void *cgribexNew(void);
void cgribexDelete(void *cgribexp);

int cgribexScanTimestep1(stream_t *streamptr);
int cgribexScanTimestep2(stream_t *streamptr);
int cgribexScanTimestep(stream_t *streamptr);

int cgribexDecode(int memtype, void *cgribexp, void *gribbuffer, size_t gribsize, void *data, size_t datasize, int unreduced,
                  size_t *numMissVals, double missval);

size_t cgribexEncode(int memtype, int varID, int levelID, int vlistID, int gridID, int zaxisID, CdiDateTime vDateTime,
                     int tsteptype, int numavg, SizeType datasize, const void *data, SizeType numMissVals, void *gribbuffer,
                     size_t gribbuffersize);

void *cgribex_handle_new_from_meassage(void *gribbuffer, size_t recsize);
void cgribex_handle_delete(void *gh);

void cgribexChangeParameterIdentification(void *gh, int code, int ltype, int lev);

#endif /* STREAM_CGRIBEX_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _STREAM_SRV_H
#define _STREAM_SRV_H

#ifndef _SERVICE_H
#endif

int srvInqContents(stream_t *streamptr);
int srvInqTimestep(stream_t *streamptr, int tsID);

void srvDefField(stream_t *streamptr);
void srvCopyField(stream_t *streamptr2, stream_t *streamptr1);
void srv_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals);
void srv_write_field(stream_t *streamptr, int memtype, const void *data);

void srvReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals);
void srvWriteVarDP(stream_t *streamptr, int varID, const double *data);

void srvReadVarSliceDP(stream_t *streamptr, int varID, int levelID, double *data, size_t *numMissVals);
void srvWriteVarSliceDP(stream_t *streamptr, int varID, int levelID, const double *data);

#endif /* _STREAM_SRV_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _STREAM_EXT_H
#define _STREAM_EXT_H

#ifndef _EXTRA_H
#endif

int extInqContents(stream_t *streamptr);
int extInqTimestep(stream_t *streamptr, int tsID);

void extDefField(stream_t *streamptr);
void extCopyField(stream_t *streamptr2, stream_t *streamptr1);
void ext_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals);
void ext_write_field(stream_t *streamptr, int memtype, const void *data);

void extReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals);
void extWriteVarDP(stream_t *streamptr, int varID, const double *data);

void extReadVarSliceDP(stream_t *streamptr, int varID, int levelID, double *data, size_t *numMissVals);
void extWriteVarSliceDP(stream_t *streamptr, int varID, int levelID, const double *data);

#endif /* _STREAM_EXT_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _STREAM_IEG_H
#define _STREAM_IEG_H

#ifndef _IEG_H
#endif

int iegInqContents(stream_t *streamptr);
int iegInqTimestep(stream_t *streamptr, int tsID);

void iegDefField(stream_t *streamptr);
void iegCopyField(stream_t *streamptr2, stream_t *streamptr1);
void ieg_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals);
void ieg_write_field(stream_t *streamptr, int memtype, const void *data);

void iegReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals);
void iegWriteVarDP(stream_t *streamptr, int varID, const double *data);

void iegReadVarSliceDP(stream_t *streamptr, int varID, int levelID, double *data, size_t *numMissVals);
void iegWriteVarSliceDP(stream_t *streamptr, int varID, int levelID, const double *data);

#endif /* _STREAM_IEG_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600
#endif

#ifdef HAVE_LIBNETCDF
#include <netcdf.h>  // NC_FORMAT_64BIT_DATA
#endif

#ifdef HAVE_LIBFDB5
#endif

#include <sys/stat.h>  // struct stat
#include <ctype.h>
#include <string.h>


static stream_t *stream_new_entry(int resH);
static int streamCompareP(void *streamptr1, void *streamptr2);
static void streamDestroyP(void *streamptr);
static void streamPrintP(void *streamptr, FILE *fp);
static int streamGetPackSize(void *streamptr, void *context);
static void streamPack(void *streamptr, void *buff, int size, int *position, void *context);
static int streamTxCode(void *streamptr);

const resOps streamOps = { streamCompareP, streamDestroyP, streamPrintP, streamGetPackSize, streamPack, streamTxCode };

static int
getByteorder(int byteswap)
{
  switch (HOST_ENDIANNESS)
  {
    case CDI_BIGENDIAN: return byteswap ? CDI_LITTLEENDIAN : CDI_BIGENDIAN;
    case CDI_LITTLEENDIAN: return byteswap ? CDI_BIGENDIAN : CDI_LITTLEENDIAN;
    /* FIXME: does not currently adjust for PDP endianness */
    case CDI_PDPENDIAN:
    default: Error("unhandled endianness");
  }

  return -1;
}

// used also in CDO
int
cdiGetFiletype(const char *uri, int *byteorder)
{
  // clang-format off
  int filetype = CDI_EUFTYPE;
  int swap = 0;
  int version;
  long recpos;

  const char *filename;
  int protocol = cdiGetProtocol(uri, &filename);

  switch (protocol)
    {
    case CDI_PROTOCOL_ACROSS: return CDI_FILETYPE_GRB2;
    case CDI_PROTOCOL_FDB:    return CDI_FILETYPE_GRB2;
    case CDI_PROTOCOL_OTHER:  return CDI_FILETYPE_NC4;  // support for NetCDF remote types and ESDM
    case CDI_PROTOCOL_FILE:
      // handled below;
      break;
    }

  int fileID = fileOpen(filename, "r");

  if      (fileID == CDI_UNDEFID) return CDI_ESYSTEM;
  else if (fileID == -2)          return CDI_ETMOF;

  char buffer[8];
  if (fileRead(fileID, buffer, 8) != 8)
    {
      struct stat buf;
      if (stat(filename, &buf) == 0)
        {
          if (buf.st_size == 0)      return CDI_EISEMPTY;
          if (buf.st_mode & S_IFDIR) return CDI_EISDIR;
        }

      return CDI_EUFTYPE;
    }

  fileRewind(fileID);

  if (memcmp(buffer, "GRIB", 4) == 0)
    {
      version = buffer[7];
      if      (version <= 1) { filetype = CDI_FILETYPE_GRB; }
      else if (version == 2) { filetype = CDI_FILETYPE_GRB2; }
    }
  else if (memcmp(buffer, "CDF\001", 4) == 0) { filetype = CDI_FILETYPE_NC; }
  else if (memcmp(buffer, "CDF\002", 4) == 0) { filetype = CDI_FILETYPE_NC2; }
  else if (memcmp(buffer, "CDF\005", 4) == 0) { filetype = CDI_FILETYPE_NC5; }
  else if (memcmp(buffer + 1, "HDF", 3) == 0) { filetype = CDI_FILETYPE_NC4; }
#ifdef HAVE_LIBSERVICE
  else if (srvCheckFiletype(fileID, &swap))   { filetype = CDI_FILETYPE_SRV; }
#endif
#ifdef HAVE_LIBEXTRA
  else if (extCheckFiletype(fileID, &swap))   { filetype = CDI_FILETYPE_EXT; }
#endif
#ifdef HAVE_LIBIEG
  else if (iegCheckFiletype(fileID, &swap))   { filetype = CDI_FILETYPE_IEG; }
#endif
#ifdef HAVE_LIBGRIB
  else if (gribCheckSeek(fileID, &recpos, &version) == 0)
    {
      if      (version <= 1) { filetype = CDI_FILETYPE_GRB; }
      else if (version == 2) { filetype = CDI_FILETYPE_GRB2; }
    }
#endif
  // clang-format on

  if (CDI_Debug && filetype != CDI_EUFTYPE) Message("found %s file = %s", strfiletype(filetype), filename);

  fileClose(fileID);

  *byteorder = getByteorder(swap);

  return filetype;
}

/*
@Function  streamInqFiletype
@Title     Get the filetype

@Prototype int streamInqFiletype(int streamID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead} or @fref{streamOpenWrite}.

@Description
The function @func{streamInqFiletype} returns the filetype of a stream.

@Result
@func{streamInqFiletype} returns the type of the file format,
one of the set of predefined CDI file format types.
The valid CDI file format types are @func{CDI_FILETYPE_GRB}, @func{CDI_FILETYPE_GRB2}, @func{CDI_FILETYPE_NC},
@func{CDI_FILETYPE_NC2}, @func{CDI_FILETYPE_NC4}, @func{CDI_FILETYPE_NC4C}, @func{CDI_FILETYPE_NC5},
@func{CDI_FILETYPE_NCZARR}, @func{CDI_FILETYPE_SRV}, @func{CDI_FILETYPE_EXT} and @func{CDI_FILETYPE_IEG}.
@EndFunction
*/
int
streamInqFiletype(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->filetype;
}

int
getByteswap(int byteorder)
{
  switch (byteorder)
  {
    case CDI_BIGENDIAN:
    case CDI_LITTLEENDIAN:
    case CDI_PDPENDIAN: return (HOST_ENDIANNESS != byteorder);
    case -1: break;
    default: Error("unexpected byteorder %d query!", byteorder);
  }

  return -1;
}

void
streamDefMaxSteps(int streamID, int maxSteps)
{
  if (maxSteps >= 0)
  {
    stream_t *streamptr = stream_to_pointer(streamID);
    streamptr->maxSteps = maxSteps;
  }
}

int
streamInqNumSteps(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  long ntsteps = streamptr->ntsteps;
  if (ntsteps == (long) CDI_UNDEFID)
  {
    int tsID = 0;
    while (streamInqTimestep(streamID, tsID++)) { ntsteps = streamptr->ntsteps; }
  }

  return (int) ntsteps;
}

static long
get_max_global_recs(stream_t *streamptr)
{
  long maxGlobalRecs = -1;
  const tsteps_t *tsteps = streamptr->tsteps;
  if (tsteps)
  {
    maxGlobalRecs = tsteps[0].nrecs;
    long numSteps = streamptr->ntsteps;
    if (numSteps > 1) maxGlobalRecs += tsteps[1].nrecs * (numSteps - 1);
  }
  return maxGlobalRecs;
}

void
streamDefNumWorker(int streamID, int numWorker)
{
  if (numWorker > 0)
  {
    stream_t *streamptr = stream_to_pointer(streamID);
    int filetype = streamptr->filetype;

    if (streamptr->filemode == 'r')
    {
      if (cdiBaseFiletype(filetype) == CDI_FILETYPE_GRIB)
      {
        (void) streamInqNumSteps(streamID);
        streamptr->maxGlobalRecs = get_max_global_recs(streamptr);
      }
#ifdef HAVE_LIBNETCDF
      else if (filetype == CDI_FILETYPE_NCZARR || (CDI_Test && cdiBaseFiletype(filetype) == CDI_FILETYPE_NETCDF))
      {
        streamptr->maxGlobalRecs = get_max_global_recs(streamptr);
        if (CDI_Test) Message("numWorker=%d", numWorker);
        if (CDI_Test) Message("maxGlobalRecs=%ld", streamptr->maxGlobalRecs);
        if (streamptr->maxGlobalRecs == -1) xabort("Internal error: number of timesteps missing!");
        if (streamptr->maxGlobalRecs == 1) numWorker = 0;
        if (numWorker > streamptr->maxGlobalRecs) numWorker = (int) streamptr->maxGlobalRecs;
        CdfInfo *cdfInfo = &(streamptr->cdfInfo);
        if (cdfInfo->chunkSizeDimT > 1 && numWorker > streamptr->nvars) numWorker = streamptr->nvars;
        if (cdfInfo->chunkSizeDimZ > 1) numWorker = 0;
        if (CDI_Test) Message("chunkSizeTdim=%d chunkSizeZdim=%d", cdfInfo->chunkSizeDimT, cdfInfo->chunkSizeDimZ);
      }
#endif
      else { numWorker = 0; }

      streamptr->numWorker = numWorker;
      if (CDI_Debug || CDI_Test) Message("Number of asynchronous worker: %d", numWorker);
    }
  }
}

/*
@Function  streamDefByteorder
@Title     Define the byte order

@Prototype void streamDefByteorder(int streamID, int byteorder)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  byteorder The byte order of a dataset, one of the CDI constants @func{CDI_BIGENDIAN} and
                     @func{CDI_LITTLEENDIAN}.

@Description
The function @func{streamDefByteorder} defines the byte order of a binary dataset
with the file format type @func{CDI_FILETYPE_SRV}, @func{CDI_FILETYPE_EXT} or @func{CDI_FILETYPE_IEG}.

@EndFunction
*/
void
streamDefByteorder(int streamID, int byteorder)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  streamptr->byteorder = byteorder;
  int filetype = streamptr->filetype;

  switch (filetype)
  {
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
    {
      srvrec_t *srvp = (srvrec_t *) streamptr->record->objectp;
      srvp->byteswap = getByteswap(byteorder);

      break;
    }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
    {
      extrec_t *extp = (extrec_t *) streamptr->record->objectp;
      extp->byteswap = getByteswap(byteorder);

      break;
    }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
    {
      iegrec_t *iegp = (iegrec_t *) streamptr->record->objectp;
      iegp->byteswap = getByteswap(byteorder);

      break;
    }
#endif
  }

  reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
}

/*
@Function  streamInqByteorder
@Title     Get the byte order

@Prototype int streamInqByteorder(int streamID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead} or @fref{streamOpenWrite}.

@Description
The function @func{streamInqByteorder} returns the byte order of a binary dataset
with the file format type @func{CDI_FILETYPE_SRV}, @func{CDI_FILETYPE_EXT} or @func{CDI_FILETYPE_IEG}.

@Result
@func{streamInqByteorder} returns the type of the byte order.
The valid CDI byte order types are @func{CDI_BIGENDIAN} and @func{CDI_LITTLEENDIAN}

@EndFunction
*/
int
streamInqByteorder(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->byteorder;
}

const char *
streamFilesuffix(int filetype)
{
  static const char noSuffix[] = "";
  static const char ncSuffix[] = ".nc";
  static const char grbSuffix[] = ".grb";
  static const char srvSuffix[] = ".srv";
  static const char extSuffix[] = ".ext";
  static const char iegSuffix[] = ".ieg";

  switch (cdiBaseFiletype(filetype))
  {
    case CDI_FILETYPE_GRIB: return grbSuffix;
    case CDI_FILETYPE_SRV: return srvSuffix;
    case CDI_FILETYPE_EXT: return extSuffix;
    case CDI_FILETYPE_IEG: return iegSuffix;
    case CDI_FILETYPE_NETCDF: return ncSuffix;
    default: return noSuffix;
  }
}

const char *
streamFilename(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->filename;
}

static long
cdiInqContents(stream_t *streamptr)
{
  if (streamptr->lockIO) CDI_IO_LOCK();

  long status = 0;
  int filetype = streamptr->filetype;

  switch (cdiBaseFiletype(filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB:
    {
      switch (streamptr->protocol)
      {
#ifdef HAVE_LIBFDB5
        case CDI_PROTOCOL_FDB: status = fdbInqContents(streamptr); break;
#endif
        case CDI_PROTOCOL_ACROSS:  // TODO read from ACROSS
        case CDI_PROTOCOL_OTHER:
        case CDI_PROTOCOL_FILE: status = grbInqContents(streamptr); break;
      }
      break;
    }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: status = srvInqContents(streamptr); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: status = extInqContents(streamptr); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: status = iegInqContents(streamptr); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: status = cdfInqContents(streamptr); break;
#endif
    default:
    {
      status = CDI_ELIBNAVAIL;
      if (CDI_Debug) Message("%s support not compiled in!", strfiletype(filetype));
    }
  }

  if (streamptr->lockIO) CDI_IO_UNLOCK();

  if (status == 0)
  {
    int taxisID = vlistInqTaxis(streamptr->vlistID);
    if (taxisID != CDI_UNDEFID)
    {
      taxis_t *taxisptr1 = &streamptr->tsteps[0].taxis;
      taxis_t *taxisptr2 = taxis_to_pointer(taxisID);
      ptaxisCopy(taxisptr2, taxisptr1);
    }
  }

  return status;
}

int
cdiGetProtocol(const char *uri, const char **filename)
{
  const char *pos = strstr(uri, "://");
  if (pos == NULL)
  {
    *filename = uri;
    return CDI_PROTOCOL_FILE;
  }

  size_t protocollen = (size_t) (pos - uri);
  *filename = pos + 3;

  // if (strncmp(uri, "file", protocollen) == 0) return CDI_PROTOCOL_FILE; // file is already used in NetCDF
  if (strncmp(uri, "fdb", protocollen) == 0) return CDI_PROTOCOL_FDB;
  if (strncmp(uri, "across", protocollen) == 0) return CDI_PROTOCOL_ACROSS;

  *filename = uri;

  return CDI_PROTOCOL_OTHER;
}

int
cdiStreamOpenDefaultDelegate(const char *uri, char filemode, int filetype, stream_t *streamptr, int recordBufIsToBeCreated)
{
  int fileID;
  char temp[2] = { filemode, 0 };

  const char *filename;
  streamptr->protocol = cdiGetProtocol(uri, &filename);

  switch (streamptr->protocol)
  {
    case CDI_PROTOCOL_ACROSS:
#if defined(HAVE_ACROSS) && defined(HAVE_LIBGRIB_API)
      if (filetype != CDI_FILETYPE_GRB2)
      {
        Warning("ACROSS needs to be used with GRIB2");
        return CDI_EUFTYPE;
      }
      fileID = across_connect(filename, filemode, streamptr);
      if (fileID >= 0)
      {
        streamptr->filetype = filetype;
        if (recordBufIsToBeCreated)
        {
          streamptr->record = (Record *) Malloc(sizeof(Record));
          streamptr->record->buffer = NULL;
        }
      }
      return fileID;
#else
#ifdef HAVE_ACROSS
      Warning("ecCodes support not compiled in (Needed for ACROSS)!");
#else
      Warning("ACROSS support not compiled in!");
#endif
      return CDI_ELIBNAVAIL;
#endif

    case CDI_PROTOCOL_FDB:
#if defined(HAVE_LIBFDB5) && defined(HAVE_LIBGRIB_API)

      if (filetype != CDI_FILETYPE_GRB && filetype != CDI_FILETYPE_GRB2)
      {
        Warning("FDB5 needs to be used with GRIB or GRIB2");
        return CDI_EUFTYPE;
      }

      check_fdb_error(fdb_initialise());
      check_fdb_error(fdb_new_handle((fdb_handle_t **) &(streamptr->protocolData)));
      streamptr->filetype = filetype;
      if (recordBufIsToBeCreated)
      {
        streamptr->record = (Record *) Malloc(sizeof(Record));
        streamptr->record->buffer = NULL;
      }
      return 88;

#else  // !(defined(HAVE_LIBFDB5) && defined(HAVE_LIBGRIB_API))

#ifdef HAVE_LIBFDB5
      Warning("ecCodes support not compiled in (Needed for FDB5)!");
#else
      Warning("FDB5 support not compiled in!");
#endif

      return CDI_ELIBNAVAIL;
#endif

    case CDI_PROTOCOL_OTHER:
    case CDI_PROTOCOL_FILE:
      // handled below;
      break;
  }

  switch (filetype)
  {
#if defined(HAVE_LIBGRIB) && (defined(HAVE_LIBCGRIBEX) || defined(HAVE_LIBGRIB_API))
    case CDI_FILETYPE_GRB:
    {
      fileID = gribOpen(filename, temp);
      if (fileID < 0) return CDI_ESYSTEM;
      if (recordBufIsToBeCreated)
      {
        streamptr->record = (Record *) Malloc(sizeof(Record));
        streamptr->record->buffer = NULL;
#ifdef HAVE_LIBCGRIBEX
        streamptr->record->objectp = cgribexNew();
#else
        streamptr->record->objectp = NULL;
#endif
      }
      break;
    }
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRB2:
    {
      fileID = gribOpen(filename, temp);
      if (fileID < 0) return CDI_ESYSTEM;
      if (recordBufIsToBeCreated)
      {
        streamptr->record = (Record *) Malloc(sizeof(Record));
        streamptr->record->buffer = NULL;
      }
      break;
    }
#endif
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
    {
      fileID = fileOpen(filename, temp);
      if (fileID < 0) return CDI_ESYSTEM;
      if (recordBufIsToBeCreated)
      {
        streamptr->record = (Record *) Malloc(sizeof(Record));
        streamptr->record->buffer = NULL;
        streamptr->record->objectp = srvNew();
      }
      break;
    }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
    {
      fileID = fileOpen(filename, temp);
      if (fileID < 0) return CDI_ESYSTEM;
      if (recordBufIsToBeCreated)
      {
        streamptr->record = (Record *) Malloc(sizeof(Record));
        streamptr->record->buffer = NULL;
        streamptr->record->objectp = extNew();
      }
      break;
    }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
    {
      fileID = fileOpen(filename, temp);
      if (fileID < 0) return CDI_ESYSTEM;
      if (recordBufIsToBeCreated)
      {
        streamptr->record = (Record *) Malloc(sizeof(Record));
        streamptr->record->buffer = NULL;
        streamptr->record->objectp = iegNew();
      }
      break;
    }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC5:
    {
      fileID = cdfOpen(filename, temp, filetype);
      break;
    }
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    case CDI_FILETYPE_NCZARR:
    {
      fileID = cdf4Open(filename, temp, &filetype);
      break;
    }
#endif
    default:
    {
      if (CDI_Debug) Message("%s support not compiled in!", strfiletype(filetype));
      return CDI_ELIBNAVAIL;
    }
  }

  streamptr->filetype = filetype;

  return fileID;
}

static long
stream_create_vlist(stream_t *streamptr, struct CdiQuery *query)
{
  int vlistID = vlistCreate();
  if (vlistID < 0) return CDI_ELIMIT;

  cdiVlistMakeInternal(vlistID);
  streamptr->vlistID = vlistID;

  if (query) { streamptr->query = query; }

  long status = cdiInqContents(streamptr);
  if (status >= 0)
  {
    vlist_t *vlistptr = vlist_to_pointer(streamptr->vlistID);
    vlistptr->ntsteps = streamptr->ntsteps;
    cdiVlistMakeImmutable(vlistID);
  }

  return status;
}

int
streamOpenID(const char *filename, char filemode, int filetype, int resH)
{
  if (CDI_Debug) Message("Open %s mode %c file %s", strfiletype(filetype), filemode, filename ? filename : "(NUL)");

  if (!filename || filetype < 0) return CDI_EINVAL;

  stream_t *streamptr = stream_new_entry(resH);
  int streamID = CDI_ESYSTEM;

#ifndef HAVE_NC4HDF5_THREADSAFE
  if (CDI_Threadsafe)
  {
#ifdef HAVE_LIBPTHREAD
    if (filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C || filetype == CDI_FILETYPE_NCZARR) streamptr->lockIO = true;
#else
    static bool lwarn = true;
    if (lwarn)
    {
      lwarn = false;
      Warning("CDI threadsafe failed, pthread support not compiled in!");
    }
#endif
  }
#endif

  if (streamptr->lockIO) CDI_IO_LOCK();

  int (*streamOpenDelegate)(const char *filename, char filemode, int filetype, stream_t *streamptr, int recordBufIsToBeCreated)
      = (int (*)(const char *, char, int, stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_OPEN_BACKEND).func;

  int fileID = streamOpenDelegate(filename, filemode, filetype, streamptr, 1);
  if (fileID < 0)
  {
    streamID = fileID;
    if (streamptr->record) Free(streamptr->record);
    reshRemove(streamptr->self, &streamOps);
    if (streamptr->lockIO) CDI_IO_UNLOCK();
    Free(streamptr);
    streamptr = NULL;
  }
  else
  {
    streamID = streamptr->self;
    if (streamID < 0) return CDI_ELIMIT;

    streamptr->filemode = filemode;
    streamptr->filename = strdup(filename);
    streamptr->fileID = fileID;
    if (streamptr->lockIO) CDI_IO_UNLOCK();
  }

  return streamID;
}

int
streamOpenNCMem(int ncidp, char mode)
{
  cdiInitialize();
  stream_t *streamptr = stream_new_entry(CDI_UNDEFID);
  int streamID = CDI_ESYSTEM;

#ifdef NC_FORMAT_64BIT_DATA  // so that we ran reliably use NC5 filetype
                             // Not doing this can result in an error message
                             // that falsely states a NC3 file is being used
  streamID = streamptr->self;
  if (streamID < 0) return CDI_ELIMIT;

  size_t max_file_path_length = 128;
  size_t actual_length = 0;
  char *filename = (char *) malloc(sizeof(char) * max_file_path_length);

  nc_inq_path(ncidp, &actual_length, filename);

  streamptr->filename = strdup(filename);
  streamptr->filemode = mode;
  streamptr->filetype = CDI_FILETYPE_NC5;
  streamptr->fileID = ncidp;

  if (mode == 'r')
  {
    int status = stream_create_vlist(streamptr, NULL);
    if (status < 0)
    {
      streamID = status;
      if (streamptr->record) Free(streamptr->record);
      reshRemove(streamptr->self, &streamOps);
    }
  }
#else
  Error("tried to use in memory capabilities without Netcdf4 enabled");
#endif

  return streamID;
}
static int
streamOpen(const char *filename, const char *filemode, int filetype)
{
  if (!filemode || strlen(filemode) != 1) return CDI_EINVAL;
  return streamOpenID(filename, (char) tolower(filemode[0]), filetype, CDI_UNDEFID);
}

static int
streamOpenA(const char *filename, const char *filemode, int filetype)
{
  if (CDI_Debug) Message("Open %s file (mode=%c); filename: %s", strfiletype(filetype), (int) *filemode, filename);
  if (CDI_Debug) printf("streamOpenA: %s\n", filename);  // seg fault without this line on thunder/squall with "cdo cat x y"

  if (!filename || !filemode || filetype < 0) return CDI_EINVAL;

  stream_t *streamptr = stream_new_entry(CDI_UNDEFID);
  int fileID = CDI_UNDEFID;

  {
    int (*streamOpenDelegate)(const char *filename, char filemode, int filetype, stream_t *streamptr, int recordBufIsToBeCreated)
        = (int (*)(const char *, char, int, stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_OPEN_BACKEND).func;

    fileID = streamOpenDelegate(filename, 'r', filetype, streamptr, 1);
  }

  if (fileID == CDI_UNDEFID || fileID == CDI_ELIBNAVAIL || fileID == CDI_ESYSTEM) return fileID;

  int streamID = streamptr->self;

  streamptr->filemode = tolower(*filemode);
  streamptr->filename = strdup(filename);
  streamptr->fileID = fileID;

  streamptr->vlistID = vlistCreate();
  cdiVlistMakeInternal(streamptr->vlistID);
  // cdiReadByteorder(streamID);
  long status = cdiInqContents(streamptr);
  if (status < 0) return (int) status;
  vlist_t *vlistptr = vlist_to_pointer(streamptr->vlistID);
  vlistptr->ntsteps = streamInqNumSteps(streamID);

  // Needed for NetCDF4
  for (int varID = 0; varID < vlistptr->nvars; ++varID) streamptr->vars[varID].defmiss = true;

  if (str_is_equal(filemode, "r")) cdiVlistMakeImmutable(streamptr->vlistID);

  {
    void (*streamCloseDelegate)(stream_t * streamptr, int recordBufIsToBeDeleted)
        = (void (*)(stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_CLOSE_BACKEND).func;

    streamCloseDelegate(streamptr, 0);
  }

  switch (filetype)
  {
#if defined(HAVE_LIBGRIB) && (defined(HAVE_LIBCGRIBEX) || defined(HAVE_LIBGRIB_API))
    case CDI_FILETYPE_GRB:
#ifdef HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRB2:
#endif
    {
      fileID = gribOpen(filename, filemode);
      if (fileID != CDI_UNDEFID) gribContainersNew(streamptr);
      break;
    }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
    {
      fileID = fileOpen(filename, filemode);
      break;
    }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
    {
      fileID = fileOpen(filename, filemode);
      break;
    }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
    {
      fileID = fileOpen(filename, filemode);
      break;
    }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC5:
    {
      fileID = cdfOpen(filename, filemode, filetype);
      streamptr->ncmode = 2;
      break;
    }
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    {
      fileID = cdf4Open(filename, filemode, &filetype);
      streamptr->ncmode = 2;
      break;
    }
    case CDI_FILETYPE_NCZARR:
    {
      Warning("%s not available in append mode!", strfiletype(filetype));
      return CDI_ELIBNAVAIL;
    }
#endif
    default:
    {
      if (CDI_Debug) Message("%s support not compiled in!", strfiletype(filetype));
      return CDI_ELIBNAVAIL;
    }
  }

  if (fileID == CDI_UNDEFID)
    streamID = CDI_UNDEFID;
  else
    streamptr->fileID = fileID;

  return streamID;
}

/*
@Function  streamOpenRead
@Title     Open a dataset for reading

@Prototype int streamOpenRead(const char *path)
@Parameter
    @Item  path  The name of the dataset to be read.

@Description
The function @func{streamOpenRead} opens an existing dataset for reading.

@Result
Upon successful completion @func{streamOpenRead} returns an identifier to the
open stream. Otherwise, a negative number with the error status is returned.

@Errors
@List
   @Item  CDI_ESYSTEM     Operating system error.
   @Item  CDI_EINVAL      Invalid argument.
   @Item  CDI_EUFILETYPE  Unsupported file type.
   @Item  CDI_ELIBNAVAIL  Library support not compiled in.
@EndList

@Example
Here is an example using @func{streamOpenRead} to open an existing NetCDF
file named @func{foo.nc} for reading:

@Source
   ...
int streamID;
   ...
streamID = streamOpenRead("foo.nc");
if ( streamID < 0 ) handle_error(streamID);
   ...
@EndSource
@EndFunction
*/
int
streamOpenRead(const char *filename)
{
  cdiInitialize();

  int byteorder = 0;
  int filetype = cdiGetFiletype(filename, &byteorder);
  if (filetype < 0) return filetype;

  int streamID = streamOpen(filename, "r", filetype);
  if (streamID >= 0)
  {
    stream_t *streamptr = stream_to_pointer(streamID);
    streamptr->byteorder = byteorder;

    long status = stream_create_vlist(streamptr, NULL);
    if (status < 0)
    {
      streamID = (int) status;
      if (streamptr->record) Free(streamptr->record);
      reshRemove(streamptr->self, &streamOps);
    }
  }

  return streamID;
}

int
streamOpenReadQuery(const char *filename, struct CdiQuery *query)
{
  cdiInitialize();

  int byteorder = 0;
  int filetype = cdiGetFiletype(filename, &byteorder);
  if (filetype < 0) return filetype;

  if (cdiBaseFiletype(filetype) != CDI_FILETYPE_NETCDF && filetype != CDI_FILETYPE_GRB2) return CDI_EQNAVAIL;

  int streamID = streamOpen(filename, "r", filetype);
  if (streamID >= 0)
  {
    stream_t *streamptr = stream_to_pointer(streamID);
    streamptr->byteorder = byteorder;

    long status = stream_create_vlist(streamptr, query);
    if (status < 0)
    {
      streamID = (int) status;
      if (streamptr->record) Free(streamptr->record);
      reshRemove(streamptr->self, &streamOps);
    }
  }

  return streamID;
}

int
streamOpenAppend(const char *filename)
{
  cdiInitialize();

  int byteorder = 0;
  int filetype = cdiGetFiletype(filename, &byteorder);
  if (filetype < 0) return filetype;

  int streamID = streamOpenA(filename, "a", filetype);
  if (streamID >= 0)
  {
    stream_t *streamptr = stream_to_pointer(streamID);
    streamptr->byteorder = byteorder;
  }

  return streamID;
}

/*
@Function  streamOpenWrite
@Title     Create a new dataset

@Prototype int streamOpenWrite(const char *path, int filetype)
@Parameter
    @Item  path      The name of the new dataset.
    @Item  filetype  The type of the file format, one of the set of predefined CDI file format types.
                     The valid CDI file format types are @func{CDI_FILETYPE_GRB}, @func{CDI_FILETYPE_GRB2}, @func{CDI_FILETYPE_NC},
                     @func{CDI_FILETYPE_NC2}, @func{CDI_FILETYPE_NC4}, @func{CDI_FILETYPE_NC4C}, @func{CDI_FILETYPE_NC5},
                     @func{CDI_FILETYPE_NCZARR}, @func{CDI_FILETYPE_SRV}, @func{CDI_FILETYPE_EXT} and @func{CDI_FILETYPE_IEG}.

@Description
The function @func{streamOpenWrite} creates a new datset.
@Result
Upon successful completion @func{streamOpenWrite} returns an identifier to the
open stream. Otherwise, a negative number with the error status is returned.

@Errors
@List
   @Item  CDI_ESYSTEM     Operating system error.
   @Item  CDI_EINVAL      Invalid argument.
   @Item  CDI_EUFILETYPE  Unsupported file type.
   @Item  CDI_ELIBNAVAIL  Library support not compiled in.
@EndList

@Example
Here is an example using @func{streamOpenWrite} to create a new NetCDF file named @func{foo.nc} for writing:

@Source
   ...
int streamID;
   ...
streamID = streamOpenWrite("foo.nc", CDI_FILETYPE_NC);
if ( streamID < 0 ) handle_error(streamID);
   ...
@EndSource
@EndFunction
*/
int
streamOpenWrite(const char *filename, int filetype)
{
  cdiInitialize();

  return streamOpen(filename, "w", filetype);
}

static void
streamDefaultValue(stream_t *streamptr)
{
  streamptr->self = CDI_UNDEFID;
  streamptr->accesstype = CDI_UNDEFID;
  streamptr->accessmode = 0;
  streamptr->filetype = CDI_FILETYPE_UNDEF;
  streamptr->byteorder = CDI_UNDEFID;
  streamptr->fileID = 0;
  streamptr->filemode = 0;
  streamptr->numvals = 0;
  streamptr->filename = NULL;
  streamptr->record = NULL;
  streamptr->query = NULL;
  streamptr->varsAllocated = 0;
  streamptr->nrecs = 0;
  streamptr->nvars = 0;
  streamptr->vars = NULL;
  streamptr->ncmode = 0;
  streamptr->curTsID = CDI_UNDEFID;
  streamptr->rtsteps = 0;
  streamptr->ntsteps = CDI_UNDEFID;
  streamptr->maxSteps = CDI_UNDEFID;
  streamptr->tsteps = NULL;
  streamptr->tstepsTableSize = 0;
  streamptr->tstepsNextID = 0;
  streamptr->vlistID = CDI_UNDEFID;
  streamptr->globalatts = 0;
  streamptr->localatts = 0;
  streamptr->unreduced = cdiDataUnreduced;
  streamptr->have_missval = cdiHaveMissval;
  streamptr->comptype = CDI_COMPRESS_NONE;
  streamptr->complevel = 0;
  streamptr->shuffle = 0;
  streamptr->sortname = (cdiSortName > 0);
  streamptr->lockIO = CDI_Lock_IO;
  // netcdf4/HDF5 filter
  streamptr->filterSpec = NULL;

  basetimeInit(&streamptr->basetime);

#ifdef HAVE_LIBNETCDF
  CdfInfo *cdfInfo = &(streamptr->cdfInfo);

  for (int i = 0; i < MAX_GRIDS_PS; i++)
  {
    cdfInfo->cdfGridList[i].start = CDI_UNDEFID;
    cdfInfo->cdfGridList[i].count = CDI_UNDEFID;
    cdfInfo->cdfGridList[i].gridID = CDI_UNDEFID;
    for (size_t j = 0; j < CDF_SIZE_NCID; ++j) cdfInfo->cdfGridList[i].ncIdList[j] = CDI_UNDEFID;
  }

  for (int i = 0; i < MAX_ZAXES_PS; i++)
  {
    cdfInfo->cdfZaxisList[i].start = CDI_UNDEFID;
    cdfInfo->cdfZaxisList[i].count = CDI_UNDEFID;
    cdfInfo->cdfZaxisList[i].zaxisID = CDI_UNDEFID;
  }

  cdfInfo->chunkSizeDimT = 0;
  cdfInfo->chunkSizeDimZ = 0;

  for (int i = 0; i < MAX_DIMS_PS; i++) cdfInfo->ncDimLenList[i] = 0;
  for (int i = 0; i < MAX_ZAXES_PS; i++) cdfInfo->zaxisIdList[i] = CDI_UNDEFID;
  for (int i = 0; i < MAX_ZAXES_PS; i++) cdfInfo->ncZvarIdList[i] = CDI_UNDEFID;
  for (int i = 0; i < MAX_DIMS_PS; i++) cdfInfo->ncDimIdList[i] = CDI_UNDEFID;

  cdfInfo->ncNumDims = 0;
  cdfInfo->complexFloatId = CDI_UNDEFID;
  cdfInfo->complexDoubleId = CDI_UNDEFID;

  cdfInfo->vct.ilev = 0;
  cdfInfo->vct.mlev = 0;
  cdfInfo->vct.ilevID = CDI_UNDEFID;
  cdfInfo->vct.mlevID = CDI_UNDEFID;
#endif
  streamptr->maxGlobalRecs = CDI_UNDEFID;

  streamptr->gribContainers = NULL;

  streamptr->numWorker = 0;
  streamptr->nextGlobalRecId = 0;
  streamptr->cachedTsID = -1;
  streamptr->jobs = NULL;
  streamptr->jobManager = NULL;

  streamptr->protocolData = NULL;

#ifdef HAVE_LIBFDB5
  streamptr->fdbNumItems = 0;
  streamptr->fdbKeyValueList = NULL;
#endif
}

static stream_t *
stream_new_entry(int resH)
{
  cdiInitialize(); /* ***************** make MT version !!! */

  stream_t *streamptr = (stream_t *) Malloc(sizeof(stream_t));
  streamDefaultValue(streamptr);

  if (resH == CDI_UNDEFID) { streamptr->self = reshPut(streamptr, &streamOps); }
  else
  {
    streamptr->self = resH;
    reshReplace(resH, streamptr, &streamOps);
  }

  return streamptr;
}

void
cdiStreamCloseDefaultDelegate(stream_t *streamptr, int recordBufIsToBeDeleted)
{
  int fileID = streamptr->fileID;
  int filetype = streamptr->filetype;

  if (streamptr->filterSpec)
  {
    free(streamptr->filterSpec);
    streamptr->filterSpec = NULL;
  }

  switch (streamptr->protocol)
  {
    case CDI_PROTOCOL_ACROSS:
#ifdef HAVE_ACROSS
      if (fileID) across_disconnect(fileID);
      if (streamptr->protocolData)
      {
        Free(((across_info_t *) streamptr->protocolData)->expid);
        Free(streamptr->protocolData);
        streamptr->protocolData = NULL;
      }
#endif
      return;

    case CDI_PROTOCOL_FDB:
#ifdef HAVE_LIBFDB5
      if (streamptr->protocolData) check_fdb_error(fdb_delete_handle(streamptr->protocolData));
      streamptr->protocolData = NULL;
#endif
      return;

    case CDI_PROTOCOL_OTHER:
    case CDI_PROTOCOL_FILE:
      // handled below;
      break;
  }

  if (fileID == CDI_UNDEFID)
  {
    Warning("File %s not open!", streamptr->filename);
    return;
  }

  switch (cdiBaseFiletype(filetype))
  {
#if defined(HAVE_LIBGRIB) && (defined(HAVE_LIBCGRIBEX) || defined(HAVE_LIBGRIB_API))
    case CDI_FILETYPE_GRIB:
      if (filetype == CDI_FILETYPE_GRB)
      {
        gribClose(fileID);
        if (recordBufIsToBeDeleted) gribContainersDelete(streamptr);
#ifdef HAVE_LIBCGRIBEX
        if (recordBufIsToBeDeleted) cgribexDelete(streamptr->record->objectp);
#endif
      }
      else if (filetype == CDI_FILETYPE_GRB2)
      {
        gribClose(fileID);
        if (recordBufIsToBeDeleted) gribContainersDelete(streamptr);
      }
      break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
    {
      fileClose(fileID);
      if (recordBufIsToBeDeleted) srvDelete(streamptr->record->objectp);
      break;
    }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
    {
      fileClose(fileID);
      if (recordBufIsToBeDeleted) extDelete(streamptr->record->objectp);
      break;
    }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
    {
      fileClose(fileID);
      if (recordBufIsToBeDeleted) iegDelete(streamptr->record->objectp);
      break;
    }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
    {
      cdfClose(fileID);
      if (streamptr->ntsteps == 0 && streamptr->tsteps != NULL)
      {
        tsteps_t *tstep = &(streamptr->tsteps[0]);
        // clang-format off
        if (tstep->recinfo) { Free(tstep->recinfo); tstep->recinfo = NULL; }
        if (tstep->records) { Free(tstep->records); tstep->records = NULL; }
        if (tstep->recIDs) { Free(tstep->recIDs); tstep->recIDs = NULL; }
        // clang-format on
      }
      break;
    }
#endif
    default:
    {
      Error("%s support not compiled in (fileID = %d)!", strfiletype(filetype), fileID);
      break;
    }
  }
}

static void
deallocate_sleveltable_t(sleveltable_t *entry)
{
  if (entry->recordID) Free(entry->recordID);
  if (entry->lindex) Free(entry->lindex);
  entry->recordID = NULL;
  entry->lindex = NULL;
}

#ifdef HAVE_LIBNETCDF
static void
deallocate_cdfCache(CdfCache *cache)
{
  if (cache->buffer) Free(cache->buffer);
  cache->buffer = NULL;
}
#endif

static void
streamDestroyViaDelegate(stream_t *streamptr, void (*streamCloseDelegate)(stream_t *streamptr, int recordBufIsToBeDeleted))
{
  xassert(streamptr);
  if (streamptr->filetype != CDI_FILETYPE_UNDEF) streamCloseDelegate(streamptr, 1);

  if (streamptr->record)
  {
    if (streamptr->record->buffer) Free(streamptr->record->buffer);
    Free(streamptr->record);
    streamptr->record = NULL;
  }

  streamptr->filetype = CDI_FILETYPE_UNDEF;
  if (streamptr->filename)
  {
    Free(streamptr->filename);
    streamptr->filename = NULL;
  }

  if (streamptr->filterSpec) free(streamptr->filterSpec);

  if (streamptr->vars)
  {
    for (int index = 0; index < streamptr->nvars; index++)
    {
      sleveltable_t *pslev = streamptr->vars[index].recordTable;
      unsigned nsub = streamptr->vars[index].subtypeSize >= 0 ? (unsigned) streamptr->vars[index].subtypeSize : 0U;
      for (size_t isub = 0; isub < nsub; isub++) deallocate_sleveltable_t(pslev + isub);
      if (pslev) Free(pslev);
#ifdef HAVE_LIBNETCDF
      if (streamptr->vars[index].cdfCache) deallocate_cdfCache(streamptr->vars[index].cdfCache);
      streamptr->vars[index].cdfCache = NULL;
#endif
    }
    Free(streamptr->vars);
    streamptr->vars = NULL;
  }

  if (streamptr->tsteps)
  {
    int maxSteps = streamptr->tstepsNextID;
    for (int index = 0; index < maxSteps; ++index)
    {
      tsteps_t *tstep = &(streamptr->tsteps[index]);
      // clang-format off
      if (tstep->recinfo) { Free(tstep->recinfo); tstep->recinfo = NULL; }
      if (tstep->records) { Free(tstep->records); tstep->records = NULL; }
      if (tstep->recIDs) { Free(tstep->recIDs); tstep->recIDs = NULL; }
      // clang-format on
      taxisDestroyKernel(&(tstep->taxis));
    }

    Free(streamptr->tsteps);
    streamptr->tsteps = NULL;
  }

#ifdef HAVE_LIBFDB5
  if (streamptr->fdbKeyValueList)
  {
    cdi_fdb_delete_kvlist(streamptr->fdbNumItems, streamptr->fdbKeyValueList);
    streamptr->fdbNumItems = 0;
    streamptr->fdbKeyValueList = NULL;
  }
#endif

  int vlistID = streamptr->vlistID;
  if (vlistID != -1)
  {
    int taxisID = (streamptr->filemode != 'w') ? vlistInqTaxis(vlistID) : -1;
    if (taxisID != -1) taxisDestroy(taxisID);
    void (*mycdiVlistDestroy_)(int, bool) = (void (*)(int, bool)) namespaceSwitchGet(NSSWITCH_VLIST_DESTROY_).func;
    mycdiVlistDestroy_(vlistID, true);
  }

  if (streamptr->jobs) Free(streamptr->jobs);
  if (streamptr->jobManager) AsyncWorker_finalize((AsyncManager *) streamptr->jobManager);

  Free(streamptr);
}

static void
streamDestroy(stream_t *streamptr)
{
  void (*streamCloseDelegate)(stream_t * streamptr, int recordBufIsToBeDeleted)
      = (void (*)(stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_CLOSE_BACKEND).func;

  streamDestroyViaDelegate(streamptr, streamCloseDelegate);
}

static void
streamDestroyP(void *streamptr)
{
  streamDestroy((stream_t *) streamptr);
}

/*
@Function  streamClose
@Title     Close an open dataset

@Prototype  void streamClose(int streamID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead} or @fref{streamOpenWrite}.

@Description
The function @func{streamClose} closes an open dataset.

@EndFunction
*/
void
streamClose(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  bool lockIO = streamptr->lockIO;
  if (lockIO) CDI_IO_LOCK();

  if (CDI_Debug) Message("streamID = %d filename = %s", streamID, streamptr->filename);
  streamDestroy(streamptr);
  reshRemove(streamID, &streamOps);
  if (CDI_Debug) Message("Removed stream %d from stream list", streamID);

  if (lockIO) CDI_IO_UNLOCK();
}

void
cdiStreamSync_(stream_t *streamptr)
{
  int fileID = streamptr->fileID;
  int filetype = streamptr->filetype;
  int vlistID = streamptr->vlistID;
  int numVars = vlistNvars(vlistID);

  if (fileID == CDI_UNDEFID) { Warning("File %s not open!", streamptr->filename); }
  else if (vlistID == CDI_UNDEFID) { Warning("Vlist undefined for file %s!", streamptr->filename); }
  else if (numVars == 0) { Warning("No variables defined!"); }
  else
  {
    if (streamptr->filemode == 'w' || streamptr->filemode == 'a')
    {
      switch (cdiBaseFiletype(filetype))
      {
#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NETCDF:
        {
          void cdf_sync(int ncid);
          if (streamptr->ncmode == 2) cdf_sync(fileID);
          break;
        }
#endif
        default:
        {
          fileFlush(fileID);
          break;
        }
      }
    }
  }
}

/*
@Function  streamSync
@Title     Synchronize an Open Dataset to Disk

@Prototype  void streamSync(int streamID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.

@Description
The function @func{streamSync} offers a way to synchronize the disk copy of a dataset with in-memory buffers.

@EndFunction
*/
void
streamSync(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  void (*myStreamSync_)(stream_t * streamptr) = (void (*)(stream_t *)) namespaceSwitchGet(NSSWITCH_STREAM_SYNC).func;
  myStreamSync_(streamptr);
}

int
cdiStreamDefTimestep_(stream_t *streamptr, int tsID)
{
  stream_check_ptr(__func__, streamptr);

  if (CDI_Debug) Message("streamID = %d  tsID = %d", streamptr->self, tsID);

  int vlistID = streamptr->vlistID;
  if (vlistID == CDI_UNDEFID)
    Error("Must not call streamDefTimestep for stream (ID=%d) with (not yet) defined vlist", streamptr->self);

  if (tsID > 0)
  {
    int newtsID = tstepsNewEntry(streamptr);
    if (tsID != newtsID) Error("Internal problem: tsID = %d newtsID = %d", tsID, newtsID);
  }

  int taxisID = vlistInqTaxis(vlistID);
  if (taxisID != CDI_UNDEFID) ptaxisCopy(&streamptr->tsteps[tsID].taxis, taxis_to_pointer(taxisID));

  streamptr->curTsID = tsID;
  streamptr->ntsteps = tsID + 1;

  bool baseFiletypeIsNetCDF = (cdiBaseFiletype(streamptr->filetype) == CDI_FILETYPE_NETCDF);
#ifdef HAVE_LIBNETCDF
  int timeIsVarying = vlistHasTime(vlistID);
  if (baseFiletypeIsNetCDF && timeIsVarying)
  {
    /* usually points to cdfDefTimestep in serial mode but
     * to cdiPioCdfDefTimestep on servers and to a null-op on
     * clients in client/server mode */
    void (*myCdfDefTimestep)(stream_t * streamptr, int tsID, size_t)
        = (void (*)(stream_t *, int, size_t)) namespaceSwitchGet(NSSWITCH_CDF_DEF_TIMESTEP).func;
    myCdfDefTimestep(streamptr, tsID, 1);
  }
#endif

  cdi_create_records(streamptr, tsID, !baseFiletypeIsNetCDF);

  return (int) streamptr->ntsteps;
}

/*
@Function  streamDefTimestep
@Title     Define a timestep

@Prototype int streamDefTimestep(int streamID, int tsID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  tsID      Timestep identifier.

@Description
The function @func{streamDefTimestep} defines a timestep of a stream by the identifier tsID.
The identifier tsID is the timestep index starting at 0 for the first timestep.
Before calling this function the functions @func{taxisDefVdate} and @func{taxisDefVtime} should be used
to define the timestamp for this timestep. All calls to write the data refer to this timestep.

@Result
@func{streamDefTimestep} returns the number of expected records of the timestep.

@EndFunction
*/
int
streamDefTimestep(int streamID, int tsID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  if (streamptr->lockIO) CDI_IO_LOCK();

  int (*myStreamDefTimestep_)(stream_t * streamptr, int tsID)
      = (int (*)(stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_DEF_TIMESTEP_).func;
  int status = myStreamDefTimestep_(streamptr, tsID);

  if (streamptr->lockIO) CDI_IO_UNLOCK();

  return status;
}

int
streamInqCurTimestepID(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->curTsID;
}

/*
@Function  streamInqTimestep
@Title     Get timestep information

@Prototype int streamInqTimestep(int streamID, int tsID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead} or @fref{streamOpenWrite}.
    @Item  tsID      Timestep identifier.

@Description
The function @func{streamInqTimestep} sets the next timestep to the identifier tsID.
The identifier tsID is the timestep index starting at 0 for the first timestep.
After a call to this function the functions @func{taxisInqVdate} and @func{taxisInqVtime} can be used
to read the timestamp for this timestep. All calls to read the data refer to this timestep.

@Result
@func{streamInqTimestep} returns the number of records of the timestep or 0, if the end of the file is reached.

@EndFunction
*/
int
streamInqTimestep(int streamID, int tsID)
{
  int nrecs = 0;
  stream_t *streamptr = stream_to_pointer(streamID);
  int vlistID = streamptr->vlistID;

  if (tsID < streamptr->ntsteps) streamptr->tsteps[tsID].curRecID = CDI_UNDEFID;  // fix for netCDF
  if (tsID < streamptr->rtsteps)
  {
    streamptr->curTsID = tsID;
    nrecs = streamptr->tsteps[tsID].nrecs;
    streamptr->tsteps[tsID].curRecID = CDI_UNDEFID;
    int taxisID = vlistInqTaxis(vlistID);
    if (taxisID == -1) Error("Timestep undefined for fileID = %d", streamID);
    ptaxisCopy(taxis_to_pointer(taxisID), &streamptr->tsteps[tsID].taxis);

    return nrecs;
  }

  if (tsID >= streamptr->ntsteps && streamptr->ntsteps != CDI_UNDEFID) return 0;

  int filetype = streamptr->filetype;

  if (CDI_Debug) Message("streamID = %d  tsID = %d  filetype = %d", streamID, tsID, filetype);

  if (streamptr->lockIO) CDI_IO_LOCK();

  switch (cdiBaseFiletype(filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB:
    {
      switch (streamptr->protocol)
      {
        case CDI_PROTOCOL_FDB: nrecs = fdbInqTimestep(streamptr, tsID); break;

        case CDI_PROTOCOL_ACROSS:  // TODO read from ACROSS
        case CDI_PROTOCOL_OTHER:
        case CDI_PROTOCOL_FILE: nrecs = grbInqTimestep(streamptr, tsID); break;
      }
      break;
    }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
    {
      nrecs = srvInqTimestep(streamptr, tsID);
      break;
    }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
    {
      nrecs = extInqTimestep(streamptr, tsID);
      break;
    }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
    {
      nrecs = iegInqTimestep(streamptr, tsID);
      break;
    }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
    {
      nrecs = cdfInqTimestep(streamptr, tsID);
      break;
    }
#endif
    default:
    {
      Error("%s support not compiled in!", strfiletype(filetype));
      break;
    }
  }

  if (streamptr->lockIO) CDI_IO_UNLOCK();

  int taxisID = vlistInqTaxis(vlistID);
  if (taxisID == -1) Error("Timestep undefined for fileID = %d", streamID);

  ptaxisCopy(taxis_to_pointer(taxisID), &streamptr->tsteps[tsID].taxis);

  return nrecs;
}

// This function is used in CDO!
SizeType
streamNvals(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->numvals;
}

/*
@Function  streamDefVlist
@Title     Define the variable list

@Prototype void streamDefVlist(int streamID, int vlistID)
@Parameter
    @Item  streamID Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.

@Description
The function @func{streamDefVlist} defines the variable list of a stream.

To safeguard against errors by modifying the wrong vlist object,
this function makes the passed vlist object immutable.
All further vlist changes have to use the vlist object returned by streamInqVlist().

@EndFunction
*/
void
streamDefVlist(int streamID, int vlistID)
{
  void (*myStreamDefVlist)(int streamID, int vlistID) = (void (*)(int, int)) namespaceSwitchGet(NSSWITCH_STREAM_DEF_VLIST_).func;
  myStreamDefVlist(streamID, vlistID);
}

// The single image implementation of streamDefVlist
void
cdiStreamDefVlist_(int streamID, int vlistID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  if (streamptr->vlistID == CDI_UNDEFID)
  {
    if (streamptr->lockIO) CDI_IO_LOCK();

    int vlistCopy = vlistDuplicate(vlistID);
    cdiVlistMakeInternal(vlistCopy);
    cdiVlistMakeImmutable(vlistID);
    cdiStreamSetupVlist(streamptr, vlistCopy);

    if (streamptr->lockIO) CDI_IO_UNLOCK();
  }
  else
    Warning("vlist already defined for %s!", streamptr->filename);
}

/*
@Function  streamInqVlist
@Title     Get the variable list

@Prototype int streamInqVlist(int streamID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead} or @fref{streamOpenWrite}.

@Description
The function @func{streamInqVlist} returns the variable list of a stream.

@Result
@func{streamInqVlist} returns an identifier to the variable list.

@EndFunction
*/
int
streamInqVlist(int streamID)
{
  stream_t *s = stream_to_pointer(streamID);
  return s->vlistID;
}

void
streamDefShuffle(int streamID, int shuffle)
{
  stream_t *s = stream_to_pointer(streamID);
  if (s->shuffle != shuffle)
  {
    s->shuffle = shuffle;
    reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
  }
}

void
streamDefFilter(int streamID, const char *filterSpec)
{
  stream_t *s = stream_to_pointer(streamID);
  if (filterSpec)
  {
    // if (s->filterSpec) Error("Filter spec already defined!");
    if (s->filterSpec == NULL)
    {
      s->filterSpec = strdup(filterSpec);
      reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
    }
  }
}

void
streamDefCompType(int streamID, int comptype)
{
  stream_t *s = stream_to_pointer(streamID);
  if (s->comptype != comptype)
  {
    s->comptype = comptype;
    reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
  }
}

void
streamDefCompLevel(int streamID, int complevel)
{
  stream_t *s = stream_to_pointer(streamID);
  if (s->complevel != complevel)
  {
    s->complevel = complevel;
    reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
  }
}

int
streamInqCompType(int streamID)
{
  stream_t *s = stream_to_pointer(streamID);
  return s->comptype;
}

int
streamInqCompLevel(int streamID)
{
  stream_t *s = stream_to_pointer(streamID);
  return s->complevel;
}

int
streamInqFileID(int streamID)
{
  stream_t *s = (stream_t *) reshGetVal(streamID, &streamOps);
  return s->fileID;
}

static int
streamTxCode(void *s)
{
  (void) s;
  return STREAM;
}

void
cdiStreamSetupVlist(stream_t *s, int vlistID)
{
  void (*myStreamSetupVlist)(stream_t * s, int vlistID)
      = (void (*)(stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_SETUP_VLIST).func;
  myStreamSetupVlist(s, vlistID);
}

void
cdiStreamSetupVlist_(stream_t *streamptr, int vlistID)
{
  streamptr->vlistID = vlistID;
  int numVars = vlistNvars(vlistID);
  for (int varID = 0; varID < numVars; ++varID)
  {
    int gridID = vlistInqVarGrid(vlistID, varID);
    int zaxisID = vlistInqVarZaxis(vlistID, varID);
    int tilesetID = vlistInqVarSubtype(vlistID, varID);
    stream_new_var(streamptr, gridID, zaxisID, tilesetID);
    if (streamptr->have_missval) vlistDefVarMissval(vlistID, varID, vlistInqVarMissval(vlistID, varID));
  }

  if (streamptr->filemode == 'w')
  {
    tstepsNewEntry(streamptr);  // timestep 0
    int vlistIDw = streamptr->vlistID;
    int timeIsVarying = vlistHasTime(vlistIDw);
    if (timeIsVarying)
    {
      int taxisID = vlistInqTaxis(vlistIDw);
      if (taxisID == CDI_UNDEFID)
      {
        Warning("taxisID undefined for fileID = %d! Using absolute time axis.", streamptr->self);
        taxisID = taxisCreate(TAXIS_ABSOLUTE);
        vlistDefTaxis(vlistIDw, taxisID);
      }

#ifdef HAVE_LIBNETCDF
      if (taxisInqType(taxisID) == TAXIS_RELATIVE)
        if (cdiBaseFiletype(streamptr->filetype) == CDI_FILETYPE_NETCDF)
        {
          const taxis_t *taxisptr = taxis_to_pointer(taxisID);
          if (cdiDateTime_isNull(taxisptr->rDateTime))
          {
            int vdate = taxisInqVdate(taxisID);
            if (vdate == 0) vdate = 10101;
            taxisDefRdate(taxisID, vdate);
          }
        }
#endif
      ptaxisCopy(&streamptr->tsteps[0].taxis, taxis_to_pointer(taxisID));
    }

    switch (cdiBaseFiletype(streamptr->filetype))
    {
#ifdef HAVE_LIBNETCDF
      case CDI_FILETYPE_NETCDF:
      {
        /* calls cdfDefCoordinateVars in serial mode but
         * cdiPioClientStreamNOP (i.e. nothing) on client ranks
         * and cdiPioServerCdfDefVars on server ranks in parallel mode*/
        void (*myCdfDefVars)(stream_t * streamptr) = (void (*)(stream_t *)) namespaceSwitchGet(NSSWITCH_CDF_STREAM_SETUP).func;
        myCdfDefVars(streamptr);
      }
      break;
#endif
#ifdef HAVE_LIBGRIB
      case CDI_FILETYPE_GRIB: gribContainersNew(streamptr); break;
#endif
      default:;
    }
  }
}

void
cdiStreamGetIndexList(unsigned numIDs, int *IDs)
{
  reshGetResHListOfType(numIDs, IDs, &streamOps);
}

int
streamInqNvars(int streamID)
{
  stream_t *s = (stream_t *) reshGetVal(streamID, &streamOps);
  return s->nvars;
}

static int
streamCompareP(void *streamptr1, void *streamptr2)
{
  stream_t *s1 = (stream_t *) streamptr1;
  stream_t *s2 = (stream_t *) streamptr2;
  enum
  {
    differ = -1,
    equal = 0,
  };

  xassert(s1);
  xassert(s2);

  if (s1->filetype != s2->filetype) return differ;
  if (s1->byteorder != s2->byteorder) return differ;
  if (s1->comptype != s2->comptype) return differ;
  if (s1->complevel != s2->complevel) return differ;

  if (s1->filename)
  {
    if (!str_is_equal(s1->filename, s2->filename)) return differ;
  }
  else if (s2->filename)
    return differ;

  return equal;
}

void
streamPrintP(void *streamptr, FILE *fp)
{
  stream_t *sp = (stream_t *) streamptr;

  if (!sp) return;

  fprintf(fp,
          "#\n"
          "# streamID %d\n"
          "#\n"
          "self          = %d\n"
          "accesstype    = %d\n"
          "accessmode    = %d\n"
          "filetype      = %d\n"
          "byteorder     = %d\n"
          "fileID        = %d\n"
          "filemode      = %d\n"
          "filename      = %s\n"
          "nrecs         = %d\n"
          "nvars         = %d\n"
          "varsAllocated = %d\n"
          "curTsID       = %d\n"
          "rtsteps       = %d\n"
          "ntsteps       = %ld\n"
          "tstepsTableSize= %d\n"
          "tstepsNextID  = %d\n"
          "ncmode        = %d\n"
          "vlistID       = %d\n"
          "globalatts    = %d\n"
          "localatts     = %d\n"
          "unreduced     = %d\n"
          "sortname      = %d\n"
          "have_missval  = %d\n"
          "ztype         = %d\n"
          "zlevel        = %d\n",
          sp->self, sp->self, sp->accesstype, sp->accessmode, sp->filetype, sp->byteorder, sp->fileID, sp->filemode, sp->filename,
          sp->nrecs, sp->nvars, sp->varsAllocated, sp->curTsID, sp->rtsteps, sp->ntsteps, sp->tstepsTableSize, sp->tstepsNextID,
          sp->ncmode, sp->vlistID, sp->globalatts, sp->localatts, sp->unreduced, sp->sortname, sp->have_missval, sp->comptype,
          sp->complevel);
}

enum
{
  streamNint = 10,
};

static int
streamGetPackSize(void *voidP, void *context)
{
  stream_t *streamP = (stream_t *) voidP;
  int packBufferSize = serializeGetSize(streamNint, CDI_DATATYPE_INT, context) + serializeGetSize(2, CDI_DATATYPE_UINT32, context)
                       + serializeGetSize((int) strlen(streamP->filename) + 1, CDI_DATATYPE_TXT, context)
                       + serializeGetSize(1, CDI_DATATYPE_FLT64, context);
  return packBufferSize;
}

static void
streamPack(void *streamptr, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  stream_t *streamP = (stream_t *) streamptr;
  int intBuffer[streamNint];

  intBuffer[0] = streamP->self;
  intBuffer[1] = streamP->filetype;
  intBuffer[2] = (int) strlen(streamP->filename) + 1;
  intBuffer[3] = streamP->vlistID;
  intBuffer[4] = streamP->byteorder;
  intBuffer[5] = streamP->comptype;
  intBuffer[6] = streamP->complevel;
  intBuffer[7] = streamP->unreduced;
  intBuffer[8] = streamP->sortname;
  intBuffer[9] = streamP->have_missval;

  serializePack(intBuffer, streamNint, CDI_DATATYPE_INT, packBuffer, packBufferSize, packBufferPos, context);
  uint32_t d = cdiCheckSum(CDI_DATATYPE_INT, streamNint, intBuffer);
  serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);

  serializePack(&CDI_Default_Missval, 1, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
  serializePack(streamP->filename, intBuffer[2], CDI_DATATYPE_TXT, packBuffer, packBufferSize, packBufferPos, context);
  d = cdiCheckSum(CDI_DATATYPE_TXT, intBuffer[2], streamP->filename);
  serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
}

struct streamAssoc
streamUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context)
{
  int intBuffer[streamNint];
  uint32_t d;
  char filename[CDI_MAX_NAME];

  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, intBuffer, streamNint, CDI_DATATYPE_INT, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
  xassert(cdiCheckSum(CDI_DATATYPE_INT, streamNint, intBuffer) == d);

  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &CDI_Default_Missval, 1, CDI_DATATYPE_FLT64, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &filename, intBuffer[2], CDI_DATATYPE_TXT, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
  xassert(d == cdiCheckSum(CDI_DATATYPE_TXT, intBuffer[2], filename));
  int targetStreamID = namespaceAdaptKey(intBuffer[0], originNamespace),
      streamID = streamOpenID(filename, 'w', intBuffer[1], targetStreamID);
  xassert(streamID >= 0 && targetStreamID == streamID);
  streamDefByteorder(streamID, intBuffer[4]);
  streamDefCompType(streamID, intBuffer[5]);
  streamDefCompLevel(streamID, intBuffer[6]);
  stream_t *streamptr = stream_to_pointer(streamID);
  streamptr->unreduced = intBuffer[7];
  streamptr->sortname = intBuffer[8];
  streamptr->have_missval = intBuffer[9];
  struct streamAssoc retval = { streamID, intBuffer[3] };
  return retval;
}

/* *
 * This function does not really close the memio,
 * this has to be done outside cdi to access the memory buffer*/
void
freePtrAfterNCMem(stream_t *streamptr, int recordBufIsToBeDeleted)
{
  (void) recordBufIsToBeDeleted;
  int fileID = streamptr->fileID;

  if (fileID == CDI_UNDEFID)
  {
    Warning("File %s not open!", streamptr->filename);
    return;
  }

  if (streamptr->ntsteps == 0 && streamptr->tsteps != NULL)
  {
    tsteps_t *tstep = &(streamptr->tsteps[0]);
    // clang-format off
    if (tstep->recinfo) { Free(tstep->recinfo); tstep->recinfo = NULL; }
    if (tstep->records) { Free(tstep->records); tstep->records = NULL; }
    if (tstep->recIDs) { Free(tstep->recIDs); tstep->recIDs = NULL; }
    // clang-format on
  }
}

void
streamCloseNCMem(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  bool lockIO = streamptr->lockIO;
  if (lockIO) CDI_IO_LOCK();

  if (CDI_Debug) Message("streamID = %d filename = %s", streamID, streamptr->filename);
  streamDestroyViaDelegate(streamptr, freePtrAfterNCMem);
  reshRemove(streamID, &streamOps);
  if (CDI_Debug) Message("Removed stream %d from stream list", streamID);

  if (lockIO) CDI_IO_UNLOCK();
}

int
streamOpenReadNCMem(int ncidp)
{
  return streamOpenNCMem(ncidp, 'r');
}
int
streamOpenWriteNCMem(int ncidp)
{
  return streamOpenNCMem(ncidp, 'w');
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif


// the single image implementation
int
cdiStreamWriteVar_(int streamID, int varID, int memtype, const void *data, SizeType numMissVals)
{
  // May fail if memtype == MEMTYPE_FLOAT and the file format does not support single precision writing.
  // A value > 0 is returned in this case, otherwise it returns zero.
  int status = 0;

  if (CDI_Debug) Message("streamID = %d varID = %d", streamID, varID);

  check_parg(data);

  stream_t *streamptr = stream_to_pointer(streamID);
  if (subtypeInqActiveIndex(streamptr->vars[varID].subtypeID) != 0) Error("Writing of non-trivial subtypes not yet implemented!");

  // check taxis
  if (streamptr->curTsID == CDI_UNDEFID) streamDefTimestep(streamID, 0);

  const int filetype = streamptr->filetype;

  if (memtype == MEMTYPE_FLOAT && cdiFiletypeIsExse(filetype)) return 1;

  switch (cdiBaseFiletype(filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grb_write_var(streamptr, varID, memtype, data, (size_t) numMissVals); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srvWriteVarDP(streamptr, varID, (double *) data); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: extWriteVarDP(streamptr, varID, (double *) data); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: iegWriteVarDP(streamptr, varID, (double *) data); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_write_var(streamptr, varID, memtype, data, (size_t) numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(filetype));
  }

  return status;
}

/*
@Function  streamWriteVar
@Title     Write a variable

@Prototype void streamWriteVar(int streamID, int varID, const double *data, SizeType numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  varID     Variable identifier.
    @Item  data      Pointer to a block of double precision floating point data values to be written.
    @Item  numMissVals     Number of missing values.

@Description
The function streamWriteVar writes the values of one time step of a variable to an open dataset.
The values are converted to the external data type of the variable, if necessary.
@EndFunction
*/
void
streamWriteVar(int streamID, int varID, const double *data, SizeType numMissVals)
{
  void (*myCdiStreamWriteVar_)(int streamID, int varID, int memtype, const void *data, SizeType numMissVals)
      = (void (*)(int, int, int, const void *, SizeType)) namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_).func;

  myCdiStreamWriteVar_(streamID, varID, MEMTYPE_DOUBLE, (const void *) data, numMissVals);
}

/*
@Function  streamWriteVarF
@Title     Write a variable

@Prototype void streamWriteVarF(int streamID, int varID, const float *data, SizeType numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  varID     Variable identifier.
    @Item  data      Pointer to a block of single precision floating point data values to be written.
    @Item  numMissVals     Number of missing values.

@Description
The function streamWriteVarF writes the values of one time step of a variable to an open dataset.
The values are converted to the external data type of the variable, if necessary.
@EndFunction
*/
void
streamWriteVarF(int streamID, int varID, const float *data, SizeType numMissVals)
{
  int (*myCdiStreamWriteVar_)(int streamID, int varID, int memtype, const void *data, SizeType numMissVals)
      = (int (*)(int, int, int, const void *, SizeType)) namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_).func;

  if (myCdiStreamWriteVar_(streamID, varID, MEMTYPE_FLOAT, (const void *) data, numMissVals))
  {
    // In case the file format does not support single precision writing,
    // we fall back to double precision writing, converting the data on the fly.
    const int vlistID = streamInqVlist(streamID);
    SizeType elementCount = gridInqSize(vlistInqVarGrid(vlistID, varID));
    elementCount *= (SizeType) zaxisInqSize(vlistInqVarZaxis(vlistID, varID));
    double *conversionBuffer = (double *) Malloc((size_t) elementCount * sizeof(*conversionBuffer));
    for (SizeType i = elementCount; i--;) conversionBuffer[i] = data[i];
    myCdiStreamWriteVar_(streamID, varID, MEMTYPE_DOUBLE, (const void *) conversionBuffer, numMissVals);
    Free(conversionBuffer);
  }
}

static int
cdiStreamWriteVarSlice(int streamID, int varID, int levelID, int memtype, const void *data, SizeType numMissVals)
{
  // May fail if memtype == MEMTYPE_FLOAT and the file format does not support single precision writing.
  // A value > 0 is returned in this case, otherwise it returns zero.
  int status = 0;

  if (CDI_Debug) Message("streamID = %d varID = %d", streamID, varID);

  check_parg(data);

  stream_t *streamptr = stream_to_pointer(streamID);
  if (subtypeInqActiveIndex(streamptr->vars[varID].subtypeID) != 0) Error("Writing of non-trivial subtypes not yet implemented!");

  // check taxis
  if (streamptr->curTsID == CDI_UNDEFID) streamDefTimestep(streamID, 0);

  const int filetype = streamptr->filetype;

  if (memtype == MEMTYPE_FLOAT && cdiFiletypeIsExse(filetype)) return 1;

  switch (cdiBaseFiletype(filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grb_write_var_slice(streamptr, varID, levelID, memtype, data, (size_t) numMissVals); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srvWriteVarSliceDP(streamptr, varID, levelID, (double *) data); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: extWriteVarSliceDP(streamptr, varID, levelID, (double *) data); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: iegWriteVarSliceDP(streamptr, varID, levelID, (double *) data); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_write_var_slice(streamptr, varID, levelID, memtype, data, (size_t) numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(filetype));
  }

  return status;
}

/*
@Function  streamWriteVarSlice
@Title     Write a horizontal slice of a variable

@Prototype void streamWriteVarSlice(int streamID, int varID, int levelID, const double *data, SizeType numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  varID     Variable identifier.
    @Item  levelID   Level identifier.
    @Item  data      Pointer to a block of double precision floating point data values to be written.
    @Item  numMissVals     Number of missing values.

@Description
The function streamWriteVarSlice writes the values of a horizontal slice of a variable to an open dataset.
The values are converted to the external data type of the variable, if necessary.
@EndFunction
*/
void
streamWriteVarSlice(int streamID, int varID, int levelID, const double *data, SizeType numMissVals)
{
  cdiStreamWriteVarSlice(streamID, varID, levelID, MEMTYPE_DOUBLE, (const void *) data, numMissVals);
}

/*
@Function  streamWriteVarSliceF
@Title     Write a horizontal slice of a variable

@Prototype void streamWriteVarSliceF(int streamID, int varID, int levelID, const float *data, SizeType numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  varID     Variable identifier.
    @Item  levelID   Level identifier.
    @Item  data      Pointer to a block of single precision floating point data values to be written.
    @Item  numMissVals     Number of missing values.

@Description
The function streamWriteVarSliceF writes the values of a horizontal slice of a variable to an open dataset.
The values are converted to the external data type of the variable, if necessary.
@EndFunction
*/
void
streamWriteVarSliceF(int streamID, int varID, int levelID, const float *data, SizeType numMissVals)
{
  if (cdiStreamWriteVarSlice(streamID, varID, levelID, MEMTYPE_FLOAT, (const void *) data, numMissVals))
  {
    // In case the file format does not support single precision writing,
    // we fall back to double precision writing, converting the data on the fly.
    const SizeType elementCount = gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
    double *conversionBuffer = (double *) Malloc((size_t) elementCount * sizeof(*conversionBuffer));
    for (SizeType i = elementCount; i--;) conversionBuffer[i] = (double) data[i];
    streamWriteVarSlice(streamID, varID, levelID, conversionBuffer, numMissVals);
    Free(conversionBuffer);
  }
}

void
streamWriteVarChunk(int streamID, int varID, const int rect[][2], const double *data, SizeType numMissVals)
{
  void (*myCdiStreamWriteVarChunk_)(int streamID, int varID, int memtype, const int rect[3][2], const void *data,
                                    SizeType numMissVals)
      = (void (*)(int, int, int, const int[3][2], const void *, SizeType)) namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_CHUNK_)
            .func;
  myCdiStreamWriteVarChunk_(streamID, varID, MEMTYPE_DOUBLE, rect, data, numMissVals);
}

void
streamWriteVarChunkF(int streamID, int varID, const int rect[][2], const float *data, SizeType numMissVals)
{
  void (*myCdiStreamWriteVarChunk_)(int streamID, int varID, int memtype, const int rect[3][2], const void *data,
                                    SizeType numMissVals)
      = (void (*)(int, int, int, const int[3][2], const void *, SizeType)) namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_CHUNK_)
            .func;
  myCdiStreamWriteVarChunk_(streamID, varID, MEMTYPE_FLOAT, rect, data, numMissVals);
}

// single image implementation
void
cdiStreamWriteVarChunk_(int streamID, int varID, int memtype, const int rect[][2], const void *data, SizeType numMissVals)
{
  if (CDI_Debug) Message("streamID = %d varID = %d", streamID, varID);

  stream_t *streamptr = stream_to_pointer(streamID);

  // streamDefineTaxis(streamID);

  const int filetype = streamptr->filetype;

  switch (cdiBaseFiletype(filetype))
  {
#if defined(HAVE_LIBGRIB)
    case CDI_FILETYPE_GRIB:
#endif
#if defined(HAVE_LIBSERVICE)
    case CDI_FILETYPE_SRV:
#endif
#if defined(HAVE_LIBEXTRA)
    case CDI_FILETYPE_EXT:
#endif
#if defined(HAVE_LIBIEG)
    case CDI_FILETYPE_IEG:
#endif
#if defined(HAVE_LIBGRIB) || defined(HAVE_LIBSERVICE) || defined(HAVE_LIBEXTRA) || defined(HAVE_LIBIEG)
      xabort("streamWriteVarChunk not implemented for filetype %s!", strfiletype(filetype));
      break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_write_var_chunk(streamptr, varID, memtype, rect, data, (size_t) numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(filetype)); break;
  }
}

static void
stream_write_record(int streamID, int memtype, const void *data, SizeType numMissVals)
{
  check_parg(data);

  stream_t *streamptr = stream_to_pointer(streamID);

  if (streamptr->lockIO) CDI_IO_LOCK();

  switch (cdiBaseFiletype(streamptr->filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grb_write_field(streamptr, memtype, data, (size_t) numMissVals); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srv_write_field(streamptr, memtype, data); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: ext_write_field(streamptr, memtype, data); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: ieg_write_field(streamptr, memtype, data); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_write_field(streamptr, memtype, data, (size_t) numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(streamptr->filetype));
  }

  if (streamptr->lockIO) CDI_IO_UNLOCK();
}

/*
@Function  streamWriteRecord
@Title     Write a horizontal slice of a variable

@Prototype void streamWriteRecord(int streamID, const double *data, SizeType numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  data      Pointer to a block of double precision floating point data values to be written.
    @Item  numMissVals     Number of missing values.

@Description
The function streamWriteRecord writes the values of a horizontal slice (record) of a variable to an open dataset.
The values are converted to the external data type of the variable, if necessary.
@EndFunction
*/
void
streamWriteField(int streamID, const double *data, SizeType numMissVals)
{
  stream_write_record(streamID, MEMTYPE_DOUBLE, (const void *) data, numMissVals);
}

void
streamWriteFieldF(int streamID, const float *data, SizeType numMissVals)
{
  stream_write_record(streamID, MEMTYPE_FLOAT, (const void *) data, numMissVals);
}
#ifdef HAVE_CONFIG_H
#endif


// the single image implementation
static int
cdiStreamReadVar(int streamID, int varID, int memType, void *data, size_t *numMissVals)
{
  // May fail if memType == MEMTYPE_FLOAT and the file format does not support single precision reading.
  // A value > 0 is returned in this case, otherwise it returns zero.
  int status = 0;

  if (CDI_Debug) Message("streamID = %d  varID = %d", streamID, varID);

  check_parg(data);
  check_parg(numMissVals);

  stream_t *streamPtr = stream_to_pointer(streamID);
  const int fileType = streamPtr->filetype;

  *numMissVals = 0;

  if (memType == MEMTYPE_FLOAT && cdiFiletypeIsExse(fileType)) return 1;

  switch (cdiBaseFiletype(fileType))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grb_read_var(streamPtr, varID, memType, data, numMissVals); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srvReadVarDP(streamPtr, varID, (double *) data, numMissVals); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: extReadVarDP(streamPtr, varID, (double *) data, numMissVals); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: iegReadVarDP(streamPtr, varID, (double *) data, numMissVals); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_read_var(streamPtr, varID, memType, data, numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(fileType));
  }

  return status;
}

/*
@Function  streamReadVar
@Title     Read a variable

@Prototype void streamReadVar(int streamID, int varID, double *data, SizeType *numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead}.
    @Item  varID     Variable identifier.
    @Item  data      Pointer to the location into which the data values are read.
                     The caller must allocate space for the returned values.
    @Item  numMissVals     Number of missing values.

@Description
The function streamReadVar reads all the values of one time step of a variable
from an open dataset.
@EndFunction
*/
void
streamReadVar(int streamID, int varID, double *data, SizeType *numMissVals)
{
  size_t numMiss = 0;
  cdiStreamReadVar(streamID, varID, MEMTYPE_DOUBLE, data, &numMiss);
  *numMissVals = (SizeType) numMiss;
}

/*
@Function  streamReadVarF
@Title     Read a variable

@Prototype void streamReadVar(int streamID, int varID, float *data, SizeType *numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead}.
    @Item  varID     Variable identifier.
    @Item  data      Pointer to the location into which the data values are read.
                     The caller must allocate space for the returned values.
    @Item  numMissVals     Number of missing values.

@Description
The function streamReadVar reads all the values of one time step of a variable
from an open dataset.
@EndFunction
*/
void
streamReadVarF(int streamID, int varID, float *data, SizeType *numMissVals)
{
  size_t numMiss = 0;
  if (cdiStreamReadVar(streamID, varID, MEMTYPE_FLOAT, data, &numMiss))
  {
    // In case the file format does not support single precision reading,
    // we fall back to double precision reading, converting the data on the fly.
    size_t elementCount = (size_t) gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
    elementCount *= (size_t) zaxisInqSize(vlistInqVarZaxis(streamInqVlist(streamID), varID));
    double *conversionBuffer = (double *) Malloc(elementCount * sizeof(*conversionBuffer));
    streamReadVar(streamID, varID, conversionBuffer, numMissVals);
    for (size_t i = elementCount; i--;) data[i] = (float) conversionBuffer[i];
    Free(conversionBuffer);
  }
  *numMissVals = (SizeType) numMiss;
}

static int
cdiStreamReadVarSlice(int streamID, int varID, int levelID, int memType, void *data, size_t *numMissVals)
{
  // May fail if memType == MEMTYPE_FLOAT and the file format does not support single precision reading.
  // A value > 0 is returned in this case, otherwise it returns zero.
  int status = 0;

  if (CDI_Debug) Message("streamID = %d  varID = %d", streamID, varID);

  check_parg(data);
  check_parg(numMissVals);

  stream_t *streamPtr = stream_to_pointer(streamID);
  const int fileType = streamPtr->filetype;

  *numMissVals = 0;

  if (memType == MEMTYPE_FLOAT && cdiFiletypeIsExse(fileType)) return 1;

  switch (cdiBaseFiletype(fileType))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grb_read_var_slice(streamPtr, varID, levelID, memType, data, numMissVals); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srvReadVarSliceDP(streamPtr, varID, levelID, (double *) data, numMissVals); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: extReadVarSliceDP(streamPtr, varID, levelID, (double *) data, numMissVals); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: iegReadVarSliceDP(streamPtr, varID, levelID, (double *) data, numMissVals); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_read_var_slice(streamPtr, varID, levelID, memType, data, numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(fileType));
  }

  return status;
}

/*
@Function  streamReadVarSlice
@Title     Read a horizontal slice of a variable

@Prototype void streamReadVarSlice(int streamID, int varID, int levelID, double *data, SizeType *numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead}.
    @Item  varID     Variable identifier.
    @Item  levelID   Level identifier.
    @Item  data      Pointer to the location into which the data values are read.
                     The caller must allocate space for the returned values.
    @Item  numMissVals     Number of missing values.

@Description
The function streamReadVarSlice reads all the values of a horizontal slice of a variable
from an open dataset.
@EndFunction
*/
void
streamReadVarSlice(int streamID, int varID, int levelID, double *data, SizeType *numMissVals)
{
  size_t numMiss = 0;
  if (cdiStreamReadVarSlice(streamID, varID, levelID, MEMTYPE_DOUBLE, data, &numMiss))
  {
    Warning("Unexpected error returned from cdiStreamReadVarSlice()!");
    size_t elementCount = (size_t) gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
    memset(data, 0, elementCount * sizeof(*data));
  }
  *numMissVals = (SizeType) numMiss;
}

/*
@Function  streamReadVarSliceF
@Title     Read a horizontal slice of a variable

@Prototype void streamReadVarSliceF(int streamID, int varID, int levelID, float *data, SizeType *numMissVals)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenRead}.
    @Item  varID     Variable identifier.
    @Item  levelID   Level identifier.
    @Item  data      Pointer to the location into which the data values are read.
                     The caller must allocate space for the returned values.
    @Item  numMissVals     Number of missing values.

@Description
The function streamReadVarSliceF reads all the values of a horizontal slice of a variable
from an open dataset.
@EndFunction
*/
void
streamReadVarSliceF(int streamID, int varID, int levelID, float *data, SizeType *numMissVals)
{
  size_t numMiss = 0;
  if (cdiStreamReadVarSlice(streamID, varID, levelID, MEMTYPE_FLOAT, data, &numMiss))
  {
    // In case the file format does not support single precision reading,
    // we fall back to double precision reading, converting the data on the fly.
    size_t elementCount = (size_t) gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
    double *conversionBuffer = (double *) Malloc(elementCount * sizeof(*conversionBuffer));
    streamReadVarSlice(streamID, varID, levelID, conversionBuffer, numMissVals);
    for (size_t i = elementCount; i--;) data[i] = (float) conversionBuffer[i];
    Free(conversionBuffer);
  }
  *numMissVals = (SizeType) numMiss;
}

static void
stream_read_record(int streamID, int memType, void *data, size_t *numMissVals)
{
  check_parg(data);
  check_parg(numMissVals);

  stream_t *streamPtr = stream_to_pointer(streamID);

  if (streamPtr->lockIO) CDI_IO_LOCK();

  *numMissVals = 0;

  switch (cdiBaseFiletype(streamPtr->filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grb_read_field(streamPtr, memType, data, numMissVals); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srv_read_field(streamPtr, memType, data, numMissVals); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: ext_read_field(streamPtr, memType, data, numMissVals); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: ieg_read_field(streamPtr, memType, data, numMissVals); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdf_read_field(streamPtr, memType, data, numMissVals); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(streamPtr->filetype));
  }

  if (streamPtr->lockIO) CDI_IO_UNLOCK();
}

void
streamReadField(int streamID, double *data, SizeType *numMissVals)
{
  size_t numMiss = 0;
  stream_read_record(streamID, MEMTYPE_DOUBLE, (void *) data, &numMiss);
  *numMissVals = (SizeType) numMiss;
}

void
streamReadFieldF(int streamID, float *data, SizeType *numMissVals)
{
  size_t numMiss = 0;
  stream_read_record(streamID, MEMTYPE_FLOAT, (void *) data, &numMiss);
  *numMissVals = (SizeType) numMiss;
}

static int
cdiStreamReadVarSlicePart(int streamID, int varID, int levelID, int varType, int start, size_t size, int memType, void *data,
                          size_t *numMissVals)
{
  int status = 0;

  if (CDI_Debug) Message("streamID = %d  varID = %d", streamID, varID);

  check_parg(data);
  check_parg(numMissVals);

  stream_t *streamPtr = stream_to_pointer(streamID);
  int fileType = streamPtr->filetype;

  *numMissVals = 0;

  // currently we only care for netcdf data
  switch (cdiBaseFiletype(fileType))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB:
    {
      grb_read_var_slice(streamPtr, varID, levelID, memType, data, numMissVals);
      break;
    }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
    {
      cdf_read_var_slice_part(streamPtr, varID, levelID, memType, varType, start, size, data, numMissVals);
      break;
    }
#endif
    default:
    {
      Error("%s support not compiled in!", strfiletype(fileType));
      status = 2;
      break;
    }
  }

  return status;
}

static void
cdiStreamReadVarPart(int streamID, int varID, int varType, int start, size_t size, int memType, void *data, size_t *numMissVals)
{
  (void) (varType);
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamID, varID);

  check_parg(data);
  check_parg(numMissVals);

  stream_t *streamPtr = stream_to_pointer(streamID);
  int fileType = streamPtr->filetype;

  *numMissVals = 0;

  // currently we only care for netcdf data
  switch (cdiBaseFiletype(fileType))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB:
    {
      grb_read_var(streamPtr, varID, memType, data, numMissVals);
      break;
    }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
    {
      cdf_read_var_part(streamPtr, varID, memType, varType, start, size, data, numMissVals);
      break;
    }
#endif
    default:
    {
      Error("%s support not compiled in!", strfiletype(fileType));
      break;
    }
  }
}

void
streamReadVarSlicePart(int streamID, int varID, int levelID, int varType, int start, SizeType size, void *data,
                       SizeType *numMissVals, int memMype)
{
  size_t numMiss = 0;
  if (cdiStreamReadVarSlicePart(streamID, varID, levelID, varType, start, (size_t) size, memMype, data, &numMiss))
  {
    Error("Unexpected error returned from cdiStreamReadVarSlicePart()!");
  }
  *numMissVals = (SizeType) numMiss;
}

void
streamReadVarPart(int streamID, int varID, int varType, int start, SizeType size, void *data, SizeType *numMissVals, int memType)
{
  size_t numMiss = 0;
  cdiStreamReadVarPart(streamID, varID, varType, start, (size_t) size, memType, data, &numMiss);
  *numMissVals = (SizeType) numMiss;
}
#ifndef VARSCAN_H
#define VARSCAN_H

#ifndef GRID_H
#endif

void varAddRecord(int recID, int param, int gridID, int zaxistype, int lbounds, int level1, int level2, int level_sf,
                  int level_unit, int prec, int *pvarID, int *plevelID, int tsteptype, int ltype1, int ltype2, const char *name,
                  const VarScanKeys *scanKeys, const var_tile_t *tiles, int *tile_index);

void varDefVCT(size_t vctsize, double *vctptr);
void varDefZAxisReference(int nlev, int nvgrid, unsigned char uuid[CDI_UUID_SIZE]);

int varDefZaxis(int vlistID, int zaxistype, int nlevels, const double *levels, const char **cvals, size_t clength,
                const double *levels1, const double *levels2, int vctsize, const double *vct, char *name, const char *longname,
                const char *units, int prec, int mode, int ltype1, int ltype2);

void varDefMissval(int varID, double missval);
void varDefCompType(int varID, int comptype);
void varDefCompLevel(int varID, int complevel);
void varDefInst(int varID, int instID);
int varInqInst(int varID);
void varDefModel(int varID, int modelID);
int varInqModel(int varID);
void varDefTable(int varID, int tableID);
int varInqTable(int varID);

void varDefKeyInt(int varID, int key, int value);
void varDefKeyBytes(int varID, int key, const unsigned char *bytes, int length);
void varDefKeyString(int varID, int key, const char *string);

void varDefOptGribInt(int varID, int tile_index, long lval, const char *keyword);
void varDefOptGribDbl(int varID, int tile_index, double dval, const char *keyword);
void varDefOptGribIntArr(int varID, int tile_index, const long *larr, size_t arr_len, const char *keyword);
void varDefOptGribDblArr(int varID, int tile_index, const double *darr, size_t arr_len, const char *keyword);
int varOptGribNentries(int varID);

bool zaxis_compare(int zaxisID, int zaxistype, int nlevels, const double *levels, const double *lbounds, const double *ubounds,
                   const char *longname, const char *units, int ltype1, int ltype2);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef CDF_FILTER_H
#define CDF_FILTER_H

#include <stdio.h>

int cdf_get_var_filter(int ncid, int varid, char *filterSpec, size_t maxLength);

#endif
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF

#include <ctype.h>
#include <limits.h>


// On Windows, define strcasecmp manually
#ifdef _WIN32
#define strcasecmp _stricmp
#else
#include <strings.h>
#endif

enum VarStatus
{
  UndefVar = -1,
  CoordVar = 0,
  DataVar = 1,
};

enum AxisType
{
  X_AXIS = 1,
  Y_AXIS = 2,
  Z_AXIS = 3,
  E_AXIS = 4,
  T_AXIS = 5,
};

static int axisTypeChar[] = { '?', 'X', 'Y', 'Z', 'E', 'T' };

typedef struct
{
  size_t len;   // Dimension size
  int dimid;    // NetCDF dim ID
  int ncvarid;  // NetCDF var ID
  int dimtype;  // AxisType
  char name[CDI_MAX_NAME];
} ncdim_t;

#define MAX_COORDVARS 5
#define MAX_AUXVARS 4
#define MAX_DIMS_CDF 8

typedef struct
{
  int cdiVarID;
  int ncid;
  int varStatus;
  bool ignoreVar;
  bool isLonLatMapping;
  bool isHealpixMapping;
  bool isCubeSphere;
  bool isCharAxis;
  bool isIndexAxis;
  bool isXaxis;
  bool isYaxis;
  bool isZaxis;
  bool isTaxis;
  bool isLon;
  bool isLat;
  bool isClimatology;
  bool hasCalendar;
  bool hasFormulaterms;
  bool printWarning;
  int timetype;
  int param;
  int code;
  int tabnum;
  int bounds;
  int gridID;
  int zaxisID;
  int gridtype;
  int zaxistype;
  int xdim;
  int ydim;
  int zdim;
  int xvarid;
  int yvarid;
  int rpvarid;
  int zvarid;
  int tvarid;
  int ivarid;
  int psvarid;
  int p0varid;
  int ncoordvars;
  int cvarids[MAX_COORDVARS];
  int coordvarids[MAX_COORDVARS];
  int auxvarids[MAX_AUXVARS];
  int nauxvars;
  int cellarea;
  int tableID;
  int truncation;
  int position;
  int numLPE;
  bool missvalDefined;
  bool fillvalDefined;
  int xtype;
  int gmapid;
  int positive;
  int ndims;
  int dimids[MAX_DIMS_CDF];    // Netcdf dimension IDs
  int dimtypes[MAX_DIMS_CDF];  // AxisType
  size_t chunks[MAX_DIMS_CDF];
  size_t xstart;
  size_t xcount;
  size_t zstart;
  size_t zcount;
  size_t tstart;
  size_t tcount;
  bool isChunked;
  int chunkType;
  int chunkSize;
  size_t chunkCacheSize;
  size_t chunkCacheNelems;
  float chunkCachePreemption;
  size_t gridSize;
  size_t xSize;
  size_t ySize;
  size_t zSize;
  int nattsNC;
  int natts;
  int *atts;
  size_t vctsize;
  double *vct;
  double missval;
  double fillval;
  double addoffset;
  double scalefactor;
  bool hasFilter;
  bool hasDeflate;
  bool hasSzip;
  bool isUnsigned;
  bool validrangeDefined;
  double validrange[2];
  int typeOfEnsembleForecast;
  int numberOfForecastsInEnsemble;
  int perturbationNumber;
  int unitsLen;
  char name[CDI_MAX_NAME];
  char longname[CDI_MAX_NAME];
  char stdname[CDI_MAX_NAME];
  char units[CDI_MAX_NAME];
  char filterSpec[CDI_MAX_NAME];
} ncvar_t;

typedef struct
{
  char gridfile[8912];
  unsigned char uuid[CDI_UUID_SIZE];
  int number_of_grid_used;
  int timedimid;
} GridInfo;

static CdiDateTime
scan_time_string(const char *ptu)
{
  int year = 0, month = 0, day = 0;
  int hour = 0, minute = 0;
  double fseconds = 0.0;
  char ch = ' ';
  if (*ptu) sscanf(ptu, "%d-%d-%d%c%d:%d:%lf", &year, &month, &day, &ch, &hour, &minute, &fseconds);

  if (day > 999 && year < 32)
  {
    int tmp = year;
    year = day;
    day = tmp;
  }

  int second = (int) fseconds;
  double aseconds;
  double ms = modf(fseconds, &aseconds) * 1000;
  assert((int) aseconds == second);
  CdiDateTime datetime;
  datetime.date.year = year;
  datetime.date.month = (short) month;
  datetime.date.day = (short) day;
  datetime.time.hour = (short) hour;
  datetime.time.minute = (short) minute;
  datetime.time.second = (short) second;
  datetime.time.ms = (short) ms;

  return datetime;
}

static int
scan_time_units(const char *unitstr)
{
  int timeunit = get_time_units(strlen(unitstr), unitstr);
  if (timeunit == -1) Warning("Unsupported TIMEUNIT: %s!", unitstr);
  return timeunit;
}

static int
set_base_time(const char *timeUnitsStr, taxis_t *taxis)
{
  int taxisType = TAXIS_ABSOLUTE;

  size_t len = strlen(timeUnitsStr);
  while (isspace(*timeUnitsStr) && len)
  {
    timeUnitsStr++;
    len--;
  }
  char tmp[32], *tu = tmp;
  if (len + 1 > sizeof(tmp)) tu = (char *) Malloc(len + 1);

  for (size_t i = 0; i < len; i++) tu[i] = (char) tolower((int) timeUnitsStr[i]);
  tu[len] = 0;

  int timeUnits = get_time_units(len, tu);
  if (timeUnits == -1)
  {
    Warning("Unsupported TIMEUNIT: %s!", timeUnitsStr);
    if (tu != tmp) Free(tu);
    return 1;
  }

  size_t pos = 0;
  while (pos < len && !isspace(tu[pos])) ++pos;
  if (tu[pos])
  {
    while (isspace(tu[pos])) ++pos;

    if (strStartsWith(tu + pos, "since")) taxisType = TAXIS_RELATIVE;

    while (pos < len && !isspace(tu[pos])) ++pos;
    if (tu[pos])
    {
      while (isspace(tu[pos])) ++pos;

      if (taxisType == TAXIS_ABSOLUTE)
      {
        if (timeUnits == TUNIT_DAY)
        {
          if (!strStartsWith(tu + pos, "%y%m%d.%f"))
          {
            Warning("Unsupported format %s for TIMEUNIT day!", tu + pos);
            timeUnits = -1;
          }
        }
        else if (timeUnits == TUNIT_MONTH)
        {
          if (!strStartsWith(tu + pos, "%y%m.%f"))
          {
            Warning("Unsupported format %s for TIMEUNIT month!", tu + pos);
            timeUnits = -1;
          }
        }
        else if (timeUnits == TUNIT_YEAR)
        {
          if (!strStartsWith(tu + pos, "%y.%f"))
          {
            Warning("Unsupported format %s for TIMEUNIT year!", tu + pos);
            timeUnits = -1;
          }
        }
        else { Warning("Unsupported format for time units: %s!", tu); }
      }
      else if (taxisType == TAXIS_RELATIVE)
      {
        taxis->rDateTime = scan_time_string(tu + pos);
        if (CDI_Debug)
          Message("rdate = %d  rtime = %d", (int) cdiDate_get(taxis->rDateTime.date), cdiTime_get(taxis->rDateTime.time));
      }
    }
  }

  taxis->type = taxisType;
  taxis->unit = timeUnits;

  if (tu != tmp) Free(tu);

  if (CDI_Debug) Message("taxisType = %d  timeUnits = %d", taxisType, timeUnits);

  return 0;
}

bool
xtypeIsText(int xtype)
{
  bool isText = (xtype == NC_CHAR) || (xtype == NC_STRING);
  return isText;
}

static bool
xtypeIsFloat(nc_type xtype)
{
  return (xtype == NC_FLOAT || xtype == NC_DOUBLE);
}

static bool
xtypeIsInt(nc_type xtype)
{
  bool isInt
      = xtype == NC_SHORT || xtype == NC_INT || xtype == NC_BYTE || xtype == NC_USHORT || xtype == NC_UINT || xtype == NC_UBYTE;
  return isInt;
}

static bool
xtypeIsInt64(nc_type xtype)
{
  return (xtype == NC_INT64 || xtype == NC_UINT64);
}

static int
cdfInqDatatype(stream_t *streamptr, int xtype, bool isUnsigned)
{
  int datatype = -1;

  if (xtype == NC_BYTE && isUnsigned) xtype = NC_UBYTE;

  // clang-format off
  if      (xtype == NC_BYTE  )  datatype = CDI_DATATYPE_INT8;
  else if (xtype == NC_CHAR  )  datatype = CDI_DATATYPE_UINT8;
  else if (xtype == NC_SHORT )  datatype = CDI_DATATYPE_INT16;
  else if (xtype == NC_INT   )  datatype = CDI_DATATYPE_INT32;
  else if (xtype == NC_FLOAT )  datatype = CDI_DATATYPE_FLT32;
  else if (xtype == NC_DOUBLE)  datatype = CDI_DATATYPE_FLT64;
  else if (xtype == NC_UBYTE )  datatype = CDI_DATATYPE_UINT8;
  else if (xtype == NC_LONG  )  datatype = CDI_DATATYPE_INT32;
  else if (xtype == NC_USHORT)  datatype = CDI_DATATYPE_UINT16;
  else if (xtype == NC_UINT  )  datatype = CDI_DATATYPE_UINT32;
  else if (xtype == NC_INT64 )  datatype = CDI_DATATYPE_FLT64;
  else if (xtype == NC_UINT64)  datatype = CDI_DATATYPE_FLT64;
  else
  {
    CdfInfo *cdfInfo = &(streamptr->cdfInfo);
    if (xtype != cdfInfo->complexFloatId && xtype != cdfInfo->complexDoubleId)
    {
      bool isUserDefinedType = false;
#ifdef NC_FIRSTUSERTYPEID
      isUserDefinedType = (xtype >= NC_FIRSTUSERTYPEID);
#endif
      if (isUserDefinedType)
      {
        int fileID = streamptr->fileID;
        size_t nfields = 0, compoundsize = 0;
        int status = nc_inq_compound(fileID, xtype, NULL, &compoundsize, &nfields);
        if (status == NC_NOERR && nfields == 2 && (compoundsize == 8 || compoundsize == 16))
        {
          nc_type field_type1 = -1, field_type2 = -1;
          int field_dims1 = 0, field_dims2 = 0;
          nc_inq_compound_field(fileID, xtype, 0, NULL, NULL, &field_type1, &field_dims1, NULL);
          nc_inq_compound_field(fileID, xtype, 1, NULL, NULL, &field_type2, &field_dims2, NULL);
          if (field_type1 == field_type2 && field_dims1 == 0 && field_dims2 == 0)
          {
            if      (field_type1 == NC_FLOAT)  cdfInfo->complexFloatId = xtype;
            else if (field_type1 == NC_DOUBLE) cdfInfo->complexDoubleId = xtype;
          }
        }
      }
    }
    if      (xtype == cdfInfo->complexFloatId )  datatype = CDI_DATATYPE_CPX32;
    else if (xtype == cdfInfo->complexDoubleId)  datatype = CDI_DATATYPE_CPX64;
  }
  // clang-format on

  return datatype;
}

static void
cdfGetAttInt(int fileID, int ncvarid, const char *attname, size_t attlen, int *attint)
{
  *attint = 0;

  nc_type atttype;
  size_t nc_attlen;
  cdf_inq_atttype(fileID, ncvarid, attname, &atttype);
  cdf_inq_attlen(fileID, ncvarid, attname, &nc_attlen);

  if (xtypeIsFloat(atttype) || xtypeIsInt(atttype))
  {
    bool needAlloc = (nc_attlen > attlen);
    int *pintatt = needAlloc ? (int *) Malloc(nc_attlen * sizeof(int)) : attint;
    cdf_get_att_int(fileID, ncvarid, attname, pintatt);
    if (needAlloc)
    {
      memcpy(attint, pintatt, attlen * sizeof(int));
      Free(pintatt);
    }
  }
}

static void
cdfGetAttInt64(int fileID, int ncvarid, const char *attname, size_t attlen, int64_t *attint)
{
  *attint = 0;

  nc_type atttype;
  size_t nc_attlen;
  cdf_inq_atttype(fileID, ncvarid, attname, &atttype);
  cdf_inq_attlen(fileID, ncvarid, attname, &nc_attlen);

  if (xtypeIsFloat(atttype) || xtypeIsInt(atttype) || xtypeIsInt64(atttype))
  {
    long long *plongatt = (long long *) Malloc(nc_attlen * sizeof(long long));
    cdf_get_att_longlong(fileID, ncvarid, attname, plongatt);
    for (size_t i = 0; i < attlen; ++i) attint[i] = plongatt[i];
    Free(plongatt);
  }
}

static void
cdfGetAttDouble(int fileID, int ncvarid, char *attname, size_t attlen, double *attdouble)
{
  *attdouble = 0.0;

  nc_type atttype;
  size_t nc_attlen;
  cdf_inq_atttype(fileID, ncvarid, attname, &atttype);
  cdf_inq_attlen(fileID, ncvarid, attname, &nc_attlen);

  if (xtypeIsFloat(atttype) || xtypeIsInt(atttype))
  {
    bool needAlloc = (nc_attlen > attlen);
    double *pdoubleatt = needAlloc ? (double *) Malloc(nc_attlen * sizeof(double)) : attdouble;
    cdf_get_att_double(fileID, ncvarid, attname, pdoubleatt);
    if (needAlloc)
    {
      memcpy(attdouble, pdoubleatt, attlen * sizeof(double));
      Free(pdoubleatt);
    }
  }
}

static bool
cdfCheckAttInt(int fileID, int ncvarid, const char *attname)
{
  nc_type atttype;
  int status_nc = nc_inq_atttype(fileID, ncvarid, attname, &atttype);
  return (status_nc == NC_NOERR && (xtypeIsInt(atttype) || xtypeIsInt64(atttype)));
}

static bool
cdfCheckAttText(int fileID, int ncvarid, const char *attname)
{
  nc_type atttype;
  int status_nc = nc_inq_atttype(fileID, ncvarid, attname, &atttype);
  return (status_nc == NC_NOERR && (atttype == NC_CHAR || atttype == NC_STRING));
}

static void
cdfGetAttText(int fileID, int ncvarid, const char *attname, size_t attlen, char *atttext)
{
  atttext[0] = 0;

  nc_type atttype;
  size_t nc_attlen;
  cdf_inq_atttype(fileID, ncvarid, attname, &atttype);
  cdf_inq_attlen(fileID, ncvarid, attname, &nc_attlen);

  if (atttype == NC_CHAR)
  {
    char attbuf[65636];
    if (nc_attlen < sizeof(attbuf))
    {
      cdf_get_att_text(fileID, ncvarid, attname, attbuf);

      if (nc_attlen > (attlen - 1)) nc_attlen = (attlen - 1);

      attbuf[nc_attlen++] = 0;
      memcpy(atttext, attbuf, nc_attlen);
    }
  }
  else if (atttype == NC_STRING)
  {
    if (nc_attlen == 1)
    {
      char *attbuf = NULL;
      cdf_get_att_string(fileID, ncvarid, attname, &attbuf);

      size_t ssize = strlen(attbuf) + 1;
      if (ssize > attlen) ssize = attlen;
      memcpy(atttext, attbuf, ssize);
      atttext[ssize - 1] = 0;
      Free(attbuf);
    }
  }
}

void
cdf_scale_add(size_t size, double *data, double addoffset, double scalefactor)
{
  bool haveAddoffset = IS_NOT_EQUAL(addoffset, 0.0);
  bool haveScalefactor = IS_NOT_EQUAL(scalefactor, 1.0);

  if (haveAddoffset && haveScalefactor)
  {
    for (size_t i = 0; i < size; ++i) data[i] = data[i] * scalefactor + addoffset;
  }
  else if (haveScalefactor)
  {
    for (size_t i = 0; i < size; ++i) data[i] *= scalefactor;
  }
  else if (haveAddoffset)
  {
    for (size_t i = 0; i < size; ++i) data[i] += addoffset;
  }
}

static int
cdf_time_dimid(int fileID, int ndims, ncdim_t *ncdims, int nvars, ncvar_t *ncvars)
{
  char dimname[CDI_MAX_NAME];
  for (int dimid = 0; dimid < ndims; ++dimid)
  {
    strcpy(dimname, ncdims[dimid].name);
    if (str_is_equal("time", str_to_lower(dimname))) return dimid;
  }

  bool check_dimids[MAX_DIMS_CDF];
  for (int i = 0; i < MAX_DIMS_CDF; ++i) check_dimids[i] = false;

  for (int varid = 0; varid < nvars; ++varid)
  {
    ncvar_t *ncvar = &ncvars[varid];
    if (ncvars[varid].ndims == 1)
    {
      int dimid0 = CDI_UNDEFID;
      for (int gdimid = 0; gdimid < ndims; ++gdimid)
        if (ncdims[gdimid].dimid == ncvar->dimids[0])
        {
          dimid0 = gdimid;
          break;
        }

      if (dimid0 != CDI_UNDEFID && check_dimids[dimid0] == false)
      {
        if (ncdims[dimid0].ncvarid != CDI_UNDEFID && ncdims[dimid0].ncvarid != varid) continue;
        check_dimids[dimid0] = true;

        char sbuf[CDI_MAX_NAME];
        for (int iatt = 0, n = ncvar->nattsNC; iatt < n; ++iatt)
        {
          sbuf[0] = 0;
          cdf_inq_attname(fileID, varid, iatt, sbuf);
          if (str_is_equal(sbuf, "units"))
          {
            cdfGetAttText(fileID, varid, "units", sizeof(sbuf), sbuf);
            if (is_timeaxis_units(sbuf)) return dimid0;
          }
        }
      }
    }
  }

  return CDI_UNDEFID;
}

static void
init_ncdims(int ndims, ncdim_t *ncdims)
{
  for (int gdimid = 0; gdimid < ndims; gdimid++)
  {
    ncdim_t *ncdim = &ncdims[gdimid];
    ncdim->dimid = CDI_UNDEFID;
    ncdim->ncvarid = CDI_UNDEFID;
    ncdim->dimtype = CDI_UNDEFID;
    ncdim->len = 0;
    ncdim->name[0] = 0;
  }
}

static void
init_ncvars(int nvars, ncvar_t *ncvars, int ncid)
{
  for (int varid = 0; varid < nvars; varid++)
  {
    ncvar_t *ncvar = &ncvars[varid];
    ncvar->cdiVarID = CDI_UNDEFID;
    ncvar->ncid = ncid;
    ncvar->varStatus = UndefVar;
    ncvar->ignoreVar = false;
    ncvar->isLonLatMapping = false;
    ncvar->isHealpixMapping = false;
    ncvar->isCubeSphere = false;
    ncvar->isIndexAxis = false;
    ncvar->isXaxis = false;
    ncvar->isYaxis = false;
    ncvar->isZaxis = false;
    ncvar->isTaxis = false;
    ncvar->isLon = false;
    ncvar->isLat = false;
    ncvar->isClimatology = false;
    ncvar->hasCalendar = false;
    ncvar->hasFormulaterms = false;
    ncvar->printWarning = true;
    ncvar->timetype = TIME_CONSTANT;
    ncvar->param = CDI_UNDEFID;
    ncvar->code = CDI_UNDEFID;
    ncvar->tabnum = 0;
    ncvar->bounds = CDI_UNDEFID;
    ncvar->gridID = CDI_UNDEFID;
    ncvar->zaxisID = CDI_UNDEFID;
    ncvar->gridtype = CDI_UNDEFID;
    ncvar->zaxistype = CDI_UNDEFID;
    ncvar->xdim = CDI_UNDEFID;
    ncvar->ydim = CDI_UNDEFID;
    ncvar->zdim = CDI_UNDEFID;
    ncvar->xvarid = CDI_UNDEFID;
    ncvar->yvarid = CDI_UNDEFID;
    ncvar->rpvarid = CDI_UNDEFID;
    ncvar->zvarid = CDI_UNDEFID;
    ncvar->tvarid = CDI_UNDEFID;
    ncvar->ivarid = CDI_UNDEFID;
    ncvar->psvarid = CDI_UNDEFID;
    ncvar->p0varid = CDI_UNDEFID;
    ncvar->ncoordvars = 0;
    for (int i = 0; i < MAX_COORDVARS; ++i) ncvar->cvarids[i] = CDI_UNDEFID;
    for (int i = 0; i < MAX_COORDVARS; ++i) ncvar->coordvarids[i] = CDI_UNDEFID;
    for (int i = 0; i < MAX_AUXVARS; ++i) ncvar->auxvarids[i] = CDI_UNDEFID;
    ncvar->nauxvars = 0;
    ncvar->cellarea = CDI_UNDEFID;
    ncvar->tableID = CDI_UNDEFID;
    ncvar->truncation = 0;
    ncvar->position = 0;
    ncvar->numLPE = 0;
    ncvar->missvalDefined = false;
    ncvar->fillvalDefined = false;
    ncvar->xtype = 0;
    ncvar->gmapid = CDI_UNDEFID;
    ncvar->positive = 0;
    ncvar->ndims = 0;
    for (int i = 0; i < MAX_DIMS_CDF; ++i) ncvar->dimids[i] = CDI_UNDEFID;
    for (int i = 0; i < MAX_DIMS_CDF; ++i) ncvar->dimtypes[i] = CDI_UNDEFID;
    for (int i = 0; i < MAX_DIMS_CDF; ++i) ncvar->chunks[i] = 0;
    ncvar->xstart = 0;
    ncvar->xcount = 0;
    ncvar->zstart = 0;
    ncvar->zcount = 0;
    ncvar->tstart = 0;
    ncvar->tcount = 0;
    ncvar->isChunked = false;
    ncvar->chunkType = CDI_UNDEFID;
    ncvar->chunkSize = CDI_UNDEFID;
    ncvar->chunkCacheSize = 0;
    ncvar->chunkCacheNelems = 0;
    ncvar->chunkCachePreemption = 0.0;
    ncvar->gridSize = 0;
    ncvar->xSize = 0;
    ncvar->ySize = 0;
    ncvar->zSize = 0;
    ncvar->nattsNC = 0;
    ncvar->natts = 0;
    ncvar->atts = NULL;
    ncvar->vctsize = 0;
    ncvar->vct = NULL;
    ncvar->missval = 0;
    ncvar->fillval = 0;
    ncvar->addoffset = 0.0;
    ncvar->scalefactor = 1.0;
    ncvar->hasFilter = false;
    ncvar->hasDeflate = false;
    ncvar->hasSzip = false;
    ncvar->isUnsigned = false;
    ncvar->validrangeDefined = false;
    ncvar->validrange[0] = VALIDMISS;
    ncvar->validrange[1] = VALIDMISS;
    ncvar->typeOfEnsembleForecast = -1;
    ncvar->numberOfForecastsInEnsemble = -1;
    ncvar->perturbationNumber = -1;
    ncvar->unitsLen = 0;
    memset(ncvar->name, 0, CDI_MAX_NAME);
    memset(ncvar->longname, 0, CDI_MAX_NAME);
    memset(ncvar->stdname, 0, CDI_MAX_NAME);
    memset(ncvar->units, 0, CDI_MAX_NAME);
    memset(ncvar->filterSpec, 0, CDI_MAX_NAME);
  }
}

static void
cdf_set_var(ncvar_t *ncvar, int varStatus)
{
  if (ncvar->varStatus != UndefVar && ncvar->varStatus != varStatus && ncvar->printWarning)
  {
    if (!ncvar->ignoreVar) Warning("Inconsistent variable definition for %s!", ncvar->name);

    ncvar->printWarning = false;
    varStatus = CoordVar;
  }

  ncvar->varStatus = varStatus;
}

static void
cdf_set_dim(ncvar_t *ncvar, int dimid, int dimtype)
{
  if (ncvar->dimtypes[dimid] != CDI_UNDEFID && ncvar->dimtypes[dimid] != dimtype)
  {
    Warning("Inconsistent dimension definition for %s! dimid=%d  type=%c  newtype=%c", ncvar->name, dimid,
            axisTypeChar[ncvar->dimtypes[dimid]], axisTypeChar[dimtype]);
  }

  ncvar->dimtypes[dimid] = dimtype;
}

static void
scan_hybrid_formulaterms(int ncid, int ncfvarid, int *avarid, int *bvarid, int *psvarid, int *p0varid)
{
  *avarid = -1;
  *bvarid = -1;
  *psvarid = -1;
  *p0varid = -1;

  char attstring[1024];
  cdfGetAttText(ncid, ncfvarid, "formula_terms", sizeof(attstring), attstring);
  char *pstring = attstring;

  bool lstop = false;
  for (int i = 0; i < 4; i++)
  {
    while (isspace((int) *pstring)) pstring++;
    if (*pstring == 0) break;
    char *tagname = pstring;
    while (!isspace((int) *pstring) && *pstring != 0) pstring++;
    if (*pstring == 0) lstop = true;
    *(pstring++) = 0;

    while (isspace((int) *pstring)) pstring++;
    if (*pstring == 0) break;
    char *varname = pstring;
    while (!isspace((int) *pstring) && *pstring != 0) pstring++;
    if (*pstring == 0) lstop = true;
    *(pstring++) = 0;

    int dimvarid;
    int status_nc = nc_inq_varid(ncid, varname, &dimvarid);
    if (status_nc == NC_NOERR)
    {
      // clang-format off
      if      (str_is_equal(tagname, "ap:")) *avarid  = dimvarid;
      else if (str_is_equal(tagname, "a:") ) *avarid  = dimvarid;
      else if (str_is_equal(tagname, "b:") ) *bvarid  = dimvarid;
      else if (str_is_equal(tagname, "ps:")) *psvarid = dimvarid;
      else if (str_is_equal(tagname, "p0:")) *p0varid = dimvarid;
      // clang-format on
    }
    else if (!str_is_equal(tagname, "ps:")) { Warning("%s - %s", nc_strerror(status_nc), varname); }

    if (lstop) break;
  }
}

static void
readVCT(int ncid, int ndims2, size_t dimlen, size_t dimlen2, int avarid2, int bvarid2, double *vct)
{
  double *abuf = (double *) Malloc(dimlen * 2 * sizeof(double));
  double *bbuf = (double *) Malloc(dimlen * 2 * sizeof(double));
  cdf_get_var_double(ncid, avarid2, abuf);
  cdf_get_var_double(ncid, bvarid2, bbuf);

  if (ndims2 == 2)
  {
    for (size_t i = 0; i < dimlen; ++i)
    {
      vct[i] = abuf[i * 2];
      vct[i + dimlen + 1] = bbuf[i * 2];
    }
    vct[dimlen] = abuf[dimlen * 2 - 1];
    vct[dimlen * 2 + 1] = bbuf[dimlen * 2 - 1];
  }
  else
  {
    for (size_t i = 0; i < dimlen2; ++i)
    {
      vct[i] = abuf[i];
      vct[i + dimlen + 1] = bbuf[i];
    }
  }

  Free(abuf);
  Free(bbuf);
}

static bool
isHybridSigmaPressureCoordinate(int ncid, int ncvarid, ncvar_t *ncvars, const ncdim_t *ncdims)
{
  bool status = false;
  ncvar_t *ncvar = &ncvars[ncvarid];

  if (str_is_equal(ncvar->stdname, "atmosphere_hybrid_sigma_pressure_coordinate"))
  {
    CDI_Convention = CDI_CONVENTION_CF;

    status = true;
    ncvar->zaxistype = ZAXIS_HYBRID;
    // int ndims = ncvar->ndims;
    int dimid = ncvar->dimids[0];
    size_t dimlen = ncdims[dimid].len;
    int avarid1 = -1, bvarid1 = -1, psvarid1 = -1, p0varid1 = -1;
    int ncfvarid = ncvarid;
    if (ncvars[ncfvarid].hasFormulaterms) scan_hybrid_formulaterms(ncid, ncfvarid, &avarid1, &bvarid1, &psvarid1, &p0varid1);
    // printf("avarid1, bvarid1, psvarid1, p0varid1 %d %d %d %d\n", avarid1, bvarid1, psvarid1, p0varid1);
    if (avarid1 != -1) ncvars[avarid1].varStatus = CoordVar;
    if (bvarid1 != -1) ncvars[bvarid1].varStatus = CoordVar;
    if (psvarid1 != -1) ncvar->psvarid = psvarid1;
    if (p0varid1 != -1) ncvar->p0varid = p0varid1;

    if (ncvar->bounds != CDI_UNDEFID && ncvars[ncvar->bounds].hasFormulaterms)
    {
      ncfvarid = ncvar->bounds;
      int avarid2 = -1, bvarid2 = -1, psvarid2 = -1, p0varid2 = -1;
      if (ncvars[ncfvarid].hasFormulaterms) scan_hybrid_formulaterms(ncid, ncfvarid, &avarid2, &bvarid2, &psvarid2, &p0varid2);
      // printf("avarid2, bvarid2, psvarid2, p0varid2 %d %d %d %d\n", avarid2, bvarid2, psvarid2, p0varid2);
      if (avarid2 != -1 && bvarid2 != -1)
      {
        ncvars[avarid2].varStatus = CoordVar;
        ncvars[bvarid2].varStatus = CoordVar;

        int ndims2 = ncvars[avarid2].ndims;
        int dimid2 = ncvars[avarid2].dimids[0];
        size_t dimlen2 = ncdims[dimid2].len;

        if ((ndims2 == 2 && dimid == dimid2) || (ndims2 == 1 && dimlen == dimlen2 - 1))
        {
          double px = 1;
          if (p0varid1 != -1 && p0varid1 == p0varid2) cdf_get_var_double(ncid, p0varid2, &px);

          size_t vctsize = (dimlen + 1) * 2;
          double *vct = (double *) Malloc(vctsize * sizeof(double));

          readVCT(ncid, ndims2, dimlen, dimlen2, avarid2, bvarid2, vct);

          if (p0varid1 != -1 && IS_NOT_EQUAL(px, 1))
            for (size_t i = 0; i < dimlen + 1; ++i) vct[i] *= px;

          ncvar->vct = vct;
          ncvar->vctsize = vctsize;
        }
      }
    }
  }

  return status;
}

static int
atttypeInt_to_datatype(nc_type atttype)
{
  // clang-format off
  int datatype = (atttype == NC_SHORT)  ? CDI_DATATYPE_INT16 :
                 (atttype == NC_BYTE)   ? CDI_DATATYPE_INT8 :
                 (atttype == NC_UBYTE)  ? CDI_DATATYPE_UINT8 :
                 (atttype == NC_USHORT) ? CDI_DATATYPE_UINT16 :
                 (atttype == NC_UINT)   ? CDI_DATATYPE_UINT32 :
                                          CDI_DATATYPE_INT32;
  // clang-format on
  return datatype;
}

static void
cdf_set_cdi_attr(int ncid, int ncvarid, int attnum, int cdiID, int varID, bool removeFillValue)
{
  nc_type atttype;
  size_t attlen;
  char attname[CDI_MAX_NAME];

  cdf_inq_attname(ncid, ncvarid, attnum, attname);
  cdf_inq_attlen(ncid, ncvarid, attname, &attlen);
  cdf_inq_atttype(ncid, ncvarid, attname, &atttype);

  if (removeFillValue && str_is_equal("_FillValue", attname)) return;

  if (xtypeIsInt(atttype))
  {
    int attint = 0;
    int *pattint = (attlen > 1) ? (int *) Malloc(attlen * sizeof(int)) : &attint;
    cdfGetAttInt(ncid, ncvarid, attname, attlen, pattint);
    cdiDefAttInt(cdiID, varID, attname, atttypeInt_to_datatype(atttype), (int) attlen, pattint);
    if (attlen > 1) Free(pattint);
  }
  else if (xtypeIsInt64(atttype))
  {
    int64_t attint64 = 0;
    int64_t *pattint64 = (attlen > 1) ? (int64_t *) Malloc(attlen * sizeof(int64_t)) : &attint64;
    cdfGetAttInt64(ncid, ncvarid, attname, attlen, pattint64);
    bool defineAtts = true;
    for (size_t i = 0; i < attlen; ++i)
      if (pattint64[i] > INT_MAX) defineAtts = false;
    if (defineAtts)
    {
      int attint = 0;
      int *pattint = (attlen > 1) ? (int *) Malloc(attlen * sizeof(int)) : &attint;
      for (size_t i = 0; i < attlen; ++i) pattint[i] = (int) pattint64[i];
      cdiDefAttInt(cdiID, varID, attname, CDI_DATATYPE_INT32, (int) attlen, pattint);
      if (attlen > 1) Free(pattint);
    }
    if (attlen > 1) Free(pattint64);
  }
  else if (xtypeIsFloat(atttype))
  {
    double attflt = 0.0;
    double *pattflt = (attlen > 1) ? (double *) Malloc(attlen * sizeof(double)) : &attflt;
    cdfGetAttDouble(ncid, ncvarid, attname, attlen, pattflt);
    int datatype = (atttype == NC_FLOAT) ? CDI_DATATYPE_FLT32 : CDI_DATATYPE_FLT64;
    cdiDefAttFlt(cdiID, varID, attname, datatype, (int) attlen, pattflt);
    if (attlen > 1) Free(pattflt);
  }
  else if (xtypeIsText(atttype))
  {
    char attstring[8192];
    cdfGetAttText(ncid, ncvarid, attname, sizeof(attstring), attstring);
    cdiDefAttTxt(cdiID, varID, attname, (int) strlen(attstring), attstring);
  }
}

static void
cdf_print_vars(const ncvar_t *ncvars, int nvars, const char *oname)
{
  // clang-format off
  char axis[7];
  enum { TAXIS = 't', ZAXIS = 'z', EAXIS = 'e', YAXIS = 'y', XAXIS = 'x' };

  fprintf(stderr, "%s:\n", oname);

  for (int varid = 0; varid < nvars; varid++)
  {
    const ncvar_t *ncvar = &ncvars[varid];
    int ndim = 0;
    if (ncvar->varStatus == DataVar || ncvar->varStatus == UndefVar)
    {
      axis[ndim++] = (ncvar->varStatus == DataVar) ? 'v' : 'u';
      axis[ndim++] = ':';
      for (int i = 0; i < ncvar->ndims; i++)
      {
        if      (ncvar->dimtypes[i] == T_AXIS) axis[ndim++] = TAXIS;
        else if (ncvar->dimtypes[i] == Z_AXIS) axis[ndim++] = ZAXIS;
        else if (ncvar->dimtypes[i] == E_AXIS) axis[ndim++] = EAXIS;
        else if (ncvar->dimtypes[i] == Y_AXIS) axis[ndim++] = YAXIS;
        else if (ncvar->dimtypes[i] == X_AXIS) axis[ndim++] = XAXIS;
        else                                   axis[ndim++] = '?';
      }
    }
    else
    {
      axis[ndim++] = 'c';
      axis[ndim++] = ':';
      if      (ncvar->isTaxis) axis[ndim++] = TAXIS;
      else if (ncvar->isZaxis) axis[ndim++] = ZAXIS;
      else if (ncvar->isLat  ) axis[ndim++] = YAXIS;
      else if (ncvar->isYaxis) axis[ndim++] = YAXIS;
      else if (ncvar->isLon  ) axis[ndim++] = XAXIS;
      else if (ncvar->isXaxis) axis[ndim++] = XAXIS;
      else                     axis[ndim++] = '?';
    }

    axis[ndim++] = 0;

    fprintf(stderr, "%3d %3d  %-6s %s\n", varid, ndim-3, axis, ncvar->name);
  }
  // clang-format on
}

static void
cdf_scan_attr_axis(ncvar_t *ncvars, ncdim_t *ncdims, int ncvarid, const char *attstring, int nvdims, const int *dimidsp)
{
  ncvar_t *ncvar = &ncvars[ncvarid];
  int attlen = (int) strlen(attstring);

  if (nvdims == 0 && attlen == 1 && attstring[0] == 'z')
  {
    cdf_set_var(ncvar, CoordVar);
    ncvar->isZaxis = true;
    return;
  }

  if (attlen != nvdims) return;

  static const char accept[] = "-tTzZyYxX";
  if ((int) strspn(attstring, accept) != attlen) return;

  while (attlen--)
  {
    int dimtype;
    bool setVar = false;
    int attchar = toupper(attstring[attlen]);
    switch (attchar)
    {
      case 'T':
        if (attlen != 0) Warning("axis attribute 't' not on first position");
        dimtype = T_AXIS;
        break;
      case 'Z':
        ncvar->zdim = dimidsp[attlen];
        dimtype = Z_AXIS;
        setVar = (ncvar->ndims == 1);
        break;
      case 'Y':
        ncvar->ydim = dimidsp[attlen];
        dimtype = Y_AXIS;
        setVar = (ncvar->ndims == 1);
        break;
      case 'X':
        ncvar->xdim = dimidsp[attlen];
        dimtype = X_AXIS;
        setVar = (ncvar->ndims == 1);
        break;
      default: continue;
    }
    cdf_set_dim(ncvar, attlen, dimtype);

    if (setVar)
    {
      cdf_set_var(ncvar, CoordVar);
      ncdims[ncvar->dimids[0]].dimtype = (ncdims[ncvar->dimids[0]].dimtype == CDI_UNDEFID) ? dimtype : CDI_UNDEFID;
    }
  }
}

static int
cdf_get_cell_varid(char *attstring, int ncid)
{
  int nc_cell_id = CDI_UNDEFID;

  char *pstring = attstring;
  while (isspace((int) *pstring)) pstring++;
  char *cell_measures = pstring;
  while (isalnum((int) *pstring)) pstring++;
  *(pstring++) = 0;
  while (isspace((int) *pstring)) pstring++;
  char *cell_var = pstring;
  while (!isspace((int) *pstring) && *pstring != 0) pstring++;
  *(pstring++) = 0;
  /*
    printf("cell_measures >%s<\n", cell_measures);
    printf("cell_var >%s<\n", cell_var);
  */
  if (strStartsWith(cell_measures, "area"))
  {
    int nc_var_id;
    int status = nc_inq_varid(ncid, cell_var, &nc_var_id);
    if (status == NC_NOERR) nc_cell_id = nc_var_id;
    /*
    else
      Warning("%s - %s", nc_strerror(status), cell_var);
    */
  }

  return nc_cell_id;
}

static bool
is_valid_coordinate(ncvar_t *ncvar)
{
  bool status = true;
  if (ncvar->ndims > 1 && (str_is_equal(ncvar->name, "zg") || str_is_equal(ncvar->name, "zghalf"))) status = false;
  return status;
}

static void
read_coordinates_vars(int ncid, char *attstring, ncvar_t *ncvar, ncvar_t *ncvars, int *nchecked_vars, char *checked_vars[],
                      int max_check_vars)
{
  bool lstop = false;
  for (int i = 0; i < MAX_COORDVARS && !lstop; i++)
  {
    while (isspace((int) *attstring)) attstring++;
    if (*attstring == 0) break;
    char *varname = attstring;
    while (!isspace((int) *attstring) && *attstring != 0) attstring++;
    if (*attstring == 0) lstop = true;
    if (*(attstring - 1) == ',') *(attstring - 1) = 0;
    *(attstring++) = 0;

    int dimvarid;
    int status = nc_inq_varid(ncid, varname, &dimvarid);
    if (status == NC_NOERR)
    {
      if (is_valid_coordinate(&ncvars[dimvarid]))
      {
        cdf_set_var(&ncvars[dimvarid], CoordVar);
        if (!CDI_Ignore_Att_Coordinates)
        {
          ncvar->coordvarids[i] = dimvarid;
          ncvar->ncoordvars++;
        }
      }
    }
    else
    {
      if (!CDI_Ignore_Att_Coordinates) ncvar->ncoordvars++;

      int k;
      for (k = 0; k < *nchecked_vars; ++k)
        if (str_is_equal(checked_vars[k], varname)) break;

      if (k == *nchecked_vars)
      {
        if (*nchecked_vars < max_check_vars) checked_vars[(*nchecked_vars)++] = strdup(varname);
        Warning("%s - >%s<", nc_strerror(status), varname);
      }
    }
  }
}

static void
read_auxiliary_vars(int ncid, char *attstring, ncvar_t *ncvar, ncvar_t *ncvars)
{
  bool lstop = false;
  for (int i = 0; i < MAX_AUXVARS && !lstop; i++)
  {
    while (isspace((int) *attstring)) attstring++;
    if (*attstring == 0) break;
    char *varname = attstring;
    while (!isspace((int) *attstring) && *attstring != 0) attstring++;
    if (*attstring == 0) lstop = true;
    *(attstring++) = 0;

    int dimvarid;
    int status = nc_inq_varid(ncid, varname, &dimvarid);
    if (status == NC_NOERR)
    {
      cdf_set_var(&ncvars[dimvarid], CoordVar);
      //  if ( !CDI_Ignore_Att_Coordinates )
      {
        ncvar->auxvarids[i] = dimvarid;
        ncvar->nauxvars++;
      }
    }
    else
      Warning("%s - %s", nc_strerror(status), varname);
  }
}

static void
read_grid_mapping(int ncid, char *attstring, ncvar_t *ncvar, ncvar_t *ncvars)
{
  int nc_gmap_id;
  int status = nc_inq_varid(ncid, attstring, &nc_gmap_id);
  if (status == NC_NOERR)
  {
    ncvar->gmapid = nc_gmap_id;
    cdf_set_var(&ncvars[ncvar->gmapid], CoordVar);
    int nc_gmap_varid = ncvars[ncvar->gmapid].ncid;
    if (cdfCheckAttText(nc_gmap_varid, nc_gmap_id, "grid_mapping_name"))
    {
      char gridMappingName[CDI_MAX_NAME];
      cdfGetAttText(nc_gmap_varid, nc_gmap_id, "grid_mapping_name", CDI_MAX_NAME, gridMappingName);
      if (str_is_equal(gridMappingName, "healpix")) { ncvar->isHealpixMapping = true; }
      else if (str_is_equal(gridMappingName, "latitude_longitude")) { ncvar->isLonLatMapping = true; }
    }
  }
  else
    Warning("%s - %s", nc_strerror(status), attstring);
}

static void
set_vars_chunks(int ncid, int ncvarid, int nvdims, ncvar_t *ncvar)
{
  int shuffle = 0, deflate = 0, deflateLevel = 0;
  nc_inq_var_deflate(ncid, ncvarid, &shuffle, &deflate, &deflateLevel);
  if (deflate > 0) ncvar->hasDeflate = true;

#ifdef HAVE_NC_DEF_VAR_SZIP
  int options_mask = 0, pixels_per_block = 0;
  nc_inq_var_szip(ncid, ncvarid, &options_mask, &pixels_per_block);
  if (options_mask && pixels_per_block) ncvar->hasSzip = true;
#endif
  ncvar->hasFilter = (cdf_get_var_filter(ncid, ncvarid, ncvar->filterSpec, CDI_MAX_NAME) > 0);
  // if (ncvar->hasFilter) printf("filterSpec: %s=%s\n", ncvar->name, ncvar->filterSpec);

  size_t chunks[MAX_DIMS_CDF];
  int storageIn;
  if (nc_inq_var_chunking(ncid, ncvarid, &storageIn, chunks) == NC_NOERR)
  {
    if (storageIn == NC_CHUNKED)
    {
      ncvar->isChunked = true;
      for (int i = 0; i < nvdims; ++i) ncvar->chunks[i] = chunks[i];
      if (CDI_Debug)
      {
        fprintf(stderr, "%s: chunking %d %d %d  chunks ", ncvar->name, storageIn, NC_CONTIGUOUS, NC_CHUNKED);
        for (int i = 0; i < nvdims; ++i) fprintf(stderr, "%zu ", chunks[i]);
        fprintf(stderr, "\n");
      }
    }
  }

  size_t size;
  size_t nelems;
  float preemption;
  if (nc_get_var_chunk_cache(ncid, ncvarid, &size, &nelems, &preemption) == NC_NOERR)
  {
    ncvar->chunkCacheSize = size;
    ncvar->chunkCacheNelems = nelems;
    ncvar->chunkCachePreemption = preemption;
    if (CDI_Debug) fprintf(stderr, "%s: chunkCacheSize=%zu nelems=%zu preemption=%g\n", ncvar->name, size, nelems, preemption);
  }
}

#if defined __GNUC__ || (__GNUC__ == 4 && __GNUC_MINOR__ == 9)
#pragma GCC diagnostic push
#pragma GCC diagnostic warning "-Wstrict-overflow"
#endif

static void
read_vars_info(int nvars, ncvar_t *ncvars, int ndims, ncdim_t *ncdims, int format)
{
  for (int varid = 0; varid < nvars; varid++)
  {
    ncvar_t *ncvar = &ncvars[varid];
    cdf_inq_var(ncvar->ncid, varid, ncvar->name, &ncvar->xtype, &ncvar->ndims, ncvar->dimids, &ncvar->nattsNC);

    for (int vdimid = 0; vdimid < ncvar->ndims; ++vdimid)
      for (int gdimid = 0; gdimid < ndims; ++gdimid)
        if (ncdims[gdimid].dimid == ncvar->dimids[vdimid])
        {
          ncvar->dimids[vdimid] = gdimid;
          break;
        }

    for (int vdimid = 0; vdimid < ncvar->ndims; vdimid++) ncvar->dimtypes[vdimid] = -1;

    if (format == NC_FORMAT_NETCDF4_CLASSIC || format == NC_FORMAT_NETCDF4)
    {
      set_vars_chunks(ncvar->ncid, varid, ncvar->ndims, ncvar);
    }
  }
}

static void
set_vars_timetype(int nvars, ncvar_t *ncvars, int timedimid)
{
  for (int varid = 0; varid < nvars; varid++)
  {
    ncvar_t *ncvar = &ncvars[varid];
    if (ncvar->ndims > 0)
    {
      if (timedimid == ncvar->dimids[0])
      {
        ncvar->timetype = TIME_VARYING;
        cdf_set_dim(ncvar, 0, T_AXIS);
      }
      else
      {
        for (int i = 1, n = ncvar->ndims; i < n; i++)
        {
          if (timedimid == ncvar->dimids[i])
          {
            Warning("Time must be the first dimension! Unsupported array structure, skipped variable %s!", ncvar->name);
            ncvar->varStatus = CoordVar;
          }
        }
      }
    }
  }
}

static void
scan_vars_attr(int nvars, ncvar_t *ncvars, int ndims, ncdim_t *ncdims, int modelID)
{
  int nchecked_vars = 0;
  enum
  {
    max_check_vars = 9
  };
  char *checked_vars[max_check_vars];
  for (int i = 0; i < max_check_vars; ++i) checked_vars[i] = NULL;

  char attname[CDI_MAX_NAME];
  char attstring[8192];

  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    int ncid = ncvar->ncid;
    const char *name = ncvar->name;
    int nvdims = ncvar->ndims;
    nc_type xtype = ncvar->xtype;
    int nvatts = ncvar->nattsNC;

    if (ncvar->natts == 0 && nvatts > 0) ncvar->atts = (int *) Malloc((size_t) nvatts * sizeof(int));

    for (int iatt = 0; iatt < nvatts; ++iatt)
    {
      int nc_cell_id = CDI_UNDEFID;

      nc_type atttype;
      size_t attlen;
      cdf_inq_attname(ncid, ncvarid, iatt, attname);
      cdf_inq_atttype(ncid, ncvarid, attname, &atttype);
      cdf_inq_attlen(ncid, ncvarid, attname, &attlen);

      size_t attstringsize = sizeof(attstring);
      bool isText = xtypeIsText(atttype), isNumber = xtypeIsFloat(atttype) || xtypeIsInt(atttype);
      bool isRealization = false, isEnsembleMembers = false, isForecastInitType = false;
      if (isText)
      {
        cdfGetAttText(ncid, ncvarid, attname, sizeof(attstring), attstring);
        attstringsize = strlen(attstring) + 1;
        if (attstringsize > CDI_MAX_NAME) attstringsize = CDI_MAX_NAME;
      }

      if (isText && str_is_equal(attname, "long_name")) { memcpy(ncvar->longname, attstring, attstringsize); }
      else if (isText && str_is_equal(attname, "standard_name")) { memcpy(ncvar->stdname, attstring, attstringsize); }
      else if (isText && str_is_equal(attname, "units"))
      {
        ncvar->unitsLen = (int) attstringsize;
        memcpy(ncvar->units, attstring, attstringsize);
      }
      else if (isText && str_is_equal(attname, "calendar")) { ncvar->hasCalendar = true; }
      else if (isText && str_is_equal(attname, "param"))
      {
        int pnum = 0, pcat = 255, pdis = 255;
        sscanf(attstring, "%d.%d.%d", &pnum, &pcat, &pdis);
        ncvar->param = cdiEncodeParam(pnum, pcat, pdis);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "trunc_type"))
      {
        if (str_is_equal(attstring, "Triangular")) ncvar->gridtype = GRID_SPECTRAL;
      }
      else if (isText && (str_is_equal(attname, "grid_type") || str_is_equal(attname, "CDI_grid_type")))
      {
        str_to_lower(attstring);
        cdf_set_gridtype(attstring, &ncvar->gridtype);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "CDI_grid_latitudes"))
      {
        int nc_yvar_id;
        int status = nc_inq_varid(ncid, attstring, &nc_yvar_id);
        if (status == NC_NOERR)
        {
          ncvar->yvarid = nc_yvar_id;
          cdf_set_var(&ncvars[ncvar->yvarid], CoordVar);
        }
        else
          Warning("%s - %s", nc_strerror(status), attstring);

        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "CDI_grid_reduced_points"))
      {
        int nc_rpvar_id;
        int status = nc_inq_varid(ncid, attstring, &nc_rpvar_id);
        if (status == NC_NOERR)
        {
          ncvar->rpvarid = nc_rpvar_id;
          cdf_set_var(&ncvars[ncvar->rpvarid], CoordVar);
        }
        else
          Warning("%s - %s", nc_strerror(status), attstring);

        cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "code"))
      {
        cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvar->code);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "table"))
      {
        int tablenum;
        cdfGetAttInt(ncid, ncvarid, attname, 1, &tablenum);
        if (tablenum > 0)
        {
          ncvar->tabnum = tablenum;
          ncvar->tableID = tableInq(modelID, tablenum, NULL);
          if (ncvar->tableID == CDI_UNDEFID) ncvar->tableID = tableDef(modelID, tablenum, NULL);
        }
        cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "CDI_grid_num_LPE")) { cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvar->numLPE); }
      else if (isText && str_is_equal(attname, "level_type"))
      {
        str_to_lower(attstring);
        cdf_set_zaxistype(attstring, &ncvar->zaxistype);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "trunc_count")) { cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvar->truncation); }
      else if (isNumber && str_is_equal(attname, "truncation")) { cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvar->truncation); }
      else if (isNumber && str_is_equal(attname, "number_of_grid_in_reference"))
      {
        cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvar->position);
      }
      else if (isNumber && str_is_equal(attname, "add_offset"))
      {
        cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvar->addoffset);
        /*
          if ( atttype != NC_BYTE && atttype != NC_SHORT && atttype != NC_INT )
          if ( ncvar->addoffset != 0 )
          Warning("attribute add_offset not supported for atttype %d", atttype);
        */
        // (also used for lon/lat) cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "scale_factor"))
      {
        cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvar->scalefactor);
        /*
          if ( atttype != NC_BYTE && atttype != NC_SHORT && atttype != NC_INT )
          if ( ncvar->scalefactor != 1 )
          Warning("attribute scale_factor not supported for atttype %d", atttype);
        */
        // (also used for lon/lat) cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "climatology"))
      {
        int ncboundsid;
        int status = nc_inq_varid(ncid, attstring, &ncboundsid);
        if (status == NC_NOERR)
        {
          ncvar->isClimatology = true;
          ncvar->bounds = ncboundsid;
          cdf_set_var(&ncvars[ncvar->bounds], CoordVar);
          cdf_set_var(ncvar, CoordVar);
        }
        else
          Warning("%s - %s", nc_strerror(status), attstring);
      }
      else if (isText && str_is_equal(attname, "bounds"))
      {
        int ncboundsid;
        int status = nc_inq_varid(ncid, attstring, &ncboundsid);
        if (status == NC_NOERR)
        {
          ncvar->bounds = ncboundsid;
          cdf_set_var(&ncvars[ncvar->bounds], CoordVar);
          cdf_set_var(ncvar, CoordVar);
        }
        else
        {
          static bool printWarning = true;
          if (printWarning)
          {
            printWarning = false;
            Warning("%s - %s", nc_strerror(status), attstring);
          }
        }
      }
      else if (isText && str_is_equal(attname, "formula_terms")) { ncvar->hasFormulaterms = true; }
      else if (isText && str_is_equal(attname, "cell_measures")
               && (nc_cell_id = cdf_get_cell_varid(attstring, ncid)) != CDI_UNDEFID)
      {
        ncvar->cellarea = nc_cell_id;
        ncvars[nc_cell_id].varStatus = CoordVar;
        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && (str_is_equal(attname, "associate") || str_is_equal(attname, "coordinates")))
      {
        read_coordinates_vars(ncid, attstring, ncvar, ncvars, &nchecked_vars, checked_vars, max_check_vars);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "auxiliary_variable"))
      {
        read_auxiliary_vars(ncid, attstring, ncvar, ncvars);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "grid_mapping"))
      {
        read_grid_mapping(ncid, attstring, ncvar, ncvars);
        cdf_set_var(ncvar, DataVar);
      }
      else if (isText && str_is_equal(attname, "positive"))
      {
        str_to_lower(attstring);
        if (str_is_equal(attstring, "down"))
          ncvar->positive = POSITIVE_DOWN;
        else if (str_is_equal(attstring, "up"))
          ncvar->positive = POSITIVE_UP;

        int dimid0 = ncvar->dimids[0];
        if (ncvar->varStatus == UndefVar
            && (nvdims == 0 || (nvdims == 1 && ncvar->dimtypes[0] == CDI_UNDEFID && ncdims[dimid0].ncvarid == CDI_UNDEFID)))
        {
          if (nvdims == 1)
          {
            cdf_set_var(ncvar, CoordVar);
            cdf_set_dim(ncvar, 0, Z_AXIS);
            if (dimid0 < ndims) ncdims[dimid0].dimtype = Z_AXIS;
          }
          else if (nvdims == 0)
          {
            cdf_set_var(ncvar, CoordVar);
            ncvar->isZaxis = true;
          }
        }
        else { ncvar->atts[ncvar->natts++] = iatt; }
      }
      else if (isText && str_is_equal(attname, "cdi"))
      {
        if (!strcasecmp(attstring, "ignore"))
        {
          ncvar->ignoreVar = true;
          cdf_set_var(ncvar, CoordVar);
        }
      }
      else if (isText && str_is_equal(attname, "_Unsigned"))
      {
        if (!strcasecmp(attstring, "true"))
        {
          ncvar->isUnsigned = true;
          /*
          ncvar->validrangeDefined = true;
          ncvar->validrange[0] = 0;
          ncvar->validrange[1] = 255;
          */
        }
        // cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "_FillValue"))
      {
        cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvar->fillval);
        ncvar->fillvalDefined = true;
        // cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "missing_value"))
      {
        cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvar->missval);
        ncvar->missvalDefined = true;
        // cdf_set_var(ncvar, DataVar);
      }
      else if (isNumber && str_is_equal(attname, "valid_range") && attlen == 2)
      {
        if (ncvar->validrangeDefined == false)
        {
          bool ignoreDatatype = (xtypeIsFloat(atttype) != xtypeIsFloat(xtype));
          if (!CDI_Ignore_Valid_Range && ignoreDatatype == false)
          {
            cdfGetAttDouble(ncid, ncvarid, attname, 2, ncvar->validrange);
            ncvar->validrangeDefined = (ncvar->validrange[0] <= ncvar->validrange[1]);
            if (((int) ncvar->validrange[0]) == 0 && ((int) ncvar->validrange[1]) == 255) ncvar->isUnsigned = true;
            // cdf_set_var(ncvar, DataVar);
          }
          else if (ignoreDatatype) { Warning("Inconsistent data type for attribute %s:valid_range, ignored!", name); }
        }
      }
      else if (isNumber && str_is_equal(attname, "valid_min") && attlen == 1)
      {
        bool ignoreDatatype = (xtypeIsFloat(atttype) != xtypeIsFloat(xtype));
        if (!CDI_Ignore_Valid_Range && ignoreDatatype == false)
        {
          cdfGetAttDouble(ncid, ncvarid, attname, 1, &(ncvar->validrange)[0]);
          ncvar->validrangeDefined = true;
        }
        else if (ignoreDatatype) { Warning("Inconsistent data type for attribute %s:valid_min, ignored!", name); }
      }
      else if (isNumber && str_is_equal(attname, "valid_max") && attlen == 1)
      {
        bool ignoreDatatype = (xtypeIsFloat(atttype) != xtypeIsFloat(xtype));
        if (!CDI_Ignore_Valid_Range && ignoreDatatype == false)
        {
          cdfGetAttDouble(ncid, ncvarid, attname, 1, &(ncvar->validrange)[1]);
          ncvar->validrangeDefined = true;
        }
        else if (ignoreDatatype) { Warning("Inconsistent data type for attribute %s:valid_max, ignored!", name); }
      }
      else if (isNumber
               && ((isRealization = str_is_equal(attname, "realization"))
                   || (isEnsembleMembers = str_is_equal(attname, "ensemble_members"))
                   || (isForecastInitType = str_is_equal(attname, "forecast_init_type"))))
      {
        int temp;
        cdfGetAttInt(ncid, ncvarid, attname, 1, &temp);

        // clang-format off
        if      (isRealization)      ncvar->perturbationNumber = temp;
        else if (isEnsembleMembers)  ncvar->numberOfForecastsInEnsemble = temp;
        else if (isForecastInitType) ncvar->typeOfEnsembleForecast = temp;
        // clang-format on

        cdf_set_var(ncvar, DataVar);
      }
      else { ncvar->atts[ncvar->natts++] = iatt; }
    }
  }

  for (int i = 0; i < max_check_vars; ++i)
    if (checked_vars[i]) Free(checked_vars[i]);
}
#if defined __GNUC__ || (__GNUC__ == 4 && __GNUC_MINOR__ == 9)
#pragma GCC diagnostic pop
#endif

static void
cdf_set_chunk_info(stream_t *streamptr, int nvars, ncvar_t *ncvars)
{
  int vlistID = streamptr->vlistID;
  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    int chunkSizeDimT = 0;
    int chunkSizeDimZ = 0;
    int chunkSizeDimY = 0;
    int chunkSizeDimX = 0;
    ncvar_t *ncvar = &ncvars[ncvarid];
    int varID = ncvar->cdiVarID;
    if (ncvar->varStatus == DataVar && ncvar->isChunked && varID != CDI_UNDEFID)
    {
      for (int i = 0, n = ncvar->ndims; i < n; ++i)
      {
        size_t chunkSize = ncvar->chunks[i];
        if (chunkSize > 1)
        {
          int dimType = ncvar->dimtypes[i];
          // clang-format off
          if      (dimType == T_AXIS && chunkSize > cdfInfo->chunkSizeDimT) { cdfInfo->chunkSizeDimT = chunkSize; }
          else if (dimType == Z_AXIS && chunkSize > cdfInfo->chunkSizeDimZ) { cdfInfo->chunkSizeDimZ = chunkSize; }

          if      (dimType == T_AXIS) { chunkSizeDimT = chunkSize; }
          else if (dimType == Z_AXIS) { chunkSizeDimZ = chunkSize; }
          else if (dimType == Y_AXIS) { chunkSizeDimY = chunkSize; }
          else if (dimType == X_AXIS) { chunkSizeDimX = chunkSize; }
          // clang-format on
        }
      }
      if ((CDI_Copy_ChunkSpec || chunkSizeDimT == 0) && CDI_Remove_ChunkSpec == false)
      {
        if (chunkSizeDimT > 0) cdiDefKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMT, chunkSizeDimT);
        if (chunkSizeDimZ > 0) cdiDefKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMZ, chunkSizeDimZ);
        if (chunkSizeDimY > 0) cdiDefKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMY, chunkSizeDimY);
        if (chunkSizeDimX > 0) cdiDefKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMX, chunkSizeDimX);
      }
    }
  }
}

static void
verify_vars_attr(int nvars, ncvar_t *ncvars, ncdim_t *ncdims)
{
  nc_type atttype;
  size_t attlen;
  char attname[CDI_MAX_NAME];
  char attstring[8192];

  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    int ncid = ncvar->ncid;
    const int *dimidsp = ncvar->dimids;
    int nvdims = ncvar->ndims;
    int nvatts = ncvar->natts;

    for (int i = 0; i < nvatts; i++)
    {
      int attnum = ncvar->atts[i];
      cdf_inq_attname(ncid, ncvarid, attnum, attname);
      cdf_inq_attlen(ncid, ncvarid, attname, &attlen);
      cdf_inq_atttype(ncid, ncvarid, attname, &atttype);

      size_t attstringsize = sizeof(attstring);
      // bool isNumber = (xtypeIsFloat(atttype) || xtypeIsInt(atttype));

      bool isText = xtypeIsText(atttype);
      if (isText)
      {
        cdfGetAttText(ncid, ncvarid, attname, sizeof(attstring), attstring);
        attstringsize = strlen(attstring) + 1;
        if (attstringsize > CDI_MAX_NAME) { attstringsize = CDI_MAX_NAME; }

        if (str_is_equal(attname, "axis")) { cdf_scan_attr_axis(ncvars, ncdims, ncvarid, attstring, nvdims, dimidsp); }
        // else if (str_is_equal(attname, "standard_name")) { memcpy(ncvar->stdname, attstring, attstringsize); }
      }
    }
  }
}

static void
find_dimtypes(ncvar_t *ncvars, ncvar_t *ncvar, bool *plxdim, bool *plydim, bool *plzdim, int *plcdim)
{
  bool lxdim = false, lydim = false, lzdim = false /*, ltdim = false */;
  int lcdim = 0;
  for (int i = 0, n = ncvar->ndims; i < n; i++)
  {
    int dimtype = ncvar->dimtypes[i];
    lxdim |= (dimtype == X_AXIS);
    lydim |= (dimtype == Y_AXIS);
    lzdim |= (dimtype == Z_AXIS);
    if (ncvar->cvarids[i] != CDI_UNDEFID) lcdim++;
    // ltdim |= (dimtype == T_AXIS);
  }

  if (!lxdim && ncvar->xvarid != CDI_UNDEFID && ncvars[ncvar->xvarid].ndims == 0) lxdim = true;
  if (!lydim && ncvar->yvarid != CDI_UNDEFID && ncvars[ncvar->yvarid].ndims == 0) lydim = true;

  *plxdim = lxdim;
  *plydim = lydim;
  *plzdim = lzdim;
  *plcdim = lcdim;
}

static void
cdf_set_dimtype(int numVars, ncvar_t *ncvars, ncdim_t *ncdims)
{
  for (int varId = 0; varId < numVars; ++varId)
  {
    ncvar_t *ncvar = &ncvars[varId];
    if (ncvar->varStatus == DataVar)
    {
      for (int i = 0, n = ncvar->ndims; i < n; ++i)
      {
        int ncdimid = ncvar->dimids[i];
        int dimtype = ncdims[ncdimid].dimtype;
        if (dimtype >= X_AXIS && dimtype <= T_AXIS) cdf_set_dim(ncvar, i, dimtype);
      }

      if (CDI_Debug)
      {
        Message("var %d %s", varId, ncvar->name);
        for (int i = 0, n = ncvar->ndims; i < n; i++) printf("  dim%d type=%d  ", i, ncvar->dimtypes[i]);
        printf("\n");
      }
    }
  }

  for (int varId = 0; varId < numVars; ++varId)
  {
    ncvar_t *ncvar = &ncvars[varId];
    if (ncvar->varStatus == DataVar)
    {
      bool lxdim = false, lydim = false, lzdim = false /* , ltdim = false */;
      int lcdim;
      find_dimtypes(ncvars, ncvar, &lxdim, &lydim, &lzdim, &lcdim);
      int allcdims = lcdim;
      int ndims = ncvar->ndims;

      if (lxdim && (lydim || ncvar->gridtype == GRID_UNSTRUCTURED))
        for (int i = ndims - 1; i >= 0; i--)
        {
          if (ncvar->dimtypes[i] == -1 && !lzdim)
          {
            if (lcdim)
            {
              int cdimvar = ncvar->cvarids[allcdims - lcdim];
              ncvar->zvarid = cdimvar;
              lcdim--;
              ncvars[cdimvar].zaxistype = ZAXIS_CHAR;
            }
            cdf_set_dim(ncvar, i, Z_AXIS);
            lzdim = true;
            int ncdimid = ncvar->dimids[i];
            if (ncdims[ncdimid].dimtype == CDI_UNDEFID) ncdims[ncdimid].dimtype = Z_AXIS;
          }
        }
    }
  }

  for (int varId = 0; varId < numVars; ++varId)
  {
    ncvar_t *ncvar = &ncvars[varId];
    for (int i = 0, n = ncvar->ndims; i < n; ++i)
    {
      if (ncvar->dimtypes[i] == CDI_UNDEFID)
      {
        int ncdimid = ncvar->dimids[i];
        if (ncdims[ncdimid].dimtype == Z_AXIS)
        {
          ncvar->isZaxis = true;
          cdf_set_dim(ncvar, i, Z_AXIS);
        }
      }
    }
  }

  for (int varId = 0; varId < numVars; ++varId)
  {
    ncvar_t *ncvar = &ncvars[varId];
    if (ncvar->varStatus == DataVar)
    {
      bool lxdim = false, lydim = false, lzdim = false /*, ltdim = false */;
      int lcdim = 0;
      find_dimtypes(ncvars, ncvar, &lxdim, &lydim, &lzdim, &lcdim);
      int allcdims = lcdim;
      int ndims = ncvar->ndims;

      //   if ( ndims > 1 )
      for (int i = ndims - 1; i >= 0; i--)
      {
        if (ncvar->dimtypes[i] == -1)
        {
          int dimtype;
          if (!lxdim)
          {
            if (lcdim && ncvar->xvarid == CDI_UNDEFID)
            {
              int cdimvar = ncvar->cvarids[allcdims - lcdim];
              ncvar->xvarid = cdimvar;
              lcdim--;
            }
            dimtype = X_AXIS;
            lxdim = true;
          }
          else if (!lydim && ncvar->gridtype != GRID_UNSTRUCTURED && ncvar->isHealpixMapping == false)
          // else if ( !lydim && ! (ncvars[ncvar->xvarid].dimids[0] == ncvars[ncvar->yvarid].dimids[0] &&
          //                        ncvars[ncvar->xvarid].ndims == 1 && ncvars[ncvar->yvarid].ndims == 1))
          {
            if (lcdim && ncvar->yvarid == CDI_UNDEFID)
            {
              int cdimvar = ncvar->cvarids[allcdims - lcdim];
              ncvar->yvarid = cdimvar;
              lcdim--;
            }
            dimtype = Y_AXIS;
            lydim = true;
          }
          else if (!lzdim)
          {
            if (lcdim > 0)
            {
              int cdimvar = ncvar->cvarids[allcdims - lcdim];
              ncvar->zvarid = cdimvar;
              lcdim--;
              ncvars[cdimvar].zaxistype = ZAXIS_CHAR;
            }
            dimtype = Z_AXIS;
            lzdim = true;
          }
          else { continue; }
          cdf_set_dim(ncvar, i, dimtype);
        }
      }
      // if (lcdim > 0) Warning("Could not assign all character coordinates to data variable!");
    }
  }
}

static void
set_vardim_coord(ncvar_t *ncvar, ncdim_t *ncdim, int axisType)
{
  cdf_set_var(ncvar, CoordVar);
  cdf_set_dim(ncvar, 0, axisType);
  ncdim->dimtype = axisType;
}

static int
get_hybrid_zaxistype(const char *longname, const char *units)
{
  if (strStartsWith(longname, "hybrid"))
  {
    if (str_is_equal(longname, "hybrid level at layer midpoints")) return ZAXIS_HYBRID;
    if (str_is_equal(longname, "hybrid model level at layer midpoints")) return ZAXIS_HYBRID;
    if (strStartsWith(longname, "hybrid level at midpoints")) return ZAXIS_HYBRID;
    if (str_is_equal(longname, "hybrid level at layer interfaces")) return ZAXIS_HYBRID_HALF;
    if (str_is_equal(longname, "hybrid model level at layer interfaces")) return ZAXIS_HYBRID_HALF;
    if (strStartsWith(longname, "hybrid level at interfaces")) return ZAXIS_HYBRID_HALF;
  }
  if (str_is_equal(units, "level")) return ZAXIS_GENERIC;
  return CDI_UNDEFID;
}

// verify coordinates vars - first scan (dimname == varname)
static void
verify_coordinates_vars_1(int ncid, int ndims, ncdim_t *ncdims, ncvar_t *ncvars, int timedimid, bool *isHybridCF)
{
  for (int ncdimid = 0; ncdimid < ndims; ncdimid++)
  {
    ncdim_t *ncdim = &ncdims[ncdimid];
    int ncvarid = ncdim->ncvarid;
    if (ncvarid != -1)
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if (ncvar->dimids[0] == timedimid)
      {
        ncvar->isTaxis = true;
        ncdim->dimtype = T_AXIS;
        continue;
      }

      if (isHybridSigmaPressureCoordinate(ncid, ncvarid, ncvars, ncdims))
      {
        *isHybridCF = true;
        continue;
      }

      if (str_is_equal(ncvar->stdname, "healpix_index"))
      {
        ncvar->isIndexAxis = true;
        continue;
      }

      if (ncvar->units[0] != 0)
      {
        if (is_lon_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isLon = true;
          set_vardim_coord(ncvar, ncdim, X_AXIS);
        }
        else if (is_lat_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isLat = true;
          set_vardim_coord(ncvar, ncdim, Y_AXIS);
        }
        else if (is_x_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isXaxis = true;
          set_vardim_coord(ncvar, ncdim, X_AXIS);
        }
        else if (is_y_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isYaxis = true;
          set_vardim_coord(ncvar, ncdim, Y_AXIS);
        }
        else if (is_pressure_units(ncvar->units)) { ncvar->zaxistype = ZAXIS_PRESSURE; }
        else if (str_is_equal(ncvar->units, "level") || str_is_equal(ncvar->units, "1"))
        {
          int zaxistype = get_hybrid_zaxistype(ncvar->longname, ncvar->units);
          if (zaxistype != CDI_UNDEFID) ncvar->zaxistype = zaxistype;
        }
        else if (is_DBL_axis(ncvar->longname)) { ncvar->zaxistype = ZAXIS_DEPTH_BELOW_LAND; }
        else if (is_height_units(ncvar->units))
        {
          // clang-format off
          if      (is_depth_axis(ncvar->stdname, ncvar->longname))    ncvar->zaxistype = ZAXIS_DEPTH_BELOW_SEA;
          else if (is_height_axis(ncvar->stdname, ncvar->longname))   ncvar->zaxistype = ZAXIS_HEIGHT;
          else if (is_altitude_axis(ncvar->stdname, ncvar->longname)) ncvar->zaxistype = ZAXIS_ALTITUDE;
          // clang-format on
        }
      }
      else
      {
        // clang-format off
        if      (is_reference_axis(ncvar->stdname, ncvar->longname)) ncvar->zaxistype = ZAXIS_REFERENCE;
        else if (str_is_equal(ncvar->stdname, "air_pressure"))       ncvar->zaxistype = ZAXIS_PRESSURE;
        // clang-format on
      }

      if (!ncvar->isLon && (ncvar->longname[0] != 0) && !ncvar->isLat && (ncvar->longname[1] != 0))
      {
        if (strStartsWith(ncvar->longname + 1, "ongitude"))
        {
          ncvar->isLon = true;
          set_vardim_coord(ncvar, ncdim, X_AXIS);
          continue;
        }
        else if (strStartsWith(ncvar->longname + 1, "atitude"))
        {
          ncvar->isLat = true;
          set_vardim_coord(ncvar, ncdim, Y_AXIS);
          continue;
        }
      }

      if (ncvar->zaxistype != CDI_UNDEFID)
      {
        ncvar->isZaxis = true;
        set_vardim_coord(ncvar, ncdim, Z_AXIS);
      }
    }
  }
}

// verify coordinates vars - second scan (all other variables)
static void
verify_coordinates_vars_2(stream_t *streamptr, int nvars, ncvar_t *ncvars)
{
  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->varStatus == CoordVar)
    {
      if (str_is_equal(ncvar->stdname, "healpix_index"))
      {
        ncvar->isIndexAxis = true;
        continue;
      }
      else if (ncvar->units[0] != 0)
      {
        if (is_lon_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isLon = true;
          continue;
        }
        else if (is_lat_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isLat = true;
          continue;
        }
        else if (is_x_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isXaxis = true;
          continue;
        }
        else if (is_y_axis(ncvar->units, ncvar->stdname))
        {
          ncvar->isYaxis = true;
          continue;
        }
        else if (ncvar->zaxistype == CDI_UNDEFID && (str_is_equal(ncvar->units, "level") || str_is_equal(ncvar->units, "1")))
        {
          int zaxistype = get_hybrid_zaxistype(ncvar->longname, ncvar->units);
          if (zaxistype != CDI_UNDEFID) ncvar->zaxistype = zaxistype;
          continue;
        }
        else if (ncvar->zaxistype == CDI_UNDEFID && is_pressure_units(ncvar->units))
        {
          ncvar->zaxistype = ZAXIS_PRESSURE;
          continue;
        }
        else if (is_DBL_axis(ncvar->longname))
        {
          ncvar->zaxistype = ZAXIS_DEPTH_BELOW_LAND;
          continue;
        }
        else if (is_height_units(ncvar->units))
        {
          // clang-format off
          if      (is_depth_axis(ncvar->stdname, ncvar->longname))  ncvar->zaxistype = ZAXIS_DEPTH_BELOW_SEA;
          else if (is_height_axis(ncvar->stdname, ncvar->longname)) ncvar->zaxistype = ZAXIS_HEIGHT;
          // clang-format on
          continue;
        }
      }
      else if (str_is_equal(ncvar->stdname, "region") || str_is_equal(ncvar->stdname, "area_type")
               || cdfInqDatatype(streamptr, ncvar->xtype, ncvar->isUnsigned) == CDI_DATATYPE_UINT8)
      {
        ncvar->isCharAxis = true;
      }
      else if (str_is_equal(ncvar->stdname, "air_pressure")) { ncvar->zaxistype = ZAXIS_PRESSURE; }

      // not needed anymore for rotated grids
      if (!ncvar->isLon && (ncvar->longname[0] != 0) && !ncvar->isLat && (ncvar->longname[1] != 0))
      {
        if (strStartsWith(ncvar->longname + 1, "ongitude"))
        {
          ncvar->isLon = true;
          continue;
        }
        else if (strStartsWith(ncvar->longname + 1, "atitude"))
        {
          ncvar->isLat = true;
          continue;
        }
      }
    }
  }
}

static void
grid_set_chunktype(grid_t *grid, ncvar_t *ncvar)
{
  if (ncvar->isChunked)
  {
    int ndims = ncvar->ndims;
    size_t chunkSizeAllDims = 1;
    for (int i = 0; i < ndims; ++i) chunkSizeAllDims *= ncvar->chunks[i];

    size_t dimN = ncvar->chunks[ndims - 1];
    if (grid->type == GRID_UNSTRUCTURED)
    {
      size_t chunkSize = (chunkSizeAllDims == dimN) ? dimN : 0;
      ncvar->chunkType = (chunkSize == grid->size) ? CDI_CHUNK_GRID : CDI_CHUNK_AUTO;
      if (ncvar->chunkType == CDI_CHUNK_AUTO && chunkSize > 1) ncvar->chunkSize = (int) chunkSize;
    }
    else
    {
      if (grid->x.size > 1 && grid->y.size > 1 && ndims > 1 && grid->x.size == dimN && grid->y.size == ncvar->chunks[ndims - 2])
        ncvar->chunkType = CDI_CHUNK_GRID;
      else if (grid->x.size > 1 && grid->x.size == dimN && chunkSizeAllDims == dimN)
        ncvar->chunkType = CDI_CHUNK_LINES;
      else
        ncvar->chunkType = CDI_CHUNK_AUTO;
    }
  }
}

// define all input grids
static void
cdf_load_vals(size_t size, int ndims, int varid, ncvar_t *ncvar, double **gridvals, struct xyValGet *valsGet, bool hasTimeDim,
              bool readPart, size_t *start, size_t *count)
{
  if (CDI_Netcdf_Lazy_Grid_Load)
  {
    *valsGet = (struct xyValGet){
      .scalefactor = ncvar->scalefactor,
      .addoffset = ncvar->addoffset,
      .start = { start[0], start[1], start[2] },
      .count = { count[0], count[1], count[2] },
      .size = size,
      .datasetNCId = ncvar->ncid,
      .varNCId = varid,
      .ndims = (short) ndims,
    };
    *gridvals = cdfPendingLoad;
  }
  else
  {
    *gridvals = (double *) Malloc(size * sizeof(double));
    if (hasTimeDim || readPart)
      cdf_get_vara_double(ncvar->ncid, varid, start, count, *gridvals);
    else
      cdf_get_var_double(ncvar->ncid, varid, *gridvals);
    cdf_scale_add(size, *gridvals, ncvar->addoffset, ncvar->scalefactor);
  }
}

#ifndef USE_MPI
static void
cdf_load_cvals(size_t size, int varid, ncvar_t *ncvar, char ***gridvals, size_t dimlength)
{
  size_t startc[] = { 0, 0 };
  size_t countc[] = { 1, size / dimlength };
  *gridvals = (char **) Malloc(dimlength * sizeof(char *));
  for (size_t i = 0; i < dimlength; i++)
  {
    (*gridvals)[i] = (char *) Malloc((size / dimlength) * sizeof(char));
    cdf_get_vara_text(ncvar->ncid, varid, startc, countc, (*gridvals)[i]);
    startc[0] = i + 1;
  }
}
#endif

static void
cdf_load_bounds(size_t size, ncvar_t *ncvar, double **gridbounds, struct cdfLazyGridIds *cellBoundsGet, bool readPart,
                size_t *start, size_t *count)
{
  if (CDI_Netcdf_Lazy_Grid_Load)
  {
    cellBoundsGet->datasetNCId = ncvar->ncid;
    cellBoundsGet->varNCId = ncvar->bounds;
    *gridbounds = cdfPendingLoad;
  }
  else
  {
    *gridbounds = (double *) Malloc(size * sizeof(double));
    if (readPart)
      cdf_get_vara_double(ncvar->ncid, ncvar->bounds, start, count, *gridbounds);
    else
      cdf_get_var_double(ncvar->ncid, ncvar->bounds, *gridbounds);
  }
}

static void
cdf_load_bounds_cube_sphere(size_t bxsize, size_t bysize, size_t size, ncvar_t *ncvar, double **gridbounds,
                            struct cdfLazyGridIds *cellBoundsGet)
{
  if (CDI_Netcdf_Lazy_Grid_Load)
  {
    cellBoundsGet->datasetNCId = ncvar->ncid;
    cellBoundsGet->varNCId = ncvar->bounds;
    *gridbounds = cdfPendingLoad;
  }
  else
  {
    float *bounds = (float *) Malloc(6 * bxsize * bysize * sizeof(float));
    cdf_get_var_float(ncvar->ncid, ncvar->bounds, bounds);

    *gridbounds = (double *) Malloc(size * sizeof(double));
    double *pbounds = *gridbounds;

    size_t m = 0;
    for (size_t k = 0; k < 6; ++k)
      for (size_t j = 0; j < (bysize - 1); ++j)
        for (size_t i = 0; i < (bxsize - 1); ++i)
        {
          size_t offset = k * bysize * bxsize;
          pbounds[m + 0] = bounds[offset + (j + 1) * bxsize + i];
          pbounds[m + 1] = bounds[offset + j * bxsize + i];
          pbounds[m + 2] = bounds[offset + j * bxsize + (i + 1)];
          pbounds[m + 3] = bounds[offset + (j + 1) * bxsize + (i + 1)];
          m += 4;
        }

    Free(bounds);
  }
}

static void
cdf_load_cellarea(size_t size, ncvar_t *ncvar, double **gridarea, struct cdfLazyGridIds *cellAreaGet)
{
  if (CDI_Netcdf_Lazy_Grid_Load)
  {
    cellAreaGet->datasetNCId = ncvar->ncid;
    cellAreaGet->varNCId = ncvar->cellarea;
    *gridarea = cdfPendingLoad;
  }
  else
  {
    *gridarea = (double *) Malloc(size * sizeof(double));
    cdf_get_var_double(ncvar->ncid, ncvar->cellarea, *gridarea);
  }
}

static void
cdf_load_cellindices(size_t size, ncvar_t *ncvar, int64_t **cellIndices, bool readPart, size_t start, size_t count)
{
  *cellIndices = (int64_t *) Malloc(size * sizeof(int64_t));
  if (readPart)
    cdf_get_vara_int64(ncvar->ncid, ncvar->ivarid, &start, &count, *cellIndices);
  else
    cdf_get_var_int64(ncvar->ncid, ncvar->ivarid, *cellIndices);
}

static void
set_cellindices(int64_t *cellIndices, size_t start, size_t count)
{
  for (size_t i = 0; i < count; ++i) { cellIndices[i] = start + i; }
}

static void
cdf_copy_grid_axis_attr(ncvar_t *ncvar, struct gridaxis_t *gridaxis)
{
  cdiDefVarKeyBytes(&gridaxis->keys, CDI_KEY_NAME, (const unsigned char *) ncvar->name, (int) strlen(ncvar->name) + 1);
  if (ncvar->longname[0])
    cdiDefVarKeyBytes(&gridaxis->keys, CDI_KEY_LONGNAME, (const unsigned char *) ncvar->longname,
                      (int) strlen(ncvar->longname) + 1);
  if (ncvar->units[0])
    cdiDefVarKeyBytes(&gridaxis->keys, CDI_KEY_UNITS, (const unsigned char *) ncvar->units, (int) strlen(ncvar->units) + 1);
#ifndef USE_MPI
  if (gridaxis->cvals)
    if (ncvar->stdname[0])
      cdiDefVarKeyBytes(&gridaxis->keys, CDI_KEY_STDNAME, (const unsigned char *) ncvar->stdname, (int) strlen(ncvar->stdname) + 1);
#endif
}

static int
cdf_get_xydimid(int ndims, int *dimids, int *dimtypes, int *xdimid, int *ydimid)
{
  int nxdims = 0, nydims = 0;
  int xdimids[2] = { -1, -1 }, ydimids[2] = { -1, -1 };

  for (int i = 0; i < ndims; i++)
  {
    if (dimtypes[i] == X_AXIS && nxdims < 2)
    {
      xdimids[nxdims] = dimids[i];
      nxdims++;
    }
    else if (dimtypes[i] == Y_AXIS && nydims < 2)
    {
      ydimids[nydims] = dimids[i];
      nydims++;
    }
  }

  if (nxdims == 2)
  {
    *xdimid = xdimids[1];
    *ydimid = xdimids[0];
  }
  else if (nydims == 2)
  {
    *xdimid = ydimids[1];
    *ydimid = ydimids[0];
  }
  else
  {
    *xdimid = xdimids[0];
    *ydimid = ydimids[0];
  }

  return nydims;
}

static void
cdf_check_gridtype(int *gridtype, bool isLon, bool isLat, size_t xsize, size_t ysize, grid_t *grid)
{
  if (grid->y.vals == NULL)
  {
    *gridtype = GRID_GENERIC;
    return;
  }

  if (isLat && (isLon || xsize == 0))
  {
    double yinc = 0.0;
    if (isLon && ysize > 1)
    {
      yinc = fabs(grid->y.vals[0] - grid->y.vals[1]);
      for (size_t i = 2; i < ysize; i++)
        if ((fabs(grid->y.vals[i - 1] - grid->y.vals[i]) - yinc) > (yinc / 1000))
        {
          yinc = 0;
          break;
        }
    }
    if (ysize < 10000 && IS_EQUAL(yinc, 0.0) && isGaussianLatitudes(ysize, grid->y.vals))
    {
      *gridtype = GRID_GAUSSIAN;
      grid->np = (int) (ysize / 2);
    }
    else { *gridtype = GRID_LONLAT; }
  }
  else { *gridtype = (isLon && !isLat && ysize == 0) ? GRID_LONLAT : GRID_GENERIC; }
}

static bool
cdf_read_xcoord(stream_t *streamptr, struct cdfLazyGrid *lazyGrid, ncdim_t *ncdims, ncvar_t *ncvar, int xvarid, ncvar_t *axisvar,
                size_t *xsize, size_t ysize, bool hasTimeDim, bool readPart, size_t *start, size_t *count, bool *isLon)
{
  grid_t *grid = &lazyGrid->base;
  bool skipvar = true;
  *isLon = axisvar->isLon;
  int ndims = axisvar->ndims;
  size_t size = 0;

  if (ndims == 1 && xtypeIsText(axisvar->xtype))
  {
    ncvar->varStatus = CoordVar;
    Warning("Unsupported x-coordinate type (char/string), skipped variable %s!", ncvar->name);
    return true;
  }

  int datatype = cdfInqDatatype(streamptr, axisvar->xtype, axisvar->isUnsigned);

  if ((ndims - hasTimeDim) == 2)
  {
    // Check size of 2 dimensional coordinates variables
    int dimid = axisvar->dimids[ndims - 2];
    size_t dimsize1 = ncdims[dimid].len;
    dimid = axisvar->dimids[ndims - 1];
    size_t dimsize2 = ncdims[dimid].len;

    if (datatype == CDI_DATATYPE_UINT8)
    {
      ncvar->gridtype = GRID_CHARXY;
      size = dimsize1 * dimsize2;
      skipvar = (dimsize1 != *xsize);
    }
    else
    {
      ncvar->gridtype = GRID_CURVILINEAR;
      size = (*xsize) * ysize;
      skipvar = (dimsize1 * dimsize2 != size);
    }
  }
  else if ((ndims - hasTimeDim) == 1)
  {
    size = *xsize;
    // Check size of 1 dimensional coordinates variables
    int dimid = axisvar->dimids[ndims - 1];
    size_t dimsize = ncdims[dimid].len;
    skipvar = readPart ? false : (dimsize != size);
  }
  else if (ndims == 0 && *xsize == 0)
  {
    size = *xsize = 1;
    skipvar = false;
  }
  else if (ncvar->isCubeSphere)
  {
    size = *xsize;
    skipvar = false;
  }

  if (skipvar)
  {
    Warning("Unsupported array structure, skipped variable %s!", ncvar->name);
    ncvar->varStatus = UndefVar;
    return true;
  }

  if (datatype != -1) grid->datatype = datatype;

  if (datatype == CDI_DATATYPE_UINT8 && !CDI_Netcdf_Lazy_Grid_Load)
  {
#ifndef USE_MPI
    cdf_load_cvals(size, xvarid, axisvar, &grid->x.cvals, *xsize);
    grid->x.clength = size / (*xsize);
#endif
  }
  else if (CDI_Read_Cell_Center)
  {
    cdf_load_vals(size, ndims, xvarid, axisvar, &grid->x.vals, &lazyGrid->xValsGet, hasTimeDim, readPart, start, count);
  }

  cdf_copy_grid_axis_attr(axisvar, &grid->x);

  return false;
}

static bool
cdf_read_ycoord(stream_t *streamptr, struct cdfLazyGrid *lazyGrid, ncdim_t *ncdims, ncvar_t *ncvar, int yvarid, ncvar_t *axisvar,
                size_t xsize, size_t *ysize, bool hasTimeDim, bool readPart, size_t *start, size_t *count, bool *isLat)
{
  grid_t *grid = &lazyGrid->base;
  bool skipvar = true;
  *isLat = axisvar->isLat;
  int ndims = axisvar->ndims;
  size_t size = 0;

  if (ndims == 1 && xtypeIsText(axisvar->xtype))
  {
    ncvar->varStatus = CoordVar;
    Warning("Unsupported y-coordinate type (char/string), skipped variable %s!", ncvar->name);
    return true;
  }

  int datatype = cdfInqDatatype(streamptr, axisvar->xtype, axisvar->isUnsigned);

  if ((ndims - hasTimeDim) == 2)
  {
    // Check size of 2 dimensional coordinates variables
    int dimid = axisvar->dimids[ndims - 2];
    size_t dimsize1 = ncdims[dimid].len;
    dimid = axisvar->dimids[ndims - 1];
    size_t dimsize2 = ncdims[dimid].len;

    if (datatype == CDI_DATATYPE_UINT8)
    {
      ncvar->gridtype = GRID_CHARXY;
      size = dimsize1 * dimsize2;
      skipvar = (dimsize1 != *ysize);
    }
    else
    {
      ncvar->gridtype = GRID_CURVILINEAR;
      size = xsize * (*ysize);
      skipvar = (dimsize1 * dimsize2 != size);
    }
  }
  else if ((ndims - hasTimeDim) == 1)
  {
    size = (*ysize) ? *ysize : xsize;
    int dimid = axisvar->dimids[ndims - 1];
    size_t dimsize = ncdims[dimid].len;
    skipvar = readPart ? false : (dimsize != size);
  }
  else if (ndims == 0 && *ysize == 0)
  {
    size = *ysize = 1;
    skipvar = false;
  }
  else if (ncvar->isCubeSphere)
  {
    size = *ysize;
    skipvar = false;
  }

  if (skipvar)
  {
    Warning("Unsupported array structure, skipped variable %s!", ncvar->name);
    ncvar->varStatus = UndefVar;
    return true;
  }

  if (datatype != -1) grid->datatype = datatype;

  if (datatype == CDI_DATATYPE_UINT8 && !CDI_Netcdf_Lazy_Grid_Load)
  {
#ifndef USE_MPI
    cdf_load_cvals(size, yvarid, axisvar, &grid->y.cvals, *ysize);
    grid->y.clength = size / (*ysize);
#endif
  }
  else if (CDI_Read_Cell_Center)
  {
    cdf_load_vals(size, ndims, yvarid, axisvar, &grid->y.vals, &lazyGrid->yValsGet, hasTimeDim, readPart, start, count);
  }

  cdf_copy_grid_axis_attr(axisvar, &grid->y);

  return false;
}

typedef struct
{
  long start;
  long count;
  bool readPart;
} GridPart;

static void
gridpart_init(GridPart *gridPart)
{
  gridPart->start = -1;
  gridPart->count = -1;
  gridPart->readPart = false;
}

static void
cdf_load_xbounds(struct cdfLazyGrid *lazyGrid, ncvar_t *ncvar, ncvar_t *ncvars, ncdim_t *ncdims, int timedimid, int xvarid,
                 int *vdimid, bool readPart, size_t *start, size_t *count)
{
  grid_t *grid = &lazyGrid->base;
  size_t size = grid->size;
  grid->x.flag = 1;
  int bvarid = ncvars[xvarid].bounds;
  if (bvarid != CDI_UNDEFID)
  {
    int ndims = ncvars[xvarid].ndims;
    int nbdims = ncvars[bvarid].ndims;
    if (nbdims == 2 || nbdims == 3)
    {
      if (ncvars[bvarid].dimids[0] == timedimid)
      {
        static bool ltwarn = true;
        if (ltwarn) Warning("Time varying grid x-bounds unsupported, skipped!");
        ltwarn = false;
      }
      else if (ncvar->isCubeSphere)
      {
        grid->nvertex = 4;
        size_t bxsize = ncdims[ncvars[bvarid].dimids[2]].len;
        size_t bysize = ncdims[ncvars[bvarid].dimids[1]].len;
        cdf_load_bounds_cube_sphere(bxsize, bysize, size * (size_t) grid->nvertex, &ncvars[xvarid], &grid->x.bounds,
                                    &lazyGrid->xBoundsGet);
      }
      else if (nbdims == ndims + 1)
      {
        *vdimid = ncvars[bvarid].dimids[nbdims - 1];
        grid->nvertex = (int) ncdims[*vdimid].len;
        if (readPart)
        {
          start[1] = 0;
          count[1] = (size_t) grid->nvertex;
        }
        cdf_load_bounds(size * (size_t) grid->nvertex, &ncvars[xvarid], &grid->x.bounds, &lazyGrid->xBoundsGet, readPart, start,
                        count);
      }
      else
      {
        static bool lwarn = true;
        if (lwarn) Warning("x-bounds doesn't follow the CF-Convention, skipped!");
        lwarn = false;
      }
    }
  }
}

static void
cdf_load_ybounds(struct cdfLazyGrid *lazyGrid, ncvar_t *ncvar, ncvar_t *ncvars, ncdim_t *ncdims, int timedimid, int yvarid,
                 int *vdimid, bool readPart, size_t *start, size_t *count)
{
  grid_t *grid = &lazyGrid->base;
  size_t size = grid->size;
  grid->y.flag = 1;
  int bvarid = ncvars[yvarid].bounds;
  if (bvarid != CDI_UNDEFID)
  {
    int ndims = ncvars[yvarid].ndims;
    int nbdims = ncvars[bvarid].ndims;
    if (nbdims == 2 || nbdims == 3)
    {
      if (ncvars[bvarid].dimids[0] == timedimid)
      {
        static bool ltwarn = true;
        if (ltwarn) Warning("Time varying grid y-bounds unsupported, skipped!");
        ltwarn = false;
      }
      else if (ncvar->isCubeSphere)
      {
        grid->nvertex = 4;
        size_t bxsize = ncdims[ncvars[bvarid].dimids[2]].len;
        size_t bysize = ncdims[ncvars[bvarid].dimids[1]].len;
        cdf_load_bounds_cube_sphere(bxsize, bysize, size * (size_t) grid->nvertex, &ncvars[yvarid], &grid->y.bounds,
                                    &lazyGrid->yBoundsGet);
      }
      else if (nbdims == ndims + 1)
      {
        if (*vdimid == CDI_UNDEFID)
        {
          *vdimid = ncvars[bvarid].dimids[nbdims - 1];
          grid->nvertex = (int) ncdims[*vdimid].len;
        }
        if (readPart)
        {
          start[1] = 0;
          count[1] = (size_t) grid->nvertex;
        }
        cdf_load_bounds(size * (size_t) grid->nvertex, &ncvars[yvarid], &grid->y.bounds, &lazyGrid->yBoundsGet, readPart, start,
                        count);
      }
      else
      {
        static bool lwarn = true;
        if (lwarn) Warning("y-bounds doesn't follow the CF-Convention, skipped!");
        lwarn = false;
      }
    }
  }
}

static void
cdf_load_ybounds_reduced(struct cdfLazyGrid *lazyGrid, ncvar_t *ncvar, ncvar_t *ncvars, ncdim_t *ncdims, int yvarid, int *vdimid)
{
  grid_t *grid = &lazyGrid->base;
  size_t size = grid->size;
  int dimid = ncvars[ncvar->rpvarid].dimids[0];
  size_t len = ncdims[dimid].len;
  grid->y.size = len;
  assert(len <= INT_MAX);
  grid->reducedPointsSize = (int) len;
  grid->reducedPoints = (int *) Malloc(len * sizeof(int));
  cdf_get_var_int(ncvar->ncid, ncvar->rpvarid, grid->reducedPoints);
  grid->np = ncvar->numLPE;

  int bvarid = (yvarid == CDI_UNDEFID) ? CDI_UNDEFID : ncvars[yvarid].bounds;
  if (bvarid != CDI_UNDEFID)
  {
    int nbdims = ncvars[bvarid].ndims;
    if (nbdims == 2 || nbdims == 3)
    {
      if (*vdimid == CDI_UNDEFID)
      {
        *vdimid = ncvars[bvarid].dimids[nbdims - 1];
        grid->nvertex = (int) ncdims[*vdimid].len;
      }
      cdf_load_bounds(size * (size_t) grid->nvertex, &ncvars[yvarid], &grid->y.bounds, &lazyGrid->yBoundsGet, false, NULL, NULL);
    }
  }
}

static bool
cdf_read_coordinates(stream_t *streamptr, struct cdfLazyGrid *lazyGrid, ncvar_t *ncvar, ncvar_t *ncvars, ncdim_t *ncdims,
                     int timedimid, int xvarid, int yvarid, size_t xsize, size_t ysize, int *vdimid, const GridPart *gridPart)
{
  grid_t *grid = &lazyGrid->base;
  size_t size = 0;
  size_t start[3] = { 0, 0, 0 }, count[3] = { 1, 1, 1 };
  bool readPart = false;

  grid->datatype = CDI_DATATYPE_FLT64;

  if (ncvar->gridtype == GRID_TRAJECTORY)
  {
    if (ncvar->xvarid == CDI_UNDEFID) Error("Longitude coordinates undefined for %s!", ncvar->name);
    if (ncvar->yvarid == CDI_UNDEFID) Error("Latitude coordinates undefined for %s!", ncvar->name);
  }
  else
  {
    bool hasTimeDim = false;

    if (xvarid != CDI_UNDEFID && yvarid != CDI_UNDEFID)
    {
      int ndims = ncvars[xvarid].ndims;
      if (ndims != ncvars[yvarid].ndims && !ncvars[xvarid].isCharAxis && !ncvars[yvarid].isCharAxis)
      {
        Warning("Inconsistent grid structure for variable %s!", ncvar->name);
        ncvar->xvarid = xvarid = CDI_UNDEFID;
        ncvar->yvarid = yvarid = CDI_UNDEFID;
      }
      if (ndims > 1)
      {
        if (ndims <= 3)
        {
          if (ncvars[xvarid].dimids[0] == timedimid && ncvars[yvarid].dimids[0] == timedimid)
          {
            static bool ltwarn = true;
            size_t ntsteps = 0;
            cdf_inq_dimlen(ncvar->ncid, ncdims[timedimid].dimid, &ntsteps);
            if (ltwarn && ntsteps > 1) Warning("Time varying grids unsupported, using grid at time step 1!");
            ltwarn = false;
            hasTimeDim = true;
            count[1] = ysize;
            count[2] = xsize;
          }
        }
        else
        {
          Warning("Unsupported grid structure for variable %s (grid dims > 2)!", ncvar->name);
          ncvar->xvarid = xvarid = CDI_UNDEFID;
          ncvar->yvarid = yvarid = CDI_UNDEFID;
        }
      }
      else if (gridPart && gridPart->readPart)
      {
        start[0] = (size_t) gridPart->start;
        count[0] = (size_t) gridPart->count;
        readPart = true;
      }
    }
    else if (grid->type == GRID_HEALPIX && gridPart && gridPart->readPart)
    {
      start[0] = (size_t) gridPart->start;
      count[0] = (size_t) gridPart->count;
      readPart = true;
    }

    if (xvarid != CDI_UNDEFID)
    {
      if (!ncvar->isCubeSphere && (ncvars[xvarid].ndims - hasTimeDim) > 2)
      {
        Warning("Coordinates variable %s has too many dimensions (%d), skipped!", ncvars[xvarid].name, ncvars[xvarid].ndims);
        // ncvar->xvarid = CDI_UNDEFID;
        xvarid = CDI_UNDEFID;
      }
    }

    if (yvarid != CDI_UNDEFID)
    {
      if (!ncvar->isCubeSphere && (ncvars[yvarid].ndims - hasTimeDim) > 2)
      {
        Warning("Coordinates variable %s has too many dimensions (%d), skipped!", ncvars[yvarid].name, ncvars[yvarid].ndims);
        // ncvar->yvarid = CDI_UNDEFID;
        yvarid = CDI_UNDEFID;
      }
    }

    bool isLon = false, isLat = false;

    if (xvarid != CDI_UNDEFID)
      if (cdf_read_xcoord(streamptr, lazyGrid, ncdims, ncvar, xvarid, &ncvars[xvarid], &xsize, ysize, hasTimeDim, readPart, start,
                          count, &isLon))
        return true;

    if (yvarid != CDI_UNDEFID)
      if (cdf_read_ycoord(streamptr, lazyGrid, ncdims, ncvar, yvarid, &ncvars[yvarid], xsize, &ysize, hasTimeDim, readPart, start,
                          count, &isLat))
        return true;

    // clang-format off
    if      (ncvar->gridtype == GRID_UNSTRUCTURED)     size = xsize;
    else if (ncvar->gridtype == GRID_HEALPIX)          size = xsize;
    else if (ncvar->gridtype == GRID_GAUSSIAN_REDUCED) size = xsize;
    else if (ysize == 0)                               size = xsize;
    else if (xsize == 0)                               size = ysize;
    else                                               size = xsize * ysize;
    // clang-format on

    if (ncvar->gridtype == CDI_UNDEFID || ncvar->gridtype == GRID_GENERIC)
      cdf_check_gridtype(&ncvar->gridtype, isLon, isLat, xsize, ysize, grid);
  }

  int gridType = grid->type;
  if (gridType != GRID_PROJECTION) { gridType = ncvar->gridtype; }
  else if (gridType == GRID_PROJECTION && ncvar->gridtype == GRID_LONLAT && ncvar->isLonLatMapping) { gridType = ncvar->gridtype; }

  switch (gridType)
  {
    case GRID_GENERIC:
    case GRID_LONLAT:
    case GRID_GAUSSIAN:
    case GRID_UNSTRUCTURED:
    case GRID_CURVILINEAR:
    case GRID_PROJECTION:
    {
      grid->size = size;
      grid->x.size = xsize;
      grid->y.size = ysize;
      if (xvarid != CDI_UNDEFID && CDI_Read_Cell_Corners)
      {
        cdf_load_xbounds(lazyGrid, ncvar, ncvars, ncdims, timedimid, xvarid, vdimid, readPart, start, count);
      }
      if (yvarid != CDI_UNDEFID && CDI_Read_Cell_Corners)
      {
        cdf_load_ybounds(lazyGrid, ncvar, ncvars, ncdims, timedimid, yvarid, vdimid, readPart, start, count);
      }

      if (ncvar->cellarea != CDI_UNDEFID) cdf_load_cellarea(size, ncvar, &grid->area, &lazyGrid->cellAreaGet);

      if (gridType == GRID_GAUSSIAN && ncvar->numLPE > 0) grid->np = ncvar->numLPE;

      break;
    }
    case GRID_HEALPIX:
    {
      grid->size = size;
      grid->x.size = size;
      if (ncvar->ivarid != CDI_UNDEFID) { cdf_load_cellindices(size, ncvar, &grid->indices, readPart, start[0], count[0]); }
      else if (readPart)
      {
        grid->indices = (int64_t *) Malloc(count[0] * sizeof(int64_t));
        set_cellindices(grid->indices, start[0], count[0]);
      }
      break;
    }
    case GRID_GAUSSIAN_REDUCED:
    {
      if (ncvar->numLPE > 0 && ncvar->rpvarid != CDI_UNDEFID && ncvars[ncvar->rpvarid].ndims == 1)
      {
        grid->size = size;
        cdf_load_ybounds_reduced(lazyGrid, ncvar, ncvars, ncdims, yvarid, vdimid);
      }
      break;
    }
    case GRID_SPECTRAL:
    {
      grid->size = size;
      grid->lcomplex = 1;
      grid->trunc = ncvar->truncation;
      break;
    }
    case GRID_FOURIER:
    {
      grid->size = size;
      grid->trunc = ncvar->truncation;
      break;
    }
    case GRID_TRAJECTORY:
    {
      grid->size = 1;
      break;
    }
    case GRID_CHARXY:
    {
      grid->size = size;
      grid->x.size = xsize;
      grid->y.size = ysize;
      break;
    }
  }

  // if ( grid->type != GRID_PROJECTION && grid->type != ncvar->gridtype )
  if (grid->type != gridType)
  {
    // int gridtype = ncvar->gridtype;
    grid->type = gridType;
    cdiGridTypeInit(grid, gridType, grid->size);
  }

  if (grid->size == 0)
  {
    int ndims = ncvar->ndims;
    int *dimtypes = ncvar->dimtypes;
    if (ndims == 0 || (ndims == 1 && dimtypes[0] == T_AXIS) || (ndims == 1 && dimtypes[0] == Z_AXIS)
        || (ndims == 2 && dimtypes[0] == T_AXIS && dimtypes[1] == Z_AXIS))
    {
      grid->type = GRID_GENERIC;
      grid->size = 1;
      grid->x.size = 0;
      grid->y.size = 0;
    }
    else
    {
      Warning("Unsupported grid, skipped variable %s!", ncvar->name);
      ncvar->varStatus = UndefVar;
      return true;
    }
  }

  return false;
}

static bool
cdf_set_unstructured_par(ncvar_t *ncvar, grid_t *grid, int *xdimid, int *ydimid, GridInfo *gridInfo, bool readPart)
{
  int ndims = ncvar->ndims;
  int *dimtypes = ncvar->dimtypes;

  int zdimid = CDI_UNDEFID;
  int xdimidx = CDI_UNDEFID, ydimidx = CDI_UNDEFID;

  for (int i = 0; i < ndims; i++)
  {
    // clang-format off
    if      (dimtypes[i] == X_AXIS) xdimidx = i;
    else if (dimtypes[i] == Y_AXIS) ydimidx = i;
    else if (dimtypes[i] == Z_AXIS) zdimid = ncvar->dimids[i];
    // clang-format on
  }

  if (*xdimid != CDI_UNDEFID && *ydimid != CDI_UNDEFID && zdimid == CDI_UNDEFID)
  {
    if (grid->x.size > grid->y.size && grid->y.size < 1000)
    {
      dimtypes[ydimidx] = Z_AXIS;
      *ydimid = CDI_UNDEFID;
      grid->size = grid->x.size;
      grid->y.size = 0;
    }
    else if (grid->y.size > grid->x.size && grid->x.size < 1000)
    {
      dimtypes[xdimidx] = Z_AXIS;
      *xdimid = *ydimid;
      *ydimid = CDI_UNDEFID;
      grid->size = grid->y.size;
      grid->x.size = grid->y.size;
      grid->y.size = 0;
    }
  }

  if (grid->size != grid->x.size)
  {
    Warning("Unsupported array structure, skipped variable %s!", ncvar->name);
    ncvar->varStatus = UndefVar;
    return true;
  }

  if (!readPart)
  {
    if (gridInfo->number_of_grid_used != CDI_UNDEFID)
      cdiDefVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDUSED, gridInfo->number_of_grid_used);
    if (ncvar->position > 0) cdiDefVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE, ncvar->position);
    if (!cdiUUIDIsNull(gridInfo->uuid)) cdiDefVarKeyBytes(&grid->keys, CDI_KEY_UUID, gridInfo->uuid, CDI_UUID_SIZE);
  }

  return false;
}

static void
cdf_read_mapping_atts(int ncid, int gmapvarid, int nvatts, int projID)
{
  if (cdfCheckAttText(ncid, gmapvarid, "grid_mapping_name"))
  {
    char attstring[CDI_MAX_NAME];
    cdfGetAttText(ncid, gmapvarid, "grid_mapping_name", CDI_MAX_NAME, attstring);
    cdiDefKeyString(projID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, attstring);
  }

  bool removeFillValue = true;
  for (int i = 0; i < nvatts; ++i) cdf_set_cdi_attr(ncid, gmapvarid, i, projID, CDI_GLOBAL, removeFillValue);
}

static void
cdf_set_grid_to_similar_vars(ncvar_t *ncvar1, ncvar_t *ncvar2, int gridtype, int xdimid, int ydimid)
{
  if (ncvar2->varStatus == DataVar && ncvar2->gridID == CDI_UNDEFID)
  {
    int xdimid2 = CDI_UNDEFID, ydimid2 = CDI_UNDEFID, zdimid2 = CDI_UNDEFID;
    int xdimidx = CDI_UNDEFID, ydimidx = CDI_UNDEFID;

    const int *dimtypes2 = ncvar2->dimtypes;
    const int *dimids2 = ncvar2->dimids;
    int ndims2 = ncvar2->ndims;
    for (int i = 0; i < ndims2; i++)
    {
      if (dimtypes2[i] == X_AXIS)
      {
        xdimid2 = dimids2[i];
        xdimidx = i;
      }
      else if (dimtypes2[i] == Y_AXIS)
      {
        ydimid2 = dimids2[i];
        ydimidx = i;
      }
      else if (dimtypes2[i] == Z_AXIS) { zdimid2 = dimids2[i]; }
    }

    if (!ncvar2->isCubeSphere)
    {
      if (ncvar2->gridtype == CDI_UNDEFID && gridtype == GRID_UNSTRUCTURED)
      {
        if (xdimid == xdimid2 && ydimid2 != CDI_UNDEFID && zdimid2 == CDI_UNDEFID)
        {
          ncvar2->dimtypes[ydimidx] = Z_AXIS;
          ydimid2 = CDI_UNDEFID;
        }

        if (xdimid == ydimid2 && xdimid2 != CDI_UNDEFID && zdimid2 == CDI_UNDEFID)
        {
          ncvar2->dimtypes[xdimidx] = Z_AXIS;
          xdimid2 = ydimid2;
          ydimid2 = CDI_UNDEFID;
        }
      }
      else if (ncvar2->gridtype == GRID_GAUSSIAN_REDUCED && gridtype == GRID_GAUSSIAN_REDUCED) { ydimid = CDI_UNDEFID; }
    }

    if (xdimid == xdimid2 && (ydimid == ydimid2 || (xdimid == ydimid && ydimid2 == CDI_UNDEFID)))
    {
      bool isSameGrid
          = (ncvar1->xvarid == ncvar2->xvarid && ncvar1->yvarid == ncvar2->yvarid && ncvar1->position == ncvar2->position);

      // if (xvarid != -1 && ncvar2->xvarid != CDI_UNDEFID && xvarid != ncvar2->xvarid) isSameGrid = false;
      // if (yvarid != -1 && ncvar2->yvarid != CDI_UNDEFID && yvarid != ncvar2->yvarid) isSameGrid = false;

      if (isSameGrid)
      {
        if (CDI_Debug) Message("Same gridID %d %s", ncvar1->gridID, ncvar2->name);
        ncvar2->gridID = ncvar1->gridID;
        ncvar2->chunkType = ncvar1->chunkType;
        ncvar2->chunkSize = ncvar1->chunkSize;
        ncvar2->gridSize = ncvar1->gridSize;
        ncvar2->xSize = ncvar1->xSize;
        ncvar2->ySize = ncvar1->ySize;
        ncvar2->xstart = ncvar1->xstart;
        ncvar2->xcount = ncvar1->xcount;
      }
    }
  }
}

static void
destroy_grid(struct cdfLazyGrid *lazyGrid, grid_t *grid)
{
  if (lazyGrid)
  {
    if (CDI_Netcdf_Lazy_Grid_Load) cdfLazyGridDestroy(lazyGrid);
    if (grid)
    {
      grid_free(grid);
      Free(grid);
    }
  }
}

static bool
is_healpix_grid(int ncid, int gmapvarid)
{
  if (gmapvarid == CDI_UNDEFID) return false;
  return cdfCheckAttInt(ncid, gmapvarid, "refinement_level");
}

static int
process_layer_query(struct CdiQuery *query, ncvar_t *ncvar, size_t *zsize, GridPart *layerPart)
{
  // process layer query information if available
  if (query)
  {
    int numLayers = cdiQueryNumLayers(query);
    if (numLayers > 0)
    {
      // Error("Query of layers not implemented!");
      size_t start = cdiQueryGetLayer(query, 0) - 1;
      size_t count = (numLayers == 2) ? cdiQueryGetLayer(query, 1) : 1;
      if ((start + count) > *zsize)
      {
        if (*zsize > 1)
          Warning("%s: layer %zu-%zu out of range (numLayers=%zu), skipped", ncvar->name, start + 1, start + count - 1, *zsize);
        ncvar->varStatus = UndefVar;
        return -1;
      }

      *zsize = count;
      ncvar->zstart = start;
      ncvar->zcount = count;
      layerPart->start = (long) start;
      layerPart->count = (long) count;
      layerPart->readPart = true;
    }
  }

  return 0;
}

static int
process_cell_query(struct CdiQuery *query, int xdimid, int ydimid, ncvar_t *ncvar, size_t *xsize, size_t *ysize, GridPart *gridPart)
{
  // process grid query information if available
  if (query)
  {
    int numCells = cdiQueryNumCells(query);
    if (numCells > 0)
    {
      // if (ncvar->gridtype != GRID_UNSTRUCTURED)
      if (xdimid != CDI_UNDEFID && ydimid != CDI_UNDEFID && xdimid != ydimid)
      {
        Warning("Query parameter cell is only available for 1D grids, skipped variable %s!", ncvar->name);
        ncvar->varStatus = UndefVar;
        return -1;
      }

      size_t start = cdiQueryGetCell(query, 0);
      size_t count = (numCells == 2) ? cdiQueryGetCell(query, 1) : 1;
      if ((start + count) > *xsize)
      {
        Error("%s: cells %zu-%zu out of range (gridsize=%zu)", ncvar->name, start, start + count - 1, *xsize);
      }

      *xsize = count;
      *ysize = 0;
      ncvar->xstart = start - 1;
      ncvar->xcount = count;
      gridPart->start = (long) start - 1;
      gridPart->count = (long) count;
      gridPart->readPart = true;
    }
  }

  return 0;
}

static int
cdf_define_all_grids(stream_t *streamptr, int vlistID, ncdim_t *ncdims, int nvars, ncvar_t *ncvars, GridInfo *gridInfo)
{
  for (int ncvarid = 0; ncvarid < nvars; ++ncvarid)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->varStatus == DataVar && ncvar->gridID == CDI_UNDEFID)
    {
      int ndims = ncvar->ndims;
      int *dimtypes = ncvar->dimtypes;
      int vdimid = CDI_UNDEFID;
      struct addIfNewRes projAdded = { .Id = CDI_UNDEFID, .isNew = 0 }, gridAdded = { .Id = CDI_UNDEFID, .isNew = 0 };
      int xdimid = CDI_UNDEFID, ydimid = CDI_UNDEFID;
      int nydims = cdf_get_xydimid(ndims, ncvar->dimids, dimtypes, &xdimid, &ydimid);

      int xaxisid = (xdimid != CDI_UNDEFID) ? ncdims[xdimid].ncvarid : CDI_UNDEFID;
      int yaxisid = (ydimid != CDI_UNDEFID) ? ncdims[ydimid].ncvarid : CDI_UNDEFID;
      int xvarid = (ncvar->xvarid != CDI_UNDEFID) ? ncvar->xvarid : xaxisid;
      int yvarid = (ncvar->yvarid != CDI_UNDEFID) ? ncvar->yvarid : yaxisid;

      size_t xsize = (xdimid != CDI_UNDEFID) ? ncdims[xdimid].len : 0;
      size_t ysize = (ydimid != CDI_UNDEFID) ? ncdims[ydimid].len : 0;

      if (ydimid == CDI_UNDEFID && yvarid != CDI_UNDEFID)
      {
        if (ncvars[yvarid].ndims == 1)
        {
          ydimid = ncvars[yvarid].dimids[0];
          ysize = ncdims[ydimid].len;
        }
      }

      int gmapvarid = ncvar->gmapid;
      bool lproj = (gmapvarid != CDI_UNDEFID);
      bool isHealpixGrid = (lproj && ncvar->isHealpixMapping) ? is_healpix_grid(ncvars[gmapvarid].ncid, gmapvarid) : false;
      if (isHealpixGrid) { ncvar->gridtype = GRID_HEALPIX; }
      if (isHealpixGrid && xvarid != CDI_UNDEFID && ncvar->ivarid == CDI_UNDEFID)
      {
        ncvar->ivarid = xvarid;
        xvarid = CDI_UNDEFID;
      }

      if (!lproj && xaxisid != CDI_UNDEFID && xaxisid != xvarid && yaxisid != CDI_UNDEFID && yaxisid != yvarid) lproj = true;

      if (ncvar->isCubeSphere && lproj && xvarid != CDI_UNDEFID && yvarid != CDI_UNDEFID && ncvars[xvarid].ndims == 3
          && ncvars[yvarid].ndims == 3)
      {
        lproj = false;
        ncvar->gridtype = GRID_UNSTRUCTURED;
        xsize = xsize * ysize * 6;
        ysize = xsize;
      }

      bool lgrid = !(lproj && ncvar->xvarid == CDI_UNDEFID);

      bool isUnstructured = (xdimid != CDI_UNDEFID && xdimid == ydimid && nydims == 0);
      if ((ncvar->gridtype == CDI_UNDEFID || ncvar->gridtype == GRID_GENERIC) && isUnstructured)
        ncvar->gridtype = GRID_UNSTRUCTURED;

      struct cdfLazyGrid *lazyGrid = NULL, *lazyProj = NULL;

      {
        int gridtype = (!lgrid && !isHealpixGrid) ? GRID_PROJECTION : ncvar->gridtype;
        if (CDI_Netcdf_Lazy_Grid_Load)
        {
          cdfLazyGridRenew(&lazyGrid, gridtype);
          if (lgrid && lproj) cdfLazyGridRenew(&lazyProj, GRID_PROJECTION);
        }
        else
        {
          cdfBaseGridRenew(&lazyGrid, gridtype);
          if (lgrid && lproj) cdfBaseGridRenew(&lazyProj, GRID_PROJECTION);
        }
      }
      grid_t *grid = &lazyGrid->base;
      grid_t *proj = (lgrid && lproj) ? &lazyProj->base : NULL;

      xaxisid = (xdimid != CDI_UNDEFID) ? ncdims[xdimid].ncvarid : CDI_UNDEFID;
      yaxisid = (ydimid != CDI_UNDEFID) ? ncdims[ydimid].ncvarid : CDI_UNDEFID;

      // process cell query information if available
      GridPart gridPart;
      gridpart_init(&gridPart);
      if (process_cell_query(streamptr->query, xdimid, ydimid, ncvar, &xsize, &ysize, &gridPart) < 0) { continue; }

      if (cdf_read_coordinates(streamptr, lazyGrid, ncvar, ncvars, ncdims, gridInfo->timedimid, xvarid, yvarid, xsize, ysize,
                               &vdimid, &gridPart))
      {
        continue;
      }

      if (gridInfo->number_of_grid_used != CDI_UNDEFID && (grid->type == CDI_UNDEFID || grid->type == GRID_GENERIC)
          && xdimid != CDI_UNDEFID && xsize > 999)
        grid->type = GRID_UNSTRUCTURED;

      if (!ncvar->isCubeSphere && grid->type == GRID_UNSTRUCTURED)
        if (cdf_set_unstructured_par(ncvar, grid, &xdimid, &ydimid, gridInfo, gridPart.readPart)) continue;

      if (lgrid && lproj)
      {
        int dimid;
        cdf_read_coordinates(streamptr, lazyProj, ncvar, ncvars, ncdims, gridInfo->timedimid, xaxisid, yaxisid, xsize, ysize,
                             &dimid, NULL);
      }

      if (CDI_Debug)
      {
        Message("grid: type = %d, size = %zu, nx = %zu, ny = %zu", grid->type, grid->size, grid->x.size, grid->y.size);
        if (proj) Message("proj: type = %d, size = %zu, nx = %zu, ny = %zu", proj->type, proj->size, proj->x.size, proj->y.size);
      }

      if (lgrid && lproj)
      {
        projAdded = cdiVlistAddGridIfNew(vlistID, proj, 2);
        grid->proj = projAdded.Id;
      }

      gridAdded = cdiVlistAddGridIfNew(vlistID, grid, 1);
      ncvar->gridID = gridAdded.Id;
      ncvar->gridSize = grid->size;
      ncvar->xSize = grid->x.size;
      ncvar->ySize = grid->y.size;

      int gridID = ncvar->gridID;

      if (grid->type == GRID_HEALPIX && ncvar->ivarid != CDI_UNDEFID)
      {
        if (!xtypeIsInt64(ncvars[ncvar->ivarid].xtype)) cdiDefKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, CDI_DATATYPE_INT32);
        if (xdimid != CDI_UNDEFID) cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_DIMNAME, ncdims[xdimid].name);
      }

      if (lproj && gmapvarid != CDI_UNDEFID)
      {
        bool gridIsNew = lgrid ? projAdded.isNew : gridAdded.isNew;
        if (gridIsNew)
        {
          int projID = lgrid ? grid->proj : gridID;
          int ncid = ncvars[gmapvarid].ncid;
          int gmapvartype = ncvars[gmapvarid].xtype;
          int nvatts = ncvars[gmapvarid].nattsNC;
          cdiDefKeyInt(projID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARTYPE, gmapvartype);
          const char *gmapvarname = ncvars[gmapvarid].name;
          cdf_read_mapping_atts(ncid, gmapvarid, nvatts, projID);
          cdiDefKeyString(projID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname);
          gridVerifyProj(projID);
        }
      }

      if (grid->type == GRID_UNSTRUCTURED && gridInfo->gridfile[0] != 0 && !gridPart.readPart)
        gridDefReference(gridID, gridInfo->gridfile);

      if (ncvar->isChunked) grid_set_chunktype(grid, ncvar);

      int gridIndex = vlistGridIndex(vlistID, gridID);
      CdfGrid *cdfGrid = &streamptr->cdfInfo.cdfGridList[gridIndex];
      if (gridPart.readPart)
      {
        cdfGrid->start = gridPart.start;
        cdfGrid->count = gridPart.count;
      }
      cdfGrid->gridID = gridID;
      if (grid->type == GRID_TRAJECTORY)
      {
        cdfGrid->ncIdList[CDF_VARID_X] = xvarid;
        cdfGrid->ncIdList[CDF_VARID_Y] = yvarid;
      }
      else
      {
        if (xdimid != CDI_UNDEFID) { cdfGrid->ncIdList[CDF_DIMID_X] = ncdims[xdimid].dimid; }
        if (ydimid != CDI_UNDEFID) { cdfGrid->ncIdList[CDF_DIMID_Y] = ncdims[ydimid].dimid; }
        if (ncvar->isCubeSphere) { cdfGrid->ncIdList[CDF_DIMID_E] = ncdims[ncvar->dimids[ndims - 3]].dimid; }
      }

      if (xdimid == CDI_UNDEFID && ydimid == CDI_UNDEFID && grid->size == 1) gridDefHasDims(gridID, CoordVar);

      if (xdimid != CDI_UNDEFID) cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_DIMNAME, ncdims[xdimid].name);
      if (ydimid != CDI_UNDEFID) cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_DIMNAME, ncdims[ydimid].name);
      if (vdimid != CDI_UNDEFID) cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_VDIMNAME, ncdims[vdimid].name);

      if (xvarid != CDI_UNDEFID && ncvars[xvarid].stdname[0])
        cdiDefKeyString(gridID, CDI_XAXIS, CDI_KEY_STDNAME, ncvars[xvarid].stdname);
      if (yvarid != CDI_UNDEFID && ncvars[yvarid].stdname[0])
        cdiDefKeyString(gridID, CDI_YAXIS, CDI_KEY_STDNAME, ncvars[yvarid].stdname);

      if (CDI_Debug) Message("gridID %d %d %s", gridID, ncvarid, ncvar->name);

      for (int ncvarid2 = ncvarid + 1; ncvarid2 < nvars; ncvarid2++)
        cdf_set_grid_to_similar_vars(ncvar, &ncvars[ncvarid2], grid->type, xdimid, ydimid);

      if (gridAdded.isNew) { lazyGrid = NULL; }
      if (projAdded.isNew) { lazyProj = NULL; }

      if (lazyGrid) destroy_grid(lazyGrid, grid);
      if (lazyProj) destroy_grid(lazyProj, proj);
    }
  }

  return 0;
}

// define all input zaxes
static int
cdf_define_all_zaxes(stream_t *streamptr, int vlistID, ncdim_t *ncdims, int nvars, ncvar_t *ncvars, size_t vctsize_echam,
                     double *vct_echam, unsigned char *uuidOfVGrid)
{
  size_t vctsize = vctsize_echam;
  double *vct = vct_echam;

  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->varStatus == DataVar && ncvar->zaxisID == CDI_UNDEFID)
    {
      bool isScalar = false;
      int zdimid = CDI_UNDEFID;
      int zvarid = CDI_UNDEFID;
      size_t zsize = 1;
      int psvarid = -1;
      int p0varid = -1;

      int positive = 0;
      int ndims = ncvar->ndims;

      if (ncvar->zvarid != -1 && ncvars[ncvar->zvarid].ndims == 0)
      {
        zvarid = ncvar->zvarid;
        isScalar = true;
      }
      else
      {
        for (int i = 0; i < ndims; i++)
        {
          if (ncvar->dimtypes[i] == Z_AXIS) zdimid = ncvar->dimids[i];
        }

        if (zdimid != CDI_UNDEFID)
        {
          // zvarid = ncdims[zdimid].ncvarid;
          zvarid = (ncvar->zvarid != CDI_UNDEFID) ? ncvar->zvarid : ncdims[zdimid].ncvarid;
          zsize = ncdims[zdimid].len;
        }
      }

      if (CDI_Debug) Message("nlevs = %zu", zsize);

      // process layer query information if available
      GridPart layerPart;
      gridpart_init(&layerPart);
      if (process_layer_query(streamptr->query, ncvar, &zsize, &layerPart) < 0) { continue; }

      double *zvar = NULL;
      char **zcvals = NULL;
      size_t zclength = 0;

      int zaxisType = CDI_UNDEFID;
      if (zvarid != CDI_UNDEFID) zaxisType = ncvars[zvarid].zaxistype;
      if (zaxisType == CDI_UNDEFID) zaxisType = ZAXIS_GENERIC;

      int zdatatype = CDI_DATATYPE_FLT64;
      double *lbounds = NULL;
      double *ubounds = NULL;

      char *pname = NULL, *plongname = NULL, *punits = NULL, *pstdname = NULL;
      if (zvarid != CDI_UNDEFID)
      {
        positive = ncvars[zvarid].positive;
        pname = ncvars[zvarid].name;
        plongname = ncvars[zvarid].longname;
        punits = ncvars[zvarid].units;
        pstdname = ncvars[zvarid].stdname;
        // clang-format off
        if      (ncvars[zvarid].xtype == NC_FLOAT) { zdatatype = CDI_DATATYPE_FLT32; }
        else if (ncvars[zvarid].xtype == NC_INT)   { zdatatype = CDI_DATATYPE_INT32; }
        else if (ncvars[zvarid].xtype == NC_SHORT) { zdatatype = CDI_DATATYPE_INT16; }
        // clang-format on

#ifndef USE_MPI
        if (zaxisType == ZAXIS_CHAR && ncvars[zvarid].ndims == 2)
        {
          zdatatype = CDI_DATATYPE_UINT8;
          zclength = ncdims[ncvars[zvarid].dimids[1]].len;
          cdf_load_cvals(zsize * zclength, zvarid, ncvar, &zcvals, zsize);
        }
#endif

        if ((zaxisType == ZAXIS_HYBRID || zaxisType == ZAXIS_HYBRID_HALF) && ncvars[zvarid].vct)
        {
          vct = ncvars[zvarid].vct;
          vctsize = ncvars[zvarid].vctsize;
          if (ncvars[zvarid].psvarid != -1) psvarid = ncvars[zvarid].psvarid;
          if (ncvars[zvarid].p0varid != -1) p0varid = ncvars[zvarid].p0varid;
        }

        if (zaxisType != ZAXIS_CHAR)
        {
          if (layerPart.count > 0 && layerPart.count == (long) zsize)
          {
            size_t start = layerPart.start;
            size_t count = layerPart.count;
            zvar = (double *) Malloc(zsize * sizeof(double));
            cdf_get_vara_double(ncvars[zvarid].ncid, zvarid, &start, &count, zvar);
          }
          else
          {
            zvar = (double *) Malloc(zsize * sizeof(double));
            cdf_get_var_double(ncvars[zvarid].ncid, zvarid, zvar);
          }
        }

        int boundsId = ncvars[zvarid].bounds;
        if (boundsId != CDI_UNDEFID)
        {
          int nbdims = ncvars[boundsId].ndims;
          if (nbdims == 2 || isScalar)
          {
            size_t nlevel = isScalar ? 1 : ncdims[ncvars[boundsId].dimids[0]].len;
            int nvertex = (int) ncdims[ncvars[boundsId].dimids[1 - isScalar]].len;
            if (nlevel == zsize && nvertex == 2)
            {
              lbounds = (double *) Malloc(4 * nlevel * sizeof(double));
              ubounds = lbounds + nlevel;
              double *zbounds = lbounds + 2 * nlevel;
              cdf_get_var_double(ncvars[zvarid].ncid, boundsId, zbounds);
              for (size_t i = 0; i < nlevel; ++i)
              {
                lbounds[i] = zbounds[i * 2];
                ubounds[i] = zbounds[i * 2 + 1];
              }
            }
          }
        }
      }
      else
      {
        pname = (zdimid != CDI_UNDEFID) ? ncdims[zdimid].name : NULL;

        if (zsize == 1 && zdimid == CDI_UNDEFID)
        {
          zaxisType = (ncvar->zaxistype != CDI_UNDEFID) ? ncvar->zaxistype : ZAXIS_SURFACE;
          // if ( pname )
          {
            zvar = (double *) Malloc(sizeof(double));
            zvar[0] = 0;
          }
        }
      }

      if (zsize > INT_MAX)
      {
        Warning("Size limit exceeded for z-axis dimension (limit=%d)!", INT_MAX);
        return CDI_EDIMSIZE;
      }

      ncvar->zaxisID = varDefZaxis(vlistID, zaxisType, (int) zsize, zvar, (const char **) zcvals, zclength, lbounds, ubounds,
                                   (int) vctsize, vct, pname, plongname, punits, zdatatype, 1, 0, -1);
      ncvar->zSize = zsize;

      int zaxisID = ncvar->zaxisID;

      int zaxisIndex = vlistZaxisIndex(vlistID, zaxisID);
      CdfZaxis *cdfZaxis = &streamptr->cdfInfo.cdfZaxisList[zaxisIndex];
      if (layerPart.readPart)
      {
        cdfZaxis->start = layerPart.start;
        cdfZaxis->count = layerPart.count;
        cdfZaxis->zaxisID = zaxisID;
      }

      if (CDI_CMOR_Mode && zsize == 1 && zaxisType != ZAXIS_HYBRID) zaxisDefScalar(zaxisID);

      if (pstdname && *pstdname)
        cdiDefKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_STDNAME, (const unsigned char *) pstdname, (int) strlen(pstdname) + 1);

      if (!cdiUUIDIsNull(uuidOfVGrid)) cdiDefKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuidOfVGrid, CDI_UUID_SIZE);

      if (zaxisType == ZAXIS_HYBRID)
      {
        if (psvarid != -1) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_PSNAME, ncvars[psvarid].name);
        if (p0varid != -1)
        {
          double px = 1;
          cdf_get_var_double(ncvars[p0varid].ncid, p0varid, &px);
          cdiDefKeyFloat(zaxisID, CDI_GLOBAL, CDI_KEY_P0VALUE, px);
          cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_P0NAME, ncvars[p0varid].name);
        }
      }

      if (positive > 0) zaxisDefPositive(zaxisID, positive);
      if (isScalar) zaxisDefScalar(zaxisID);

      if (zdimid != CDI_UNDEFID) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_DIMNAME, ncdims[zdimid].name);

      if (zvar) Free(zvar);
      if (zcvals)
      {
        for (size_t i = 0; i < zsize; i++) Free(zcvals[i]);
        Free(zcvals);
      }
      if (lbounds) Free(lbounds);

      if (zvarid != CDI_UNDEFID)
      {
        int ncid = ncvars[zvarid].ncid;
        int nvatts = ncvars[zvarid].natts;
        for (int iatt = 0; iatt < nvatts; ++iatt)
        {
          int attnum = ncvars[zvarid].atts[iatt];
          cdf_set_cdi_attr(ncid, zvarid, attnum, zaxisID, CDI_GLOBAL, false);
        }
      }

      streamptr->cdfInfo.zaxisIdList[zaxisIndex] = (zdimid >= 0) ? ncdims[zdimid].dimid : zdimid;

      if (CDI_Debug) Message("zaxisID %d %d %s", zaxisID, ncvarid, ncvar->name);

      for (int ncvarid2 = ncvarid + 1; ncvarid2 < nvars; ncvarid2++)
        if (ncvars[ncvarid2].varStatus == DataVar
            && ncvars[ncvarid2].zaxisID == CDI_UNDEFID /*&& ncvars[ncvarid2].zaxistype == CDI_UNDEFID*/)
        {
          int zvarid2 = CDI_UNDEFID;
          if (ncvars[ncvarid2].zvarid != CDI_UNDEFID && ncvars[ncvars[ncvarid2].zvarid].ndims == 0)
            zvarid2 = ncvars[ncvarid2].zvarid;

          int zdimid2 = CDI_UNDEFID;
          ndims = ncvars[ncvarid2].ndims;
          for (int i = 0; i < ndims; i++)
          {
            if (ncvars[ncvarid2].dimtypes[i] == Z_AXIS) zdimid2 = ncvars[ncvarid2].dimids[i];
          }

          if (zdimid == zdimid2 /* && zvarid == zvarid2 */)
          {
            if ((zdimid != CDI_UNDEFID && ncvars[ncvarid2].zaxistype == CDI_UNDEFID)
                || (zdimid == CDI_UNDEFID && zvarid != CDI_UNDEFID && zvarid == zvarid2)
                || (zdimid == CDI_UNDEFID && zaxisType == ncvars[ncvarid2].zaxistype)
                || (zdimid == CDI_UNDEFID && zvarid2 == CDI_UNDEFID && ncvars[ncvarid2].zaxistype == CDI_UNDEFID))
            {
              if (CDI_Debug) Message("zaxisID %d %d %s", zaxisID, ncvarid2, ncvars[ncvarid2].name);
              ncvars[ncvarid2].zaxisID = zaxisID;
              ncvars[ncvarid2].zSize = zsize;
            }
          }
        }
    }
  }

  return 0;
}

struct cdf_varinfo
{
  int varid;
  const char *name;
};

static int
cdf_cmp_varname(const void *s1, const void *s2)
{
  const struct cdf_varinfo *x = (const struct cdf_varinfo *) s1, *y = (const struct cdf_varinfo *) s2;
  return strcmp(x->name, y->name);
}

static void
cdf_sort_varnames(int *varids, int nvars, const ncvar_t *ncvars)
{
  struct cdf_varinfo *varInfo = (struct cdf_varinfo *) Malloc((size_t) nvars * sizeof(struct cdf_varinfo));

  for (int varID = 0; varID < nvars; varID++)
  {
    int ncvarid = varids[varID];
    varInfo[varID].varid = ncvarid;
    varInfo[varID].name = ncvars[ncvarid].name;
  }
  qsort(varInfo, (size_t) nvars, sizeof(varInfo[0]), cdf_cmp_varname);
  for (int varID = 0; varID < nvars; varID++) { varids[varID] = varInfo[varID].varid; }
  Free(varInfo);
  if (CDI_Debug)
    for (int i = 0; i < nvars; i++) Message("sorted varids[%d] = %d", i, varids[i]);
}

static void
cdf_define_code_and_param(int vlistID, int varID)
{
  if (vlistInqVarCode(vlistID, varID) == -varID - 1)
  {
    char name[CDI_MAX_NAME];
    name[0] = 0;
    vlistInqVarName(vlistID, varID, name);
    size_t len = strlen(name);
    if (len > 3 && isdigit((int) name[3]))
    {
      if (strStartsWith(name, "var")) vlistDefVarCode(vlistID, varID, atoi(name + 3));
    }
    else if (len > 4 && isdigit((int) name[4]))
    {
      if (strStartsWith(name, "code")) vlistDefVarCode(vlistID, varID, atoi(name + 4));
    }
    else if (len > 5 && isdigit((int) name[5]))
    {
      if (strStartsWith(name, "param"))
      {
        int pnum = -1, pcat = 255, pdis = 255;
        sscanf(name + 5, "%d.%d.%d", &pnum, &pcat, &pdis);
        vlistDefVarParam(vlistID, varID, cdiEncodeParam(pnum, pcat, pdis));
      }
    }
  }
}

static void
cdf_define_institut_and_model_id(int vlistID, int varID)
{
  int varInstID = vlistInqVarInstitut(vlistID, varID);
  int varModelID = vlistInqVarModel(vlistID, varID);
  int varTableID = vlistInqVarTable(vlistID, varID);
  int code = vlistInqVarCode(vlistID, varID);
  if (CDI_Default_TableID != CDI_UNDEFID)
  {
    char name[CDI_MAX_NAME];
    name[0] = 0;
    char longname[CDI_MAX_NAME];
    longname[0] = 0;
    char units[CDI_MAX_NAME];
    units[0] = 0;
    tableInqEntry(CDI_Default_TableID, code, -1, name, longname, units);
    if (name[0])
    {
      cdiDeleteKey(vlistID, varID, CDI_KEY_NAME);
      cdiDeleteKey(vlistID, varID, CDI_KEY_LONGNAME);
      cdiDeleteKey(vlistID, varID, CDI_KEY_UNITS);

      if (varTableID != CDI_UNDEFID)
      {
        cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, name);
        if (longname[0]) cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, longname);
        if (units[0]) cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, units);
      }
      else { varTableID = CDI_Default_TableID; }
    }

    if (CDI_Default_ModelID != CDI_UNDEFID) varModelID = CDI_Default_ModelID;
    if (CDI_Default_InstID != CDI_UNDEFID) varInstID = CDI_Default_InstID;
  }
  if (varInstID != CDI_UNDEFID) vlistDefVarInstitut(vlistID, varID, varInstID);
  if (varModelID != CDI_UNDEFID) vlistDefVarModel(vlistID, varID, varModelID);
  if (varTableID != CDI_UNDEFID) vlistDefVarTable(vlistID, varID, varTableID);
}

static inline size_t
size_of_dim_chunks(size_t n, size_t c)
{
  return (n / c + (n % c > 0)) * c;
}

typedef struct
{
  size_t cacheSize;
  size_t start[MAX_DIMENSIONS];
  size_t count[MAX_DIMENSIONS];
  int numDims;
} ChunkCacheValues;

static void
chunkCacheValues_init(ChunkCacheValues *chunkCacheValues)
{
  chunkCacheValues->cacheSize = 0;
  chunkCacheValues->numDims = 0;
  for (int i = 0; i < MAX_DIMENSIONS; ++i) { chunkCacheValues->start[i] = 0; }
  for (int i = 0; i < MAX_DIMENSIONS; ++i) { chunkCacheValues->count[i] = 0; }
}

static ChunkCacheValues
calc_chunk_cache_size(int timedimid, ncvar_t *ncvar)
{
  ChunkCacheValues chunkCacheValues;
  chunkCacheValues_init(&chunkCacheValues);
  int numDims = 0;
  size_t nx = ncvar->xSize, ny = ncvar->ySize, nz = ncvar->zSize;
  size_t cx = 0, cy = 0, cz = 0;
  for (int i = 0; i < ncvar->ndims; i++)
  {
    int dimtype = ncvar->dimtypes[i];
    // clang-format off
    if      (dimtype == Z_AXIS) { cz = ncvar->chunks[i]; }
    else if (dimtype == Y_AXIS) { cy = ncvar->chunks[i]; }
    else if (dimtype == X_AXIS) { cx = ncvar->chunks[i]; }
    // clang-format on
  }

  size_t numSteps = (ncvar->dimids[0] == timedimid) ? ncvar->chunks[0] : 1;
  size_t chunkCacheSize = numSteps;
  size_t numLevels = 0;
  if (nz > 0 && cz > 0)
  {
    numLevels = (numSteps == 1) ? cz : size_of_dim_chunks(nz, cz);
    chunkCacheSize *= numLevels;
  }

  if (chunkCacheSize == 1) { chunkCacheSize = 0; }  // no chunk cache needed because the full field is read
  else
  {
    if (ny > 0 && cy > 0) { chunkCacheSize *= size_of_dim_chunks(ny, cy); }
    if (nx > 0 && cx > 0) { chunkCacheSize *= size_of_dim_chunks(nx, cx); }

    chunkCacheSize *= cdf_xtype_to_numbytes(ncvar->xtype);
    if (CDI_CacheSize_Max > 0 && chunkCacheSize > CDI_CacheSize_Max) { chunkCacheSize = CDI_CacheSize_Max; }
  }

  chunkCacheValues.cacheSize = chunkCacheSize;
  chunkCacheValues.numDims = numDims;
  return chunkCacheValues;
}

static ChunkCacheValues
get_chunk_cache_size(int timedimid, ncvar_t *ncvar)
{
  ChunkCacheValues chunkCacheValues;
  chunkCacheValues_init(&chunkCacheValues);
  int numDims = 0;
  size_t nxc = ncvar->xcount, nzc = ncvar->zcount;
  size_t nx = ncvar->xSize, ny = ncvar->ySize, nz = ncvar->zSize;
  size_t cx = 0, cy = 0, cz = 0;
  for (int i = 0; i < ncvar->ndims; i++)
  {
    int dimtype = ncvar->dimtypes[i];
    // clang-format off
    if      (dimtype == Z_AXIS) { cz = ncvar->chunks[i]; }
    else if (dimtype == Y_AXIS) { cy = ncvar->chunks[i]; }
    else if (dimtype == X_AXIS) { cx = ncvar->chunks[i]; }
    // clang-format on
  }

  size_t numSteps = (ncvar->dimids[0] == timedimid) ? ncvar->chunks[0] : 1;
  size_t chunkCacheSize = numSteps;
  size_t numLevels = 0;
  if (nz > 0 && cz > 0)
  {
    numLevels = (nzc > 0) ? nzc : ((numSteps == 1) ? cz : nz);
    chunkCacheSize *= numLevels;
  }

  if (chunkCacheSize == 1) { chunkCacheSize = 0; }  // no chunk cache needed because the full field is read
  else
  {
    if (numSteps > 0) { chunkCacheValues.count[numDims++] = numSteps; }
    if (numLevels > 0)
    {
      if (nzc > 0) chunkCacheValues.start[numDims] = ncvar->zstart;
      chunkCacheValues.count[numDims++] = numLevels;
    }
    if (ny > 0 && cy > 0)
    {
      size_t size = ny;
      chunkCacheSize *= size;
      chunkCacheValues.count[numDims++] = size;
    }
    if (nx > 0 && cx > 0)
    {
      size_t size = (nxc > 0) ? nxc : nx;
      chunkCacheSize *= size;
      if (nxc > 0) chunkCacheValues.start[numDims] = ncvar->xstart;
      chunkCacheValues.count[numDims++] = size;
    }

    chunkCacheSize *= cdf_xtype_to_numbytes(ncvar->xtype);
    if (CDI_CacheSize_Max > 0 && chunkCacheSize > CDI_CacheSize_Max) { chunkCacheSize = CDI_CacheSize_Max; }
  }

  chunkCacheValues.cacheSize = chunkCacheSize;
  chunkCacheValues.numDims = numDims;
  return chunkCacheValues;
}

static void
cdf_set_var_chunk_cache(ncvar_t *ncvar, int ncvarid, size_t chunkCacheSize)
{
  if (CDI_Debug || CDI_Cache_Info) Message("%s: chunkCacheSize=%zu", ncvar->name, chunkCacheSize);
  nc_set_var_chunk_cache(ncvar->ncid, ncvarid, chunkCacheSize, ncvar->chunkCacheNelems, ncvar->chunkCachePreemption);
}

static void
cdf_init_cache(CdfCache *cache)
{
  cache->buffer = NULL;
  for (int i = 0; i < MAX_DIMENSIONS; ++i) { cache->start[i] = 0; }
  for (int i = 0; i < MAX_DIMENSIONS; ++i) { cache->count[i] = 0; }
  cache->bufferSize = 0;
  cache->cacheSize = 0;
  cache->numSteps = 0;
  cache->maxSteps = 0;
  cache->numDims = 0;
}

// define all input data variables
static void
cdf_define_all_vars(stream_t *streamptr, int vlistID, int instID, int modelID, int nvars, int num_ncvars, ncvar_t *ncvars,
                    ncdim_t *ncdims, int timedimid)
{
  int *varids = (int *) Malloc((size_t) nvars * sizeof(int));
  int n = 0;
  for (int ncvarid = 0; ncvarid < num_ncvars; ncvarid++)
    if (ncvars[ncvarid].varStatus == DataVar) { varids[n++] = ncvarid; }

  if (CDI_Debug)
    for (int i = 0; i < nvars; i++) Message("varids[%d] = %d", i, varids[i]);

  if (streamptr->sortname) cdf_sort_varnames(varids, nvars, ncvars);

  for (int varID1 = 0; varID1 < nvars; varID1++)
  {
    int ncvarid = varids[varID1];
    ncvar_t *ncvar = &ncvars[ncvarid];
    int gridID = ncvar->gridID;
    int zaxisID = ncvar->zaxisID;

    stream_new_var(streamptr, gridID, zaxisID, CDI_UNDEFID);
    int varID = vlistDefVar(vlistID, gridID, zaxisID, ncvar->timetype);
    ncvar->cdiVarID = varID;

    if (ncvar->hasFilter) cdiDefKeyString(vlistID, varID, CDI_KEY_FILTERSPEC_IN, ncvar->filterSpec);
    if (ncvar->hasFilter) vlistDefVarCompType(vlistID, varID, CDI_COMPRESS_FILTER);
    if (ncvar->hasDeflate) vlistDefVarCompType(vlistID, varID, CDI_COMPRESS_ZIP);
    if (ncvar->hasSzip) vlistDefVarCompType(vlistID, varID, CDI_COMPRESS_SZIP);
    if (ncvar->isChunked)
    {
      if (ncvar->chunkType != CDI_UNDEFID) cdiDefKeyInt(vlistID, varID, CDI_KEY_CHUNKTYPE, ncvar->chunkType);
      if (ncvar->chunkSize > 1) cdiDefKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE, ncvar->chunkSize);

      bool cacheGrid = (ncvar->gridtype == GRID_UNSTRUCTURED || ncvar->isHealpixMapping);
      bool cacheFormat
          = (streamptr->filetype == CDI_FILETYPE_NCZARR && CDI_Cache_NCZ)
            || ((streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C) && CDI_Cache_NC4);
      ChunkCacheValues chunkCacheValues
          = (cacheFormat && cacheGrid) ? get_chunk_cache_size(timedimid, ncvar) : calc_chunk_cache_size(timedimid, ncvar);
      size_t cacheSize = chunkCacheValues.cacheSize;
      if (cacheSize > 0 && cacheFormat && cacheGrid && chunkCacheValues.numDims <= 3)
      {
        CdfCache *cdfCache = (CdfCache *) Malloc(sizeof(CdfCache));
        cdf_init_cache(cdfCache);
        cdfCache->cacheSize = cacheSize;
        cdfCache->numDims = chunkCacheValues.numDims;
        cdfCache->maxSteps = (timedimid == CDI_UNDEFID) ? 0 : ncdims[timedimid].len;
        for (int i = 0; i < chunkCacheValues.numDims; ++i) { cdfCache->start[i] = chunkCacheValues.start[i]; }
        for (int i = 0; i < chunkCacheValues.numDims; ++i) { cdfCache->count[i] = chunkCacheValues.count[i]; }
        if (CDI_Debug || CDI_Cache_Info) Message("%s: chunkCacheSize=%zu", ncvar->name, cacheSize);
        streamptr->vars[varID].cdfCache = cdfCache;
        cdf_set_var_chunk_cache(ncvar, ncvarid, 0);
      }
      else
      {
        if (CDI_CacheSize_In >= 0) { cacheSize = (size_t) CDI_CacheSize_In; }
        cdf_set_var_chunk_cache(ncvar, ncvarid, cacheSize);
      }
    }

    streamptr->vars[varID1].defmiss = false;
    streamptr->vars[varID1].ncvarid = ncvarid;

    cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, ncvar->name);
    if (ncvar->param != CDI_UNDEFID) vlistDefVarParam(vlistID, varID, ncvar->param);
    if (ncvar->code != CDI_UNDEFID) vlistDefVarCode(vlistID, varID, ncvar->code);
    if (ncvar->code != CDI_UNDEFID) vlistDefVarParam(vlistID, varID, cdiEncodeParam(ncvar->code, ncvar->tabnum, 255));
    if (ncvar->longname[0]) cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, ncvar->longname);
    if (ncvar->stdname[0]) cdiDefKeyString(vlistID, varID, CDI_KEY_STDNAME, ncvar->stdname);
    if (ncvar->unitsLen > 0) cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, ncvar->units);

    if (ncvar->validrangeDefined) vlistDefVarValidrange(vlistID, varID, ncvar->validrange);

    if (IS_NOT_EQUAL(ncvar->addoffset, 0.0)) cdiDefKeyFloat(vlistID, varID, CDI_KEY_ADDOFFSET, ncvar->addoffset);
    if (IS_NOT_EQUAL(ncvar->scalefactor, 1.0)) cdiDefKeyFloat(vlistID, varID, CDI_KEY_SCALEFACTOR, ncvar->scalefactor);

    vlistDefVarDatatype(vlistID, varID, cdfInqDatatype(streamptr, ncvar->xtype, ncvar->isUnsigned));

    vlistDefVarInstitut(vlistID, varID, instID);
    vlistDefVarModel(vlistID, varID, modelID);
    if (ncvar->tableID != CDI_UNDEFID) vlistDefVarTable(vlistID, varID, ncvar->tableID);

    if (ncvar->fillvalDefined == false && ncvar->missvalDefined)
    {
      ncvar->fillvalDefined = true;
      ncvar->fillval = ncvar->missval;
    }

    if (ncvar->fillvalDefined) vlistDefVarMissval(vlistID, varID, ncvar->fillval);

    if (CDI_Debug)
      Message("varID = %d  gridID = %d  zaxisID = %d", varID, vlistInqVarGrid(vlistID, varID), vlistInqVarZaxis(vlistID, varID));

    int gridIndex = vlistGridIndex(vlistID, gridID);
    const CdfGrid *ncGrid = &(streamptr->cdfInfo.cdfGridList[gridIndex]);
    int xdimid = ncGrid->ncIdList[CDF_DIMID_X];
    int ydimid = ncGrid->ncIdList[CDF_DIMID_Y];

    int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
    int zdimid = streamptr->cdfInfo.zaxisIdList[zaxisindex];

    int ndims = ncvar->ndims;
    static const int ipow10[4] = { 1, 10, 100, 1000 };

    int iodim = (ncvar->timetype != TIME_CONSTANT);

    const int *dimids = ncvar->dimids;

    int ixyz = 0;
    if ((ndims - iodim) <= 2 && (ydimid == xdimid || ydimid == CDI_UNDEFID))
    {
      ixyz = (xdimid == ncdims[dimids[ndims - 1]].dimid) ? 321 : 213;
    }
    else
    {
      for (int idim = iodim; idim < ndims; idim++)
      {
        int dimid = ncdims[dimids[idim]].dimid;
        // clang-format off
        if      (xdimid == dimid) { ixyz += 1 * ipow10[ndims - idim - 1]; }
        else if (ydimid == dimid) { ixyz += 2 * ipow10[ndims - idim - 1]; }
        else if (zdimid == dimid) { ixyz += 3 * ipow10[ndims - idim - 1]; }
        // clang-format on
      }
    }

    if (ncvar->isCubeSphere) ixyz = 0;
    vlistDefVarXYZ(vlistID, varID, ixyz);
    /*
    printf("ixyz %d\n", ixyz);
    printf("ndims %d\n", ncvar->ndims);
    for (int i = 0; i < ncvar->ndims; ++i)
      printf("dimids: %d %d\n", i, dimids[i]);
    printf("xdimid, ydimid %d %d\n", xdimid, ydimid);
    */
    if (ncvar->numberOfForecastsInEnsemble != -1)
    {
      cdiDefKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, ncvar->numberOfForecastsInEnsemble);
      cdiDefKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, ncvar->perturbationNumber);
      if (ncvar->numberOfForecastsInEnsemble != -1)
        cdiDefKeyInt(vlistID, varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, ncvar->typeOfEnsembleForecast);
    }
  }

  for (int varID = 0; varID < nvars; varID++)
  {
    int ncvarid = varids[varID];
    ncvar_t *ncvar = &ncvars[ncvarid];
    int ncid = ncvar->ncid;
    int nvatts = ncvar->natts;
    for (int iatt = 0; iatt < nvatts; ++iatt) cdf_set_cdi_attr(ncid, ncvarid, ncvar->atts[iatt], vlistID, varID, false);

    if (ncvar->atts) Free(ncvar->atts);
    if (ncvar->vct) Free(ncvar->vct);

    ncvar->atts = NULL;
    ncvar->vct = NULL;
  }

  // release mem of not freed attributes
  for (int ncvarid = 0; ncvarid < num_ncvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->atts) Free(ncvar->atts);
    ncvar->atts = NULL;
  }

  if (varids) Free(varids);

  for (int varID = 0; varID < nvars; varID++) cdf_define_code_and_param(vlistID, varID);
  for (int varID = 0; varID < nvars; varID++) cdf_define_institut_and_model_id(vlistID, varID);
}

static void
cdf_copy_attint(int fileID, int vlistID, nc_type xtype, size_t attlen, char *attname)
{
  int attint[8];
  int *pattint = (attlen > 8) ? (int *) Malloc(attlen * sizeof(int)) : attint;
  cdfGetAttInt(fileID, NC_GLOBAL, attname, attlen, pattint);
  int datatype = (xtype == NC_SHORT) ? CDI_DATATYPE_INT16 : CDI_DATATYPE_INT32;
  cdiDefAttInt(vlistID, CDI_GLOBAL, attname, datatype, (int) attlen, pattint);
  if (attlen > 8) Free(pattint);
}

static void
cdf_copy_attflt(int fileID, int vlistID, nc_type xtype, size_t attlen, char *attname)
{
  double attflt[8];
  double *pattflt = (attlen > 8) ? (double *) Malloc(attlen * sizeof(double)) : attflt;
  cdfGetAttDouble(fileID, NC_GLOBAL, attname, attlen, pattflt);
  int datatype = (xtype == NC_FLOAT) ? CDI_DATATYPE_FLT32 : CDI_DATATYPE_FLT64;
  cdiDefAttFlt(vlistID, CDI_GLOBAL, attname, datatype, (int) attlen, pattflt);
  if (attlen > 8) Free(pattflt);
}

static void
check_cube_sphere(int vlistID, int nvars, ncvar_t *ncvars, ncdim_t *ncdims)
{
  bool isGeosData = false;
  int numNames = 4;
  const char *attnames[] = { "additional_vars", "file_format_version", "gridspec_file", "grid_mapping_name" };
  const char *grid_mapping = "gnomonic cubed-sphere";
  char attstring[256];
  int nf_dimid = -1, ncontact_dimid = -1;

  int numFound = 0;
  for (int i = 0; i < numNames; ++i)
    if (0 == cdiInqAttTxt(vlistID, CDI_GLOBAL, attnames[i], (int) sizeof(attstring), attstring)) numFound++;

  if (numFound == numNames && strStartsWith(attstring, grid_mapping))
  {
    for (int i = 0; i < numNames; ++i) cdiDelAtt(vlistID, CDI_GLOBAL, attnames[i]);

    const char *nf_name = "nf";
    const char *ncontact_name = "ncontact";
    for (int varid = 0; varid < nvars; ++varid)
    {
      ncvar_t *ncvar = &ncvars[varid];
      if (ncvar->ndims == 1)
      {
        int dimid = ncvar->dimids[0];
        if (ncdims[dimid].len == 6 && str_is_equal(nf_name, ncvar->name))
        {
          isGeosData = true;
          nf_dimid = ncvar->dimids[0];
        }
        if (ncdims[dimid].len == 4 && str_is_equal(ncontact_name, ncvar->name)) ncontact_dimid = ncvar->dimids[0];
      }
      if (isGeosData && ncontact_dimid != -1) break;
    }
  }

  if (isGeosData)
  {
    ncdims[nf_dimid].dimtype = E_AXIS;
    for (int varid = 0; varid < nvars; ++varid)
    {
      ncvar_t *ncvar = &ncvars[varid];
      if (str_is_equal("orientation", ncvar->name) || str_is_equal("anchor", ncvar->name) || str_is_equal("contacts", ncvar->name))
        cdf_set_var(ncvar, CoordVar);
    }

    for (int varid = 0; varid < nvars; ++varid)
    {
      ncvar_t *ncvar = &ncvars[varid];
      int ndims = ncvar->ndims;
      if (ndims >= 3 && ncvar->dimids[ndims - 3] == nf_dimid && ncvar->ncoordvars == 2 && ncvar->gmapid != -1)
        ncvar->isCubeSphere = true;
    }

    int xvarid = -1, yvarid = -1;
    int xboundsid = -1, yboundsid = -1;
    for (int varid = 0; varid < nvars; ++varid)
    {
      ncvar_t *ncvar = &ncvars[varid];
      int ndims = ncvar->ndims;
      if (ndims == 3)
      {
        // clang-format off
        if      (str_is_equal("lons", ncvar->name)) xvarid = varid;
        else if (str_is_equal("lats", ncvar->name)) yvarid = varid;
        else if (str_is_equal("corner_lons", ncvar->name)) xboundsid = varid;
        else if (str_is_equal("corner_lats", ncvar->name)) yboundsid = varid;
        // clang-format on
      }
      if (xvarid != -1 && xboundsid != -1 && yvarid != -1 && yboundsid != -1)
      {
        cdf_set_var(&ncvars[xboundsid], CoordVar);
        cdf_set_var(&ncvars[yboundsid], CoordVar);
        ncvars[xvarid].bounds = xboundsid;
        ncvars[yvarid].bounds = yboundsid;
        break;
      }
    }
  }

  if (CDI_Debug) Message("isGeosData %d", isGeosData);
}

static void
cdf_scan_global_attr(int fileID, int vlistID, int ngatts, int *instID, int *modelID, bool *ucla_les, unsigned char *uuidOfVGrid,
                     GridInfo *gridInfo)
{
  nc_type xtype = 0;
  size_t attlen = 0;
  char attname[CDI_MAX_NAME] = { 0 };

  for (int iatt = 0; iatt < ngatts; iatt++)
  {
    cdf_inq_attname(fileID, NC_GLOBAL, iatt, attname);
    cdf_inq_atttype(fileID, NC_GLOBAL, attname, &xtype);
    cdf_inq_attlen(fileID, NC_GLOBAL, attname, &attlen);

    if (xtypeIsText(xtype))
    {
      enum
      {
        attstringsize = 65636
      };
      char attstring[attstringsize] = { 0 };

      cdfGetAttText(fileID, NC_GLOBAL, attname, attstringsize, attstring);

      size_t attstrlen = strlen(attstring);

      if (attlen > 0 && attstring[0] != 0)
      {
        if (str_is_equal(attname, "institution"))
        {
          *instID = institutInq(0, 0, NULL, attstring);
          if (*instID == CDI_UNDEFID) *instID = institutDef(0, 0, NULL, attstring);

          cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int) attstrlen, attstring);
        }
        else if (str_is_equal(attname, "source"))
        {
          *modelID = modelInq(-1, 0, attstring);
          if (*modelID == CDI_UNDEFID) *modelID = modelDef(-1, 0, attstring);

          cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int) attstrlen, attstring);
        }
        else if (str_is_equal(attname, "Source") && strStartsWith(attstring, "UCLA-LES"))
        {
          *ucla_les = true;
          cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int) attstrlen, attstring);
        }
        /*
        else if ( str_is_equal(attname, "Conventions") )
          {
          }
        */
        else if (str_is_equal(attname, "_NCProperties")) {}
        else if (str_is_equal(attname, "CDI")) {}
        else if (str_is_equal(attname, "CDO")) {}
        else if (str_is_equal(attname, "grid_file_uri")) { memcpy(gridInfo->gridfile, attstring, attstrlen + 1); }
        else if (attstrlen == 36 && str_is_equal(attname, "uuidOfHGrid")) { cdiStr2UUID(attstring, gridInfo->uuid); }
        else if (attstrlen == 36 && str_is_equal(attname, "uuidOfVGrid")) { cdiStr2UUID(attstring, uuidOfVGrid); }
        else
        {
          if (str_is_equal(attname, "ICON_grid_file_uri") && gridInfo->gridfile[0] == 0)
            memcpy(gridInfo->gridfile, attstring, attstrlen + 1);

          cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int) attstrlen, attstring);
        }
      }
      else { cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int) attstrlen, attstring); }
    }
    else if (xtype == NC_SHORT || xtype == NC_INT)
    {
      if (str_is_equal(attname, "number_of_grid_used"))
      {
        gridInfo->number_of_grid_used = CDI_UNDEFID;
        cdfGetAttInt(fileID, NC_GLOBAL, attname, 1, &gridInfo->number_of_grid_used);
      }
      else { cdf_copy_attint(fileID, vlistID, xtype, attlen, attname); }
    }
    else if (xtype == NC_FLOAT || xtype == NC_DOUBLE) { cdf_copy_attflt(fileID, vlistID, xtype, attlen, attname); }
  }
}

static int
find_leadtime(int nvars, ncvar_t *ncvars, int timedimid)
{
  int leadtime_id = CDI_UNDEFID;

  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->ndims == 1 && timedimid == ncvar->dimids[0])
      if (ncvar->stdname[0] && str_is_equal(ncvar->stdname, "forecast_period"))
      {
        leadtime_id = ncvarid;
        break;
      }
  }

  return leadtime_id;
}

static void
find_time_vars(int nvars, ncvar_t *ncvars, ncdim_t *ncdims, int timedimid, stream_t *streamptr, bool *timeHasUnits,
               bool *timeHasBounds, bool *timeClimatology)
{
  int ncvarid;

  if (timedimid == CDI_UNDEFID)
  {
    char timeUnitsStr[CDI_MAX_NAME];

    for (ncvarid = 0; ncvarid < nvars; ncvarid++)
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if (ncvar->ndims == 0 && ncvar->units[0] && str_is_equal(ncvar->name, "time"))
      {
        strcpy(timeUnitsStr, ncvar->units);
        str_to_lower(timeUnitsStr);
        if (is_time_units(timeUnitsStr))
        {
          streamptr->basetime.ncvarid = ncvarid;
          break;
        }
      }
    }
  }
  else
  {
    bool hasTimeVar = false;

    if (ncdims[timedimid].ncvarid != CDI_UNDEFID)
    {
      streamptr->basetime.ncvarid = ncdims[timedimid].ncvarid;
      hasTimeVar = true;
    }

    for (ncvarid = 0; ncvarid < nvars; ncvarid++)
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if (ncvarid != streamptr->basetime.ncvarid && ncvar->ndims == 1 && timedimid == ncvar->dimids[0] && !xtypeIsText(ncvar->xtype)
          && is_timeaxis_units(ncvar->units))
      {
        ncvar->varStatus = CoordVar;

        if (!hasTimeVar)
        {
          hasTimeVar = true;
          streamptr->basetime.ncvarid = ncvarid;
        }
        else { Warning("Found more than one time variable, skipped variable %s!", ncvar->name); }
      }
    }

    if (hasTimeVar == false)  // search for WRF time description
    {
      for (ncvarid = 0; ncvarid < nvars; ncvarid++)
      {
        ncvar_t *ncvar = &ncvars[ncvarid];
        if (ncvarid != streamptr->basetime.ncvarid && ncvar->ndims == 2 && timedimid == ncvar->dimids[0]
            && xtypeIsText(ncvar->xtype) && (ncdims[ncvar->dimids[1]].len == 19 || ncdims[ncvar->dimids[1]].len == 64))
        {
          ncvar->isTaxis = true;
          streamptr->basetime.ncvarid = ncvarid;
          streamptr->basetime.isWRF = true;
          break;
        }
      }
    }

    // time varID
    ncvarid = streamptr->basetime.ncvarid;

    if (ncvarid == CDI_UNDEFID && ncdims[timedimid].len > 0) Warning("Time variable >%s< not found!", ncdims[timedimid].name);
  }

  // time varID
  ncvarid = streamptr->basetime.ncvarid;

  if (ncvarid != CDI_UNDEFID && streamptr->basetime.isWRF == false)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->units[0] != 0) *timeHasUnits = true;

    if (ncvar->bounds != CDI_UNDEFID)
    {
      int nbdims = ncvars[ncvar->bounds].ndims;
      if (nbdims == 2)
      {
        int len = (int) ncdims[ncvars[ncvar->bounds].dimids[nbdims - 1]].len;
        if (len == 2 && timedimid == ncvars[ncvar->bounds].dimids[0])
        {
          *timeHasBounds = true;
          streamptr->basetime.ncvarboundsid = ncvar->bounds;
          if (ncvar->isClimatology) *timeClimatology = true;
        }
      }
    }
  }
}

static void
read_vct_echam(int fileID, int nvars, ncvar_t *ncvars, ncdim_t *ncdims, double **vct, size_t *pvctsize)
{
  // find ECHAM VCT
  int nvcth_id = CDI_UNDEFID, vcta_id = CDI_UNDEFID, vctb_id = CDI_UNDEFID;
  // int p0_id = CDI_UNDEFID;

  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    const char *name = ncvar->name;
    if (ncvar->ndims == 1)
    {
      size_t len = strlen(name);
      if (len == 4 && name[0] == 'h' && name[1] == 'y')
      {
        if (name[2] == 'a' && name[3] == 'i')  // hyai
        {
          vcta_id = ncvarid;
          nvcth_id = ncvar->dimids[0];
          ncvar->varStatus = CoordVar;
        }
        else if (name[2] == 'b' && name[3] == 'i')  // hybi
        {
          vctb_id = ncvarid;
          nvcth_id = ncvar->dimids[0];
          ncvar->varStatus = CoordVar;
        }
        else if ((name[2] == 'a' || name[2] == 'b') && name[3] == 'm')
        {
          ncvar->varStatus = CoordVar;  // hyam or hybm
        }
      }
    }
    /*
    else if (ncvar->ndims == 0)
      {
        size_t len = strlen(name);
        if (len == 2 && name[0] == 'P' && name[1] == '0') p0_id = ncvarid;
      }
    */
  }

  // read VCT
  if (nvcth_id != CDI_UNDEFID && vcta_id != CDI_UNDEFID && vctb_id != CDI_UNDEFID)
  {
    size_t vctsize = 2 * ncdims[nvcth_id].len;
    *vct = (double *) Malloc(vctsize * sizeof(double));
    cdf_get_var_double(fileID, vcta_id, *vct);
    cdf_get_var_double(fileID, vctb_id, *vct + vctsize / 2);
    *pvctsize = vctsize;
    /*
    if (p0_id != CDI_UNDEFID)
      {
        double p0;
        cdf_get_var_double(fileID, p0_id, &p0);
      }
    */
  }
}

static void
cdf_set_ucla_dimtype(int ndims, ncdim_t *ncdims, ncvar_t *ncvars)
{
  for (int ncdimid = 0; ncdimid < ndims; ncdimid++)
  {
    ncdim_t *ncdim = &ncdims[ncdimid];
    int ncvarid = ncdim->ncvarid;
    if (ncvarid != -1)
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if (ncdim->dimtype == CDI_UNDEFID && ncvar->units[0] == 'm')
      {
        // clang-format off
        if      (ncvar->name[0] == 'x') ncdim->dimtype = X_AXIS;
        else if (ncvar->name[0] == 'y') ncdim->dimtype = Y_AXIS;
        else if (ncvar->name[0] == 'z') ncdim->dimtype = Z_AXIS;
        // clang-format on
      }
    }
  }
}

static int
cdf_check_variables(stream_t *streamptr, int nvars, ncvar_t *ncvars, size_t ntsteps, int timedimid)
{
  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->isTaxis && ncvar->ndims == 2)
    {
      ncvar->varStatus = CoordVar;
      continue;
    }

    if (ncvar->varStatus == UndefVar && ncvar->ndims > 1 && timedimid != CDI_UNDEFID && timedimid == ncvar->dimids[0])
      cdf_set_var(ncvar, DataVar);

    if (ncvar->varStatus == UndefVar)
    {
      if (ncvar->ndims == 0)
        cdf_set_var(ncvar, nvars == 1 ? DataVar : CoordVar);
      else if (ncvar->ndims > 0)
        cdf_set_var(ncvar, DataVar);
      else
      {
        ncvar->varStatus = CoordVar;
        Warning("Variable %s has an unknown type, skipped!", ncvar->name);
      }
    }

    if (ncvar->varStatus == CoordVar) continue;

    if ((ncvar->ndims > 4 && !ncvar->isCubeSphere) || ncvar->ndims > 5)
    {
      ncvar->varStatus = CoordVar;
      Warning("%d dimensional variables are not supported, skipped variable %s!", ncvar->ndims, ncvar->name);
      continue;
    }

    if (((ncvar->ndims == 4 && !ncvar->isCubeSphere) || ncvar->ndims == 5) && timedimid == CDI_UNDEFID)
    {
      ncvar->varStatus = CoordVar;
      Warning("%d dimensional variables without time dimension are not supported, skipped variable %s!", ncvar->ndims, ncvar->name);
      continue;
    }

    if (xtypeIsText(ncvar->xtype))
    {
      ncvar->varStatus = CoordVar;
      Warning("Unsupported data type (char/string), skipped variable %s!", ncvar->name);
      continue;
    }

    if (cdfInqDatatype(streamptr, ncvar->xtype, ncvar->isUnsigned) == -1)
    {
      ncvar->varStatus = CoordVar;
      Warning("Unsupported data type, skipped variable %s!", ncvar->name);
      continue;
    }

    if (timedimid != CDI_UNDEFID && ntsteps == 0 && ncvar->ndims > 0)
    {
      if (timedimid == ncvar->dimids[0])
      {
        ncvar->varStatus = CoordVar;
        Warning("Number of time steps undefined, skipped variable %s!", ncvar->name);
        continue;
      }
    }
  }

  return timedimid;
}

static void
cdfVerifyVars(int nvars, ncvar_t *ncvars
              //, ncdim_t *ncdims
)
{
  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->varStatus == DataVar && ncvar->ndims > 0)
    {
      int ndims = 0;
      for (int i = 0; i < ncvar->ndims; ++i)
      {
        // clang-format off
        if      (ncvar->dimtypes[i] == T_AXIS) ndims++;
        else if (ncvar->dimtypes[i] == E_AXIS) ndims++;
        else if (ncvar->dimtypes[i] == Z_AXIS) ndims++;
        else if (ncvar->dimtypes[i] == Y_AXIS) ndims++;
        else if (ncvar->dimtypes[i] == X_AXIS) ndims++;
        // clang-format on
      }

      if (ncvar->ndims != ndims)
      {
        ncvar->varStatus = CoordVar;
        Warning("Inconsistent number of dimensions, skipped variable %s!", ncvar->name);
      }
      /* check failed with cdo option --cmor
      int zdimid = -1;
      for (int i = 0; i < ncvar->ndims; ++i)
        {
          if (ncvar->dimtypes[i] == Z_AXIS) zdimid = ncvar->dimids[i];
        }

      int zaxisID = ncvar->zaxisID;
      if (zaxisInqScalar(zaxisID) && zdimid != -1)
        {
          ncvar->varStatus = CoordVar;
          Warning("Unsupported dimension >%s<, skipped variable %s!", ncdims[zdimid].name, ncvar->name);
        }
      */
    }
  }
}

static CdiDateTime
wrf_read_timestep(int fileID, int nctimevarid, size_t tsID)
{
  enum
  {
    // position of terminator separating date and time from rest of
    dateTimeSepPos = 19,
    dateTimeStrSize = 128,
  };
  size_t start[2] = { tsID, 0 }, count[2] = { 1, dateTimeSepPos };
  char stvalue[dateTimeStrSize];
  stvalue[0] = 0;
  cdf_get_vara_text(fileID, nctimevarid, start, count, stvalue);
  stvalue[dateTimeSepPos] = 0;

  int year = 1, month = 1, day = 1, hour = 0, minute = 0, second = 0;
  if (strlen(stvalue) == dateTimeSepPos) sscanf(stvalue, "%d-%d-%d_%d:%d:%d", &year, &month, &day, &hour, &minute, &second);
  return cdiDateTime_set(cdiEncodeDate(year, month, day), cdiEncodeTime(hour, minute, second));
}

static double
get_timevalue(int fileID, int nctimevarid, size_t ncStepIndex, double *timevarBuffer)
{
  double timevalue = 0.0;

  if (timevarBuffer) { timevalue = timevarBuffer[ncStepIndex]; }
  else { cdf_get_var1_double(fileID, nctimevarid, &ncStepIndex, &timevalue); }

  if (timevalue >= NC_FILL_DOUBLE || timevalue < -NC_FILL_DOUBLE) timevalue = 0.0;

  return timevalue;
}

static void
cdf_read_timesteps(size_t numTimesteps, stream_t *streamptr, taxis_t *taxis0)
{
  streamptr->curTsID = 0;
  streamptr->rtsteps = 1;

  if (numTimesteps == 0)
  {
    cdi_create_timesteps(numTimesteps, streamptr);
    cdf_create_records(streamptr, 0);
  }
  else
  {
    int fileID = streamptr->fileID;
    int nctimevarid = streamptr->basetime.ncvarid;
    int nctimeboundsid = streamptr->basetime.ncvarboundsid;
    bool hasTimesteps = (nctimevarid != CDI_UNDEFID /*&& streamptr->basetime.hasUnits*/);

    int *ncStepIndices = (int *) Malloc(numTimesteps * sizeof(int));
    for (size_t tsID = 0; tsID < numTimesteps; ++tsID) ncStepIndices[tsID] = (int) tsID;

    CdiDateTime *vDateTimeList = NULL;

    if (hasTimesteps)
    {
      vDateTimeList = (CdiDateTime *) Malloc(numTimesteps * sizeof(CdiDateTime));

      if (streamptr->basetime.isWRF)
      {
        for (size_t tsID = 0; tsID < numTimesteps; ++tsID) vDateTimeList[tsID] = wrf_read_timestep(fileID, nctimevarid, tsID);
      }
      else if (streamptr->basetime.hasUnits)
      {
        double *timevarBuffer = (double *) Malloc(numTimesteps * sizeof(double));
        cdf_get_var_double(fileID, nctimevarid, timevarBuffer);
        for (size_t tsID = 0; tsID < numTimesteps; ++tsID)
          vDateTimeList[tsID] = cdi_decode_timeval(get_timevalue(fileID, nctimevarid, tsID, timevarBuffer), taxis0);
        if (timevarBuffer) Free(timevarBuffer);
      }
      else { hasTimesteps = false; }
    }

    // process step query information if available
    struct CdiQuery *query = streamptr->query;
    if (query)
    {
      int queryNumSteps = cdiQueryNumSteps(query);
      if (queryNumSteps > 0)
      {
        int startStep = cdiQueryGetStep(query, 0) - 1;
        int endStep = startStep + ((queryNumSteps == 2) ? (cdiQueryGetStep(query, 1) - 1) : 0);
        // currently, the query interface does not support more than INT_MAX-1 steps!
        assert(numTimesteps < INT_MAX);
        for (int i = 0; i < startStep; ++i) ncStepIndices[i] = -1;
        for (int i = endStep + 1; i < (int) numTimesteps; ++i) ncStepIndices[i] = -1;
      }
    }

    size_t numSteps = 0;
    for (size_t tsID = 0; tsID < numTimesteps; ++tsID) numSteps += (ncStepIndices[tsID] >= 0);

    cdi_create_timesteps(numSteps, streamptr);

    for (size_t tsID = 0, stepID = 0; tsID < numTimesteps; ++tsID)
    {
      if (ncStepIndices[tsID] >= 0)
      {
        streamptr->tsteps[stepID].ncStepIndex = ncStepIndices[tsID];
        cdf_create_records(streamptr, stepID);

        taxis_t *taxis = &streamptr->tsteps[stepID].taxis;
        ptaxisCopy(taxis, taxis0);

        if (hasTimesteps) taxis->vDateTime = vDateTimeList[tsID];

        stepID++;
      }
    }

    if (ncStepIndices) Free(ncStepIndices);
    if (vDateTimeList) Free(vDateTimeList);

    if (hasTimesteps)
    {
      if (nctimeboundsid != CDI_UNDEFID)
      {
        enum
        {
          numBnds = 2,
          tbNdims = 2
        };

        for (size_t tsID = 0; tsID < numSteps; ++tsID)
        {
          size_t ncStepIndex = (size_t) streamptr->tsteps[tsID].ncStepIndex;
          taxis_t *taxis = &streamptr->tsteps[tsID].taxis;
          size_t start[tbNdims] = { ncStepIndex, 0 };
          size_t count[tbNdims] = { 1, numBnds };
          double timeBnds[numBnds];
          cdf_get_vara_double(fileID, nctimeboundsid, start, count, timeBnds);
          for (size_t i = 0; i < numBnds; ++i)
            if (timeBnds[i] >= NC_FILL_DOUBLE || timeBnds[i] < -NC_FILL_DOUBLE) timeBnds[i] = 0.0;

          taxis->vDateTime_lb = cdi_decode_timeval(timeBnds[0], taxis);
          taxis->vDateTime_ub = cdi_decode_timeval(timeBnds[1], taxis);
        }
      }

      int leadtimeid = streamptr->basetime.leadtimeid;
      if (leadtimeid != CDI_UNDEFID)
      {
        for (size_t tsID = 0; tsID < numSteps; ++tsID)
        {
          size_t ncStepIndex = (size_t) streamptr->tsteps[tsID].ncStepIndex;
          taxis_t *taxis = &streamptr->tsteps[tsID].taxis;
          cdi_set_forecast_period(get_timevalue(fileID, leadtimeid, ncStepIndex, NULL), taxis);
        }
      }
    }
  }
}

static void
stream_set_ncdims(stream_t *streamptr, int ndims, ncdim_t *ncdims)
{
  int n = (ndims > MAX_DIMS_PS) ? MAX_DIMS_PS : ndims;
  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  cdfInfo->ncNumDims = n;
  for (int i = 0; i < n; i++) cdfInfo->ncDimIdList[i] = ncdims[i].dimid;
  for (int i = 0; i < n; i++) cdfInfo->ncDimLenList[i] = ncdims[i].len;
}

static void
set_ncdim_ids(int fileID, int ndims, ncdim_t *ncdims)
{
  if (ndims)
  {
    int gdimid = 0;
    for (int i = 0; i < NC_MAX_DIMS; ++i)
    {
      if (nc_inq_dimlen(fileID, i, NULL) == NC_NOERR)
      {
        ncdims[gdimid++].dimid = i;
        if (gdimid == ndims) break;
      }
    }
  }
}

static void
read_ncdims(int fileID, int ndims, ncdim_t *ncdims)
{
  for (int gdimid = 0; gdimid < ndims; gdimid++)
  {
    cdf_inq_dimlen(fileID, ncdims[gdimid].dimid, &ncdims[gdimid].len);
    cdf_inq_dimname(fileID, ncdims[gdimid].dimid, ncdims[gdimid].name);
  }
}

static void
check_ncgroups(int fileID)
{
  int numgrps = 0;
  int ncids[NC_MAX_VARS];
  char gname[CDI_MAX_NAME];
  nc_inq_grps(fileID, &numgrps, ncids);
  for (int i = 0; i < numgrps; ++i)
  {
    int ncid = ncids[i];
    nc_inq_grpname(ncid, gname);
    int gndims, gnvars, gngatts, gunlimdimid;
    cdf_inq(ncid, &gndims, &gnvars, &gngatts, &gunlimdimid);

    if (CDI_Debug) Message("%s: ndims %d, nvars %d, ngatts %d", gname, gndims, gnvars, gngatts);
  }
  if (numgrps) Warning("NetCDF4 groups not supported! Found %d root group%s.", numgrps, (numgrps > 1) ? "s" : "");
}

static void
find_coordinates_vars(int ndims, ncdim_t *ncdims, int nvars, ncvar_t *ncvars)
{
  for (int gdimid = 0; gdimid < ndims; gdimid++)
  {
    for (int varid = 0; varid < nvars; varid++)
    {
      ncvar_t *ncvar = &ncvars[varid];
      if (ncvar->ndims == 1 && gdimid == ncvar->dimids[0] && ncdims[gdimid].ncvarid == CDI_UNDEFID)
      {
        if (str_is_equal(ncvar->name, ncdims[gdimid].name))
        {
          ncdims[gdimid].ncvarid = varid;
          ncvar->varStatus = CoordVar;
        }
      }
    }
  }
}

// set time dependent data vars
static void
find_varying_data_vars1d(int timedimid, int nvars, ncvar_t *ncvars)
{
  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
  {
    ncvar_t *ncvar = &ncvars[ncvarid];
    if (ncvar->ndims == 1)
    {
      if (timedimid != CDI_UNDEFID && timedimid == ncvar->dimids[0])
      {
        if (ncvar->varStatus != CoordVar) cdf_set_var(ncvar, DataVar);
      }
      else
      {
        //  if ( ncvar->varStatus != DataVar ) cdf_set_var(ncvar, CoordVar);
      }
      // if ( ncvar->varStatus != DataVar ) cdf_set_var(ncvar, CoordVar);
    }
  }
}

static void
set_coordinates_varids(int numVars, ncvar_t *ncvars)
{
  for (int varId = 0; varId < numVars; varId++)
  {
    ncvar_t *ncvar = &ncvars[varId];
    if (ncvar->varStatus == DataVar && ncvar->ncoordvars)
    {
      for (int i = 0; i < ncvar->ncoordvars; i++)
      {
        int coordVarId = ncvar->coordvarids[i];
        if (coordVarId != CDI_UNDEFID)
        {
          ncvar_t *coordVar = &ncvars[coordVarId];
          if (coordVar->isZaxis == false && coordVar->zaxistype != CDI_UNDEFID) { coordVar->isZaxis = true; }
          // clang-format off
          if      (coordVar->isLon || coordVar->isXaxis) { ncvar->xvarid = coordVarId; }
          else if (coordVar->isLat || coordVar->isYaxis) { ncvar->yvarid = coordVarId; }
          else if (coordVar->isZaxis)                    { ncvar->zvarid = coordVarId; }
          else if (coordVar->isTaxis)                    { ncvar->tvarid = coordVarId; }
          else if (coordVar->isCharAxis)                 { ncvar->cvarids[i] = coordVarId; }
          else if (coordVar->isIndexAxis)                { ncvar->ivarid = coordVarId; }
          else if (coordVar->printWarning)
          {
            Warning("Coordinates variable %s can't be assigned!", coordVar->name);
            coordVar->printWarning = false;
          }
          // clang-format on
        }
      }
    }
  }
}

static void
process_var_query(struct CdiQuery *query, int nvars, ncvar_t *ncvars)
{
  // process var query information if available
  if (query && cdiQueryNumNames(query) > 0)
  {
    for (int ncvarid = 0; ncvarid < nvars; ++ncvarid)
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if (ncvar->varStatus == DataVar && cdiQueryName(query, ncvar->name) < 0) ncvar->varStatus = CoordVar;
    }
  }
}

int
cdfInqContents(stream_t *streamptr)
{
  GridInfo gridInfo;
  gridInfo.gridfile[0] = 0;
  memset(gridInfo.uuid, 0, CDI_UUID_SIZE);
  gridInfo.number_of_grid_used = CDI_UNDEFID;
  gridInfo.timedimid = CDI_UNDEFID;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  if (CDI_Debug) Message("streamID = %d, fileID = %d", streamptr->self, fileID);

  int ndims = 0, nvars = 0, ngatts = 0, unlimdimid = 0;
  cdf_inq(fileID, &ndims, &nvars, &ngatts, &unlimdimid);

  if (CDI_Debug) Message("root: ndims %d, nvars %d, ngatts %d", ndims, nvars, ngatts);

  // alloc ncdims
  ncdim_t *ncdims = ndims ? (ncdim_t *) Malloc((size_t) ndims * sizeof(ncdim_t)) : NULL;
  init_ncdims(ndims, ncdims);
  set_ncdim_ids(fileID, ndims, ncdims);
  read_ncdims(fileID, ndims, ncdims);

  int format = 0;
  nc_inq_format(fileID, &format);
  if (format == NC_FORMAT_NETCDF4) check_ncgroups(fileID);

  if (nvars == 0)
  {
    Warning("No arrays found!");
    return CDI_EUFSTRUCT;
  }

  // alloc ncvars
  ncvar_t *ncvars = (ncvar_t *) Malloc((size_t) nvars * sizeof(ncvar_t));
  init_ncvars(nvars, ncvars, fileID);

  read_vars_info(nvars, ncvars, ndims, ncdims, format);
  find_coordinates_vars(ndims, ncdims, nvars, ncvars);

  // scan global attributes
  int instID = CDI_UNDEFID;
  int modelID = CDI_UNDEFID;
  bool ucla_les = false;
  unsigned char uuidOfVGrid[CDI_UUID_SIZE] = { 0 };
  cdf_scan_global_attr(fileID, vlistID, ngatts, &instID, &modelID, &ucla_les, uuidOfVGrid, &gridInfo);

  // find time dim
  int timedimid = (unlimdimid >= 0) ? unlimdimid : cdf_time_dimid(fileID, ndims, ncdims, nvars, ncvars);
  streamptr->basetime.ncdimid = timedimid;

  size_t ntsteps = (timedimid == CDI_UNDEFID) ? 0 : ncdims[timedimid].len;
  if (ntsteps > INT_MAX)
  {
    Warning("Size limit exceeded for time dimension (limit=%d)!", INT_MAX);
    return CDI_EDIMSIZE;
  }

  if (CDI_Debug) Message("Number of timesteps = %zu", ntsteps);
  if (CDI_Debug) Message("Time dimid = %d", streamptr->basetime.ncdimid);

  // set T_AXIS dimtype
  for (int gdimid = 0; gdimid < ndims; gdimid++)
  {
    if (timedimid == gdimid) ncdims[gdimid].dimtype = T_AXIS;
  }

  stream_set_ncdims(streamptr, ndims, ncdims);

  if (CDI_Debug) cdf_print_vars(ncvars, nvars, "scan_vars_attr");

  // scan attributes of all variables
  set_vars_timetype(nvars, ncvars, timedimid);
  scan_vars_attr(nvars, ncvars, ndims, ncdims, modelID);
  verify_vars_attr(nvars, ncvars, ncdims);

  if (CDI_Convert_Cubesphere) check_cube_sphere(vlistID, nvars, ncvars, ncdims);

  if (CDI_Debug) cdf_print_vars(ncvars, nvars, "find_varying_data_vars1d");

  find_varying_data_vars1d(timedimid, nvars, ncvars);

  // find time vars
  bool timeHasUnits = false;
  bool timeHasBounds = false;
  bool timeClimatology = false;
  find_time_vars(nvars, ncvars, ncdims, timedimid, streamptr, &timeHasUnits, &timeHasBounds, &timeClimatology);

  int leadtime_id = find_leadtime(nvars, ncvars, timedimid);
  if (leadtime_id != CDI_UNDEFID) ncvars[leadtime_id].varStatus = CoordVar;

  // check ncvars
  timedimid = cdf_check_variables(streamptr, nvars, ncvars, ntsteps, timedimid);

  // verify coordinates vars - first scan (dimname == varname)
  bool isHybridCF = false;
  verify_coordinates_vars_1(fileID, ndims, ncdims, ncvars, timedimid, &isHybridCF);

  // verify coordinates vars - second scan (all other variables)
  verify_coordinates_vars_2(streamptr, nvars, ncvars);

  if (CDI_Debug) cdf_print_vars(ncvars, nvars, "verify_coordinate_vars");

  if (ucla_les) cdf_set_ucla_dimtype(ndims, ncdims, ncvars);

  /*
  for (int ncdimid = 0; ncdimid < ndims; ncdimid++)
    {
      int ncvarid = ncdims[ncdimid].ncvarid;
      if (ncvarid != -1)
        {
          printf("coord var %d %s %s\n", ncvarid, ncvar->name, ncvar->units);
          if (ncdims[ncdimid].dimtype == X_AXIS) printf("coord var %d %s is x dim\n", ncvarid, ncvar->name);
          if (ncdims[ncdimid].dimtype == Y_AXIS) printf("coord var %d %s is y dim\n", ncvarid, ncvar->name);
          if (ncdims[ncdimid].dimtype == Z_AXIS) printf("coord var %d %s is z dim\n", ncvarid, ncvar->name);
          if (ncdims[ncdimid].dimtype == T_AXIS) printf("coord var %d %s is t dim\n", ncvarid, ncvar->name);

          if (ncvar->isLon) printf("coord var %d %s is lon\n", ncvarid, ncvar->name);
          if (ncvar->isLat) printf("coord var %d %s is lat\n", ncvarid, ncvar->name);
          if (ncvar->isZaxis) printf("coord var %d %s is lev\n", ncvarid, ncvar->name);
        }
    }
  */

  set_coordinates_varids(nvars, ncvars);

  cdf_set_dimtype(nvars, ncvars, ncdims);

  // read ECHAM VCT if present
  size_t vctsize = 0;
  double *vct = NULL;
  if (!isHybridCF) read_vct_echam(fileID, nvars, ncvars, ncdims, &vct, &vctsize);

  // process var query information if available
  process_var_query(streamptr->query, nvars, ncvars);

  if (CDI_Debug) cdf_print_vars(ncvars, nvars, "cdf_define_all_grids");

  // define all grids
  gridInfo.timedimid = timedimid;
  int status = cdf_define_all_grids(streamptr, vlistID, ncdims, nvars, ncvars, &gridInfo);
  if (status < 0) return status;

  // define all zaxes
  status = cdf_define_all_zaxes(streamptr, vlistID, ncdims, nvars, ncvars, vctsize, vct, uuidOfVGrid);
  if (vct) Free(vct);
  if (status < 0) return status;

  // verify vars
  cdfVerifyVars(nvars, ncvars);

  // select vars
  int nvarsData = 0;
  for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
    if (ncvars[ncvarid].varStatus == DataVar) nvarsData++;

  if (CDI_Debug) Message("time varid = %d", streamptr->basetime.ncvarid);
  if (CDI_Debug) Message("ntsteps = %zu", ntsteps);
  if (CDI_Debug) Message("nvarsData = %d", nvarsData);

  if (nvarsData == 0)
  {
    streamptr->ntsteps = 0;
    Warning("No data arrays found!");
    return CDI_EUFSTRUCT;
  }

  if (ntsteps == 0 && streamptr->basetime.ncdimid == CDI_UNDEFID && streamptr->basetime.ncvarid != CDI_UNDEFID) ntsteps = 1;

  // define all data variables
  cdf_define_all_vars(streamptr, vlistID, instID, modelID, nvarsData, nvars, ncvars, ncdims, timedimid);

  cdf_set_chunk_info(streamptr, nvars, ncvars);

  // time varID
  int nctimevarid = streamptr->basetime.ncvarid;

  if (nctimevarid != CDI_UNDEFID && (!timeHasUnits || streamptr->basetime.isWRF)) ncvars[nctimevarid].units[0] = 0;
  if (nctimevarid != CDI_UNDEFID && timeHasUnits) streamptr->basetime.hasUnits = true;

  taxis_t taxis0;
  ptaxisInit(&taxis0);

  if (timeHasUnits)
  {
    if (set_base_time(ncvars[nctimevarid].units, &taxis0) == 1)
    {
      nctimevarid = CDI_UNDEFID;
      streamptr->basetime.ncvarid = CDI_UNDEFID;
      streamptr->basetime.hasUnits = false;
    }

    if (leadtime_id != CDI_UNDEFID && taxis0.type == TAXIS_RELATIVE)
    {
      streamptr->basetime.leadtimeid = leadtime_id;
      taxis0.type = TAXIS_FORECAST;

      int timeunit = (ncvars[leadtime_id].units[0] != 0) ? scan_time_units(ncvars[leadtime_id].units) : -1;
      if (timeunit == -1) timeunit = taxis0.unit;
      taxis0.fc_unit = timeunit;
    }
  }

  if (timeHasBounds)
  {
    taxis0.hasBounds = true;
    if (timeClimatology) taxis0.climatology = true;
  }

  if (nctimevarid != CDI_UNDEFID)
  {
    ptaxisDefName(&taxis0, ncvars[nctimevarid].name);
    if (ncvars[nctimevarid].longname[0]) ptaxisDefLongname(&taxis0, ncvars[nctimevarid].longname);
    if (ncvars[nctimevarid].units[0]) ptaxisDefUnits(&taxis0, ncvars[nctimevarid].units);

    int xtype = ncvars[nctimevarid].xtype;
    int datatype = (xtype == NC_INT) ? CDI_DATATYPE_INT32 : ((xtype == NC_FLOAT) ? CDI_DATATYPE_FLT32 : CDI_DATATYPE_FLT64);
    ptaxisDefDatatype(&taxis0, datatype);
  }

  int calendar = CDI_UNDEFID;
  if (nctimevarid != CDI_UNDEFID && ncvars[nctimevarid].hasCalendar)
  {
    char attstring[1024];
    cdfGetAttText(fileID, nctimevarid, "calendar", sizeof(attstring), attstring);
    str_to_lower(attstring);
    calendar = attribute_to_calendar(attstring);
  }

  if (streamptr->basetime.isWRF) taxis0.type = TAXIS_ABSOLUTE;

  int taxisID;
  if (taxis0.type == TAXIS_FORECAST) { taxisID = taxisCreate(TAXIS_FORECAST); }
  else if (taxis0.type == TAXIS_RELATIVE) { taxisID = taxisCreate(TAXIS_RELATIVE); }
  else
  {
    taxisID = taxisCreate(TAXIS_ABSOLUTE);
    if (!timeHasUnits)
    {
      taxisDefTunit(taxisID, TUNIT_DAY);
      taxis0.unit = TUNIT_DAY;
    }
  }

  if (calendar == CDI_UNDEFID && taxis0.type != TAXIS_ABSOLUTE) calendar = CALENDAR_STANDARD;

#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)
#pragma GCC diagnostic push
#pragma GCC diagnostic warning "-Wstrict-overflow"
#endif
  if (calendar != CDI_UNDEFID)
  {
    taxis0.calendar = calendar;
    taxisDefCalendar(taxisID, calendar);
  }
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)
#pragma GCC diagnostic pop
#endif

  vlistDefTaxis(vlistID, taxisID);

  cdf_read_timesteps(ntsteps, streamptr, &taxis0);
  taxisDestroyKernel(&taxis0);

  // free ncdims
  if (ncdims) Free(ncdims);

  // free ncvars
  if (ncvars)
  {
    for (int ncvarid = 0; ncvarid < nvars; ncvarid++)
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if (ncvar->atts) Free(ncvar->atts);
      if (ncvar->vct) Free(ncvar->vct);
    }
    Free(ncvars);
  }

  return 0;
}

int
cdfInqTimestep(stream_t *streamptr, int tsID)
{
  if (tsID < 0 || tsID >= streamptr->ntsteps) Error("tsID=%d out of range (0-%d)!", tsID, streamptr->ntsteps - 1);

  streamptr->curTsID = tsID;
  int numRecs = streamptr->tsteps[tsID].nrecs;

  return numRecs;
}

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF


static const char bndsName[] = "bnds";

void
cdfCopyField(stream_t *streamptr2, stream_t *streamptr1)
{
  int vlistID1 = streamptr1->vlistID;
  int tsID = streamptr1->curTsID;
  int vrecID = streamptr1->tsteps[tsID].curRecID;
  int recID = streamptr1->tsteps[tsID].recIDs[vrecID];
  int ivarID = streamptr1->tsteps[tsID].recinfo[recID].varID;
  int gridID = vlistInqVarGrid(vlistID1, ivarID);
  size_t datasize = (size_t) gridInqSize(gridID);
  int datatype = vlistInqVarDatatype(vlistID1, ivarID);
  int memtype = (datatype != CDI_DATATYPE_FLT32) ? MEMTYPE_DOUBLE : MEMTYPE_FLOAT;

  void *data = Malloc(datasize * ((memtype == MEMTYPE_DOUBLE) ? sizeof(double) : sizeof(float)));

  size_t numMissVals;
  cdf_read_field(streamptr1, memtype, data, &numMissVals);
  cdf_write_field(streamptr2, memtype, data, numMissVals);

  Free(data);
}

void
cdfDefField(stream_t *streamptr)
{
  (void) streamptr;
}

static void
cdfDefComplex(stream_t *streamptr, int gridID, int gridIndex)
{
  int dimID = 0;
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  for (int index = 0; index < gridIndex; ++index)
  {
    if (cdfGridList[index].ncIdList[CDF_DIMID_X] != CDI_UNDEFID)
    {
      int gridID0 = cdfGridList[index].gridID;
      int gridtype0 = gridInqType(gridID0);
      if (gridtype0 == GRID_SPECTRAL || gridtype0 == GRID_FOURIER)
      {
        dimID = cdfGridList[index].ncIdList[CDF_DIMID_X];
        goto dimIDEstablished;
      }
    }
  }

  {
    static const char axisname[] = "nc2";
    size_t dimlen = 2;
    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    cdf_def_dim(fileID, axisname, dimlen, &dimID);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }

dimIDEstablished:
  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] = dimID;
}

struct idSearch
{
  int numNonMatching, foundID;
  size_t foundIdx;
};

static inline struct idSearch
cdfSearchIDBySize(size_t startIdx, size_t numIDs, const CdfGrid cdfGridList[/*numIDs*/], int ncIDType, int searchType,
                  SizeType searchSize, int (*typeInq)(int id), SizeType (*sizeInq)(int id))
{
  int numNonMatching = 0, foundID = CDI_UNDEFID;
  size_t foundIdx = SIZE_MAX;
  for (size_t index = startIdx; index < numIDs; index++)
  {
    if (cdfGridList[index].ncIdList[ncIDType] != CDI_UNDEFID)
    {
      int id0 = cdfGridList[index].gridID, id0Type = typeInq(id0);
      if (id0Type == searchType)
      {
        SizeType size0 = sizeInq(id0);
        if (searchSize == size0)
        {
          foundID = cdfGridList[index].ncIdList[ncIDType];
          foundIdx = index;
          break;
        }
        numNonMatching++;
      }
    }
  }
  return (struct idSearch){ .numNonMatching = numNonMatching, .foundID = foundID, .foundIdx = foundIdx };
}

static SizeType
cdfGridInqHalfSize(int gridID)
{
  return gridInqSize(gridID) / 2;
}

static void
cdfDefSPorFC(stream_t *streamptr, int gridID, int gridIndex, char *axisname, size_t maxlen, int gridRefType)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = gridInqSize(gridID) / 2;

  struct idSearch search
      = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_Y, gridRefType, dimlen, gridInqType, cdfGridInqHalfSize);
  int dimID = search.foundID;
  int iz = search.numNonMatching;

  if (dimID == CDI_UNDEFID)
  {
    int fileID = streamptr->fileID;
    size_t len = strlen(axisname);
    if (iz) snprintf(axisname + len, maxlen - len, "%1d", iz + 1);

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    cdf_def_dim(fileID, axisname, (size_t) dimlen, &dimID);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[CDF_DIMID_Y] = dimID;
}

static void
cdfDefSP(stream_t *streamptr, int gridID, int gridIndex)
{
  // char longname[] = "Spherical harmonic coefficient";
  char axisname[5] = "nsp";
  cdfDefSPorFC(streamptr, gridID, gridIndex, axisname, sizeof(axisname), GRID_SPECTRAL);
}

static void
cdfDefFC(stream_t *streamptr, int gridID, int gridIndex)
{
  char axisname[5] = "nfc";
  cdfDefSPorFC(streamptr, gridID, gridIndex, axisname, sizeof(axisname), GRID_FOURIER);
}

static const struct cdfDefGridAxisInqs
{
  SizeType (*axisSize)(int gridID);
  double (*axisVal)(int gridID, SizeType index);
  const double *(*axisValsPtr)(int gridID);
  const double *(*axisBoundsPtr)(int gridID);
  enum cdfIDIdx dimIdx, varIdx;
  char axisSym;
  enum gridPropInq valsQueryKey, bndsQueryKey;
  char axisPanoplyName[4];
} gridInqsX = {
  .axisSize = gridInqXsize,
  .axisVal = gridInqXval,
  .axisValsPtr = gridInqXvalsPtr,
  .axisBoundsPtr = gridInqXboundsPtr,
  .dimIdx = CDF_DIMID_X,
  .varIdx = CDF_VARID_X,
  .axisSym = 'X',
  .valsQueryKey = GRID_PROP_XVALS,
  .bndsQueryKey = GRID_PROP_XBOUNDS,
  .axisPanoplyName = "Lon",
}, gridInqsY = {
  .axisSize = gridInqYsize,
  .axisVal = gridInqYval,
  .axisValsPtr = gridInqYvalsPtr,
  .axisBoundsPtr = gridInqYboundsPtr,
  .dimIdx = CDF_DIMID_Y,
  .varIdx = CDF_VARID_Y,
  .axisSym = 'Y',
  .valsQueryKey = GRID_PROP_YVALS,
  .bndsQueryKey = GRID_PROP_YBOUNDS,
  .axisPanoplyName = "Lat",
};

static void
cdfPutGridStdAtts(int fileID, int ncvarid, int gridID, int dimtype)
{
  size_t len;

  int axisKey = (dimtype == 'Z') ? CDI_GLOBAL : ((dimtype == 'X') ? CDI_XAXIS : CDI_YAXIS);

  {
    char stdname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_STDNAME, stdname, &length);
    if (stdname[0] && (len = strlen(stdname))) cdf_put_att_text(fileID, ncvarid, "standard_name", len, stdname);
  }
  {
    char longname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_LONGNAME, longname, &length);
    if (longname[0] && (len = strlen(longname))) cdf_put_att_text(fileID, ncvarid, "long_name", len, longname);
  }
  {
    char units[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_UNITS, units, &length);
    if (units[0] && (len = strlen(units))) cdf_put_att_text(fileID, ncvarid, "units", len, units);
  }
}

static int
grid_inq_xtype(int gridID)
{
  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  return (datatype == CDI_DATATYPE_FLT32) ? NC_FLOAT : NC_DOUBLE;
}

static void
cdfDefTrajLatLon(stream_t *streamptr, int gridID, int gridIndex, const struct cdfDefGridAxisInqs *inqs)
{
  nc_type xtype = grid_inq_xtype(gridID);
  CdfGrid *ncgrid = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = inqs->axisSize(gridID);
  if (dimlen != 1) Error("%c size isn't 1 for %s grid!", inqs->axisSym, gridNamePtr(gridInqType(gridID)));

  int ncvarid = ncgrid[gridIndex].ncIdList[inqs->dimIdx];
  if (ncvarid == CDI_UNDEFID)
  {
    int dimNcID = streamptr->basetime.ncvarid;
    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    char axisname[CDI_MAX_NAME];
    int axistype = (inqs->axisSym == 'X') ? CDI_XAXIS : CDI_YAXIS;
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axistype, CDI_KEY_NAME, axisname, &length);
    cdf_def_var(fileID, axisname, xtype, 1, &dimNcID, &ncvarid);
    cdfPutGridStdAtts(fileID, ncvarid, gridID, inqs->axisSym);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }

  ncgrid[gridIndex].gridID = gridID;
  // var ID for trajectory !!!
  ncgrid[gridIndex].ncIdList[inqs->dimIdx] = ncvarid;
}

static void
cdfDefTrajLon(stream_t *streamptr, int gridID, int gridIndex)
{
  cdfDefTrajLatLon(streamptr, gridID, gridIndex, &gridInqsX);
}

static void
cdfDefTrajLat(stream_t *streamptr, int gridID, int gridIndex)
{
  cdfDefTrajLatLon(streamptr, gridID, gridIndex, &gridInqsY);
}

static int
checkDimName(int fileID, size_t dimlen, char *dimname)
{
  // check whether the dimenion name is already defined with the same length
  unsigned iz = 0;
  int dimid = CDI_UNDEFID;
  char name[CDI_MAX_NAME];

  size_t len = strlen(dimname);
  memcpy(name, dimname, len + 1);

  do {
    if (iz) snprintf(name + len, CDI_MAX_NAME - len, "_%u", iz + 1);

    int dimid0;
    int status = nc_inq_dimid(fileID, name, &dimid0);
    if (status != NC_NOERR) break;
    size_t dimlen0;
    cdf_inq_dimlen(fileID, dimid0, &dimlen0);
    if (dimlen0 == dimlen)
    {
      dimid = dimid0;
      break;
    }
    iz++;
  } while (iz <= 99);

  if (iz) snprintf(dimname + len, CDI_MAX_NAME - len, "_%u", iz + 1);

  return dimid;
}

static unsigned
checkGridName(char *axisname, int fileID)
{
  // check that the name is not already defined
  unsigned iz = 0;

  char axisname2[CDI_MAX_NAME];
  size_t axisnameLen = strlen(axisname);
  memcpy(axisname2, axisname, axisnameLen + 1);

  do {
    if (iz) snprintf(axisname2 + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

    int ncdimid;
    if (nc_inq_varid(fileID, axisname2, &ncdimid) != NC_NOERR) break;

    ++iz;
  } while (iz <= 99);

  if (iz) snprintf(axisname + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

  return iz;
}

static int
checkZaxisName(char *axisname, int fileID, int vlistID, int zaxisID, int nzaxis)
{
  char axisname2[CDI_MAX_NAME];

  // check that the name is not already defined
  unsigned iz = 0;

  size_t axisnameLen = strlen(axisname);
  memcpy(axisname2, axisname, axisnameLen + 1);
  do {
    if (iz) snprintf(axisname2 + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

    int ncdimid;
    int status = nc_inq_varid(fileID, axisname2, &ncdimid);
    if (status != NC_NOERR)
    {
      if (iz)
      {
        // check that the name does not exist for other zaxes
        for (int index = 0; index < nzaxis; index++)
        {
          int zaxisID0 = vlistZaxis(vlistID, index);
          if (zaxisID != zaxisID0)
          {
            const char *axisname0 = zaxisInqNamePtr(zaxisID0);
            if (str_is_equal(axisname0, axisname2)) goto nextSuffix;
          }
        }
      }
      break;
    }

  nextSuffix:
    ++iz;
  } while (iz <= 99);

  if (iz) snprintf(axisname + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

  return (int) iz;
}

struct cdfPostDefPutVar
{
  int fileID, ncvarid;
  union
  {
    const void *array;
    int int1;
  } values;
};

static void
cdfDelayedPutVarDouble(void *data)
{
  struct cdfPostDefPutVar *put = (struct cdfPostDefPutVar *) data;
  cdf_put_var_double(put->fileID, put->ncvarid, (const double *) put->values.array);
}

static void
cdfDelayedPutVarInt1(void *data)
{
  struct cdfPostDefPutVar *put = (struct cdfPostDefPutVar *) data;
  cdf_put_var_int(put->fileID, put->ncvarid, &put->values.int1);
}

void
cdfDelayedPutVarDeepCleanup(void *data)
{
  struct cdfPostDefPutVar *what = (struct cdfPostDefPutVar *) data;
  Free((void *) what->values.array);
  Free(what);
}

static void
cdfPostDefActionApply(size_t numActions, struct cdfPostDefAction *actions)
{
  for (size_t i = 0; i < numActions; ++i) actions[i].action(actions[i].data);
}

static void
cdfPostDefActionListDelete(struct cdfPostDefActionList *list)
{
  struct cdfPostDefAction *actions = list->actions;
  for (size_t i = 0, len = list->len; i < len; ++i)
  {
    void (*cleanup)(void *) = actions[i].cleanup;
    void *data = actions[i].data;
    (cleanup == (void (*)(void *))(void (*)(void)) memFree) ? Free(data) : cleanup(data);
  }
  Free(list);
}

struct cdfPostDefActionList *
cdfPostDefActionAdd(struct cdfPostDefActionList *list, struct cdfPostDefAction addendum)
{
  size_t appendPos = list ? list->len : 0;
  if (!list || list->size == list->len)
  {
    enum
    {
      initialListSize = 1
    };
    size_t newSize = list ? (list->size * 2) : initialListSize, newLen = list ? list->len + 1 : 1,
           newAllocSize = sizeof(struct cdfPostDefActionList) + newSize * sizeof(struct cdfPostDefAction);
    list = (struct cdfPostDefActionList *) Realloc(list, newAllocSize);
    list->size = newSize;
    list->len = newLen;
  }
  else
    ++(list->len);
  list->actions[appendPos] = addendum;
  return list;
}

static struct cdfPostDefActionList *
cdfPostDefActionConcat(struct cdfPostDefActionList *listA, const struct cdfPostDefActionList *listB)
{
  size_t appendPos = listA ? listA->len : 0, appendLen = listB ? listB->len : 0;
  if (appendLen)
  {
    size_t newLen = appendPos + appendLen;
    if (!listA || listA->size < newLen)
    {
      enum
      {
        initialListSize = 1
      };
      size_t newSize = listA ? listA->size : initialListSize;
      while (newSize < newLen) newSize *= 2;
      size_t newAllocSize = sizeof(struct cdfPostDefActionList) + newSize * sizeof(struct cdfPostDefAction);
      listA = (struct cdfPostDefActionList *) Realloc(listA, newAllocSize);
      listA->size = newSize;
      listA->len = newLen;
    }
    else
      listA->len = newLen;
    struct cdfPostDefAction *restrict actionsA = listA->actions;
    const struct cdfPostDefAction *restrict actionsB = listB->actions;
    for (size_t i = 0; i < appendLen; ++i) actionsA[appendPos + i] = actionsB[i];
  }
  return listA;
}

void
cdfPostDefActionAddPutVal(struct cdfPostDefActionList **list_, int fileID, int ncvarid, const double *values,
                          void (*cleanup)(void *))
{
  struct cdfPostDefPutVar *delayedPutVals = (struct cdfPostDefPutVar *) Malloc(sizeof(*delayedPutVals));
  delayedPutVals->values.array = values;
  delayedPutVals->fileID = fileID;
  delayedPutVals->ncvarid = ncvarid;
  *list_ = cdfPostDefActionAdd(
      *list_, (struct cdfPostDefAction){ .data = (void *) delayedPutVals, .action = cdfDelayedPutVarDouble, .cleanup = cleanup });
}

static inline void
cdfPostDefActionAddPut1Int(struct cdfPostDefActionList **list_, int fileID, int ncvarid, int iVal, void (*cleanup)(void *))
{
  struct cdfPostDefPutVar *delayedPutVals = (struct cdfPostDefPutVar *) Malloc(sizeof(*delayedPutVals));
  delayedPutVals->values.int1 = iVal;
  delayedPutVals->fileID = fileID;
  delayedPutVals->ncvarid = ncvarid;
  *list_ = cdfPostDefActionAdd(
      *list_, (struct cdfPostDefAction){ .data = (void *) delayedPutVals, .action = cdfDelayedPutVarInt1, .cleanup = cleanup });
}

static void
cdfGridCompress(int fileID, int ncvarid, size_t gridsize, int filetype, int comptype, size_t *chunks)
{
  if (gridsize >= 32 && comptype == CDI_COMPRESS_ZIP
      && (filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C || filetype == CDI_FILETYPE_NCZARR))
  {
    cdf_def_var_chunking(fileID, ncvarid, NC_CHUNKED, chunks);
    int shuffle = 1, compLevel = 1;
    cdfDefVarDeflate(fileID, ncvarid, shuffle, compLevel);
  }
}

static struct cdfPostDefActionList *
cdfDefAxisCommon(stream_t *streamptr, int gridID, int gridIndex, int ndims, bool addVarToGrid,
                 const struct cdfDefGridAxisInqs *gridAxisInq, int axisKey, char axisLetter,
                 void (*finishCyclicBounds)(double *pbounds, SizeType dimlen, const double *pvals))
{
  int dimID = CDI_UNDEFID;
  SizeType dimlen = gridAxisInq->axisSize(gridID);
  nc_type xtype = grid_inq_xtype(gridID);

  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  bool hasVals = gridInqPropPresence(gridID, gridAxisInq->valsQueryKey);
  char dimname[CDI_MAX_NAME + 3];
  dimname[0] = 0;
  int length = sizeof(dimname);
  if (ndims && !hasVals) cdiInqKeyString(gridID, axisKey, CDI_KEY_DIMNAME, dimname, &length);

  for (int index = 0; index < gridIndex; ++index)
  {
    int gridID0 = cdfGridList[index].gridID;
    assert(gridID0 != CDI_UNDEFID);
    int gridType0 = gridInqType(gridID0);
    if (gridType0 == GRID_GAUSSIAN || gridType0 == GRID_LONLAT || gridType0 == GRID_PROJECTION || gridType0 == GRID_GENERIC)
    {
      SizeType dimlen0 = gridAxisInq->axisSize(gridID0);
      char dimname0[CDI_MAX_NAME];
      dimname0[0] = 0;
      length = sizeof(dimname0);
      if (dimname[0]) cdiInqKeyString(gridID0, axisKey, CDI_KEY_DIMNAME, dimname0, &length);
      bool lname = dimname0[0] ? str_is_equal(dimname, dimname0) : true;
      if (dimlen == dimlen0 && lname)
      {
        double (*inqVal)(int gridID, SizeType index) = gridAxisInq->axisVal;
        if (IS_EQUAL(inqVal(gridID0, 0), inqVal(gridID, 0)) && IS_EQUAL(inqVal(gridID0, dimlen - 1), inqVal(gridID, dimlen - 1)))
        {
          dimID = cdfGridList[index].ncIdList[(axisLetter == 'X') ? CDF_DIMID_X : CDF_DIMID_Y];
          break;
        }
      }
    }
  }

  struct cdfPostDefActionList *delayed = NULL;
  if (dimID == CDI_UNDEFID)
  {
    int ncvarid = CDI_UNDEFID;
    char axisname[CDI_MAX_NAME];
    length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_NAME, axisname, &length);
    int fileID = streamptr->fileID;
    if (axisname[0] == 0) Error("axis name undefined!");

    checkGridName(axisname, fileID);
    size_t axisnameLen = strlen(axisname);

    bool switchNCMode = (streamptr->ncmode == 2 && (hasVals || ndims));
    if (switchNCMode)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    if (ndims)
    {
      if (dimname[0] == 0) strcpy(dimname, axisname);
      dimID = checkDimName(fileID, (size_t) dimlen, dimname);
      if (dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);
    }

    if (hasVals)
    {
      cdf_def_var(fileID, axisname, xtype, ndims, &dimID, &ncvarid);

      int chunkSize = 0;
      int chunkType = CDI_CHUNK_GRID;
      cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKTYPE, &chunkType);
      cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKSIZE, &chunkSize);
      if (chunkSize > 0) chunkType = CDI_CHUNK_AUTO;

      if (chunkType == CDI_CHUNK_GRID && dimlen > ChunkSizeLim) chunkType = CDI_CHUNK_LINES;

      size_t chunk = calc_chunksize_x(chunkType, chunkSize, (size_t) dimlen, true);
      cdfGridCompress(fileID, ncvarid, (size_t) dimlen, streamptr->filetype, streamptr->comptype, &chunk);

      cdfPutGridStdAtts(fileID, ncvarid, gridID, axisLetter);
      {
        char axisStr[2] = { axisLetter, '\0' };
        cdf_put_att_text(fileID, ncvarid, "axis", 1, axisStr);
      }
      cdfFuncPtrPostDefActionGridProp mycdfPostDefActionGridProp
          = (cdfFuncPtrPostDefActionGridProp) namespaceSwitchGet(NSSWITCH_CDF_POSTDEFACTION_GRID_PROP).func;
      mycdfPostDefActionGridProp(streamptr, gridID, ncvarid, gridAxisInq->valsQueryKey, &delayed);

      bool genBounds = false, hasBounds = gridInqPropPresence(gridID, gridAxisInq->bndsQueryKey);
      bool grid_is_cyclic = (gridIsCyclic(gridID) > 0);
      double *restrict pbounds;
      size_t nvertex = (size_t) gridInqNvertex(gridID);
      if (CDI_CMOR_Mode && grid_is_cyclic && !hasBounds)
      {
        const double *pvals = gridAxisInq->axisValsPtr(gridID);
        genBounds = true;
        nvertex = 2;
        pbounds = (double *) Malloc(2 * (size_t) dimlen * sizeof(double));
        for (size_t i = 0; i < (size_t) dimlen - 1; ++i)
        {
          pbounds[i * 2 + 1] = (pvals[i] + pvals[i + 1]) * 0.5;
          pbounds[i * 2 + 2] = (pvals[i] + pvals[i + 1]) * 0.5;
        }
        finishCyclicBounds(pbounds, dimlen, pvals);
      }
      else
        pbounds = (double *) gridAxisInq->axisBoundsPtr(gridID);

      int nvdimID = CDI_UNDEFID;
      if (pbounds)
      {
        if (nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR) cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);
      }
      if ((hasBounds || genBounds) && nvdimID != CDI_UNDEFID)
      {
        char boundsname[CDI_MAX_NAME];
        memcpy(boundsname, axisname, axisnameLen);
        boundsname[axisnameLen] = '_';
        memcpy(boundsname + axisnameLen + 1, bndsName, sizeof(bndsName));
        int dimIDs[2] = { dimID, nvdimID };
        int ncbvarid;
        cdf_def_var(fileID, boundsname, xtype, 2, dimIDs, &ncbvarid);
        cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), boundsname);
        cdfPostDefActionAddPutVal(&delayed, fileID, ncbvarid, pbounds,
                                  genBounds ? cdfDelayedPutVarDeepCleanup : (void (*)(void *))(void (*)(void)) memFree);
      }
    }

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }

    if (ndims == 0 || addVarToGrid) cdfGridList[gridIndex].ncIdList[(axisLetter == 'X') ? CDF_VARID_X : CDF_VARID_Y] = ncvarid;
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[(axisLetter == 'X') ? CDF_DIMID_X : CDF_DIMID_Y] = dimID;

  return delayed;
}

static void
finishCyclicXBounds(double *pbounds, SizeType dimlen, const double *pvals)
{
  pbounds[0] = (pvals[0] + pvals[dimlen - 1] - 360) * 0.5;
  pbounds[2 * dimlen - 1] = (pvals[dimlen - 1] + pvals[0] + 360) * 0.5;
}

static void
finishCyclicYBounds(double *pbounds, SizeType dimlen, const double *pvals)
{
  pbounds[0] = copysign(90.0, pvals[0]);
  pbounds[2 * dimlen - 1] = copysign(90.0, pvals[dimlen - 1]);
}

static struct cdfPostDefActionList *
cdfDefXaxis(stream_t *streamptr, int gridID, int gridIndex, int ndims, bool addVarToGrid)
{
  return cdfDefAxisCommon(streamptr, gridID, gridIndex, ndims, addVarToGrid, &gridInqsX, CDI_XAXIS, 'X', finishCyclicXBounds);
}

static struct cdfPostDefActionList *
cdfDefYaxis(stream_t *streamptr, int gridID, int gridIndex, int ndims, bool addVarToGrid)
{
  return cdfDefAxisCommon(streamptr, gridID, gridIndex, ndims, addVarToGrid, &gridInqsY, CDI_YAXIS, 'Y', finishCyclicYBounds);
}

static void
cdfDefGridReference(stream_t *streamptr, int gridID)
{
  int fileID = streamptr->fileID;

  int number = 0;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, &number);
  if (number > 0) cdf_put_att_int(fileID, NC_GLOBAL, "number_of_grid_used", NC_INT, 1, &number);

  grid_t *gridptr = grid_to_pointer(gridID);
  const char *gridfile = cdiInqVarKeyStringPtr(&gridptr->keys, CDI_KEY_REFERENCEURI);
  if (gridfile && gridfile[0] != 0) cdf_put_att_text(fileID, NC_GLOBAL, "grid_file_uri", strlen(gridfile), gridfile);
}

static void
cdfDefGridUUID(stream_t *streamptr, int gridID)
{
  unsigned char uuid[CDI_UUID_SIZE] = { 0 };
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
  if (!cdiUUIDIsNull(uuid))
  {
    char uuidStr[uuidNumHexChars + 1] = { 0 };
    if (cdiUUID2Str(uuid, uuidStr) == uuidNumHexChars)
    {
      int fileID = streamptr->fileID;
      // if (streamptr->ncmode == 2) cdf_redef(fileID);
      cdf_put_att_text(fileID, NC_GLOBAL, "uuidOfHGrid", uuidNumHexChars, uuidStr);
      // if (streamptr->ncmode == 2  cdf_enddef(fileID, streamptr->self);
    }
  }
}

void
cdfPostDefActionGridProp(stream_t *streamptr, int gridID, int ncvarid, enum gridPropInq gridProp,
                         struct cdfPostDefActionList **delayed)
{
  const void *valsPtr = NULL;
  switch (gridProp)
  {
    case GRID_PROP_MASK:
    case GRID_PROP_MASK_GME: Error("unsupported key: %d", (int) gridProp); break;
    case GRID_PROP_XVALS: valsPtr = gridInqXvalsPtr(gridID); break;
    case GRID_PROP_YVALS: valsPtr = gridInqYvalsPtr(gridID); break;
    case GRID_PROP_AREA: valsPtr = gridInqAreaPtr(gridID); break;
    case GRID_PROP_XBOUNDS: valsPtr = gridInqXboundsPtr(gridID); break;
    case GRID_PROP_YBOUNDS: valsPtr = gridInqYboundsPtr(gridID); break;
  }
  cdfPostDefActionAddPutVal(delayed, streamptr->fileID, ncvarid, (const double *) valsPtr,
                            (void (*)(void *))(void (*)(void)) memFree);
}

static int
cdfDefIrregularGridAxisSetup(stream_t *streamptr, int gridID, nc_type xtype, int varID, size_t dimlens, int ndims, int dimIDs[],
                             size_t *chunks, const struct cdfDefGridAxisInqs *inqs, struct cdfPostDefActionList **delayed)
{
  int ncvarid = CDI_UNDEFID;
  int fileID = streamptr->fileID;
  if (gridInqPropPresence(gridID, inqs->valsQueryKey))
  {
    char axisname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, varID, CDI_KEY_NAME, axisname, &length);
    checkGridName(axisname, fileID);
    cdf_def_var(fileID, axisname, xtype, ndims - 1, dimIDs, &ncvarid);
    cdfGridCompress(fileID, ncvarid, dimlens, streamptr->filetype, streamptr->comptype, chunks);

    cdfPutGridStdAtts(fileID, ncvarid, gridID, inqs->axisSym);
    cdfFuncPtrPostDefActionGridProp mycdfPostDefActionGridProp
        = (cdfFuncPtrPostDefActionGridProp) namespaceSwitchGet(NSSWITCH_CDF_POSTDEFACTION_GRID_PROP).func;
    mycdfPostDefActionGridProp(streamptr, gridID, ncvarid, inqs->valsQueryKey, delayed);

    // attribute for Panoply
    if (!CDI_CMOR_Mode && ndims == 3) cdf_put_att_text(fileID, ncvarid, "_CoordinateAxisType", 3, inqs->axisPanoplyName);

    if (gridInqPropPresence(gridID, inqs->bndsQueryKey) && dimIDs[ndims - 1] != CDI_UNDEFID)
    {
      size_t axisnameLen = strlen(axisname);
      axisname[axisnameLen] = '_';
      memcpy(axisname + axisnameLen + 1, bndsName, sizeof(bndsName));
      int ncbvarid;
      cdf_def_var(fileID, axisname, xtype, ndims, dimIDs, &ncbvarid);
      cdfGridCompress(fileID, ncbvarid, dimlens, streamptr->filetype, streamptr->comptype, chunks);

      cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), axisname);
      mycdfPostDefActionGridProp(streamptr, gridID, ncbvarid, inqs->bndsQueryKey, delayed);
    }
  }
  return ncvarid;
}

struct cdfDefIrregularGridCommonIDs
{
  int xdimID, ydimID, ncxvarid, ncyvarid, ncavarid;
  struct cdfPostDefActionList *delayed;
};

static struct cdfDefIrregularGridCommonIDs
cdfDefIrregularGridCommon(stream_t *streamptr, int gridID, size_t xsize, size_t ysize, int ndims, const char *xdimname_default,
                          size_t nvertex, const char *vdimname_default, bool setVdimname)
{
  nc_type xtype = grid_inq_xtype(gridID);
  int xdimID = CDI_UNDEFID;
  int ydimID = CDI_UNDEFID;
  int fileID = streamptr->fileID;

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    cdf_redef(fileID);
    streamptr->ncmode = 1;
  }

  {
    char xdimname[CDI_MAX_NAME + 3];
    int length = sizeof(xdimname);
    cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_DIMNAME, xdimname, &length);
    if (xdimname[0] == 0) strcpy(xdimname, xdimname_default);
    xdimID = checkDimName(fileID, xsize, xdimname);
    if (xdimID == CDI_UNDEFID) cdf_def_dim(fileID, xdimname, xsize, &xdimID);
  }

  if (ndims == 3)
  {
    char ydimname[CDI_MAX_NAME + 3];
    int length = sizeof(ydimname);
    cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_DIMNAME, ydimname, &length);
    if (ydimname[0] == 0)
    {
      ydimname[0] = 'y';
      ydimname[1] = 0;
    }
    ydimID = checkDimName(fileID, ysize, ydimname);
    if (ydimID == CDI_UNDEFID) cdf_def_dim(fileID, ydimname, ysize, &ydimID);
  }

  int nvdimID = CDI_UNDEFID;
  int dimIDs[3];
  dimIDs[ndims - 1] = CDI_UNDEFID;
  if (setVdimname)
  {
    char vdimname[CDI_MAX_NAME + 3];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_VDIMNAME, vdimname, &length);
    if (vdimname[0] == 0) strcpy(vdimname, vdimname_default);
    nvdimID = dimIDs[ndims - 1] = checkDimName(fileID, nvertex, vdimname);
    if (nvdimID == CDI_UNDEFID) cdf_def_dim(fileID, vdimname, nvertex, dimIDs + ndims - 1);
  }

  size_t gridsize = xsize * ysize;
  size_t chunks[3] = { 1, 1, 1 };
  int chunkSize = 0;
  int chunkType = CDI_CHUNK_GRID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKTYPE, &chunkType);
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKSIZE, &chunkSize);
  if (chunkSize > 0 && ydimID == CDI_UNDEFID) chunkType = CDI_CHUNK_AUTO;

  if (chunkType == CDI_CHUNK_GRID && gridsize > ChunkSizeLim) chunkType = CDI_CHUNK_LINES;

  if (ndims == 3)
  {
    chunks[0] = calc_chunksize_y(chunkType, gridsize, xsize, ysize);
    chunks[1] = calc_chunksize_x(chunkType, chunkSize, xsize, (ydimID == CDI_UNDEFID));
    dimIDs[0] = ydimID;
    dimIDs[1] = xdimID;
  }
  else  // ndims == 2
  {
    chunks[0] = calc_chunksize_x(chunkType, chunkSize, xsize, (ydimID == CDI_UNDEFID));
    dimIDs[0] = xdimID;
    cdfDefGridReference(streamptr, gridID);
    cdfDefGridUUID(streamptr, gridID);
  }

  struct cdfPostDefActionList *delayed = NULL;
  int ncxvarid
      = cdfDefIrregularGridAxisSetup(streamptr, gridID, xtype, CDI_XAXIS, gridsize, ndims, dimIDs, chunks, &gridInqsX, &delayed);
  int ncyvarid
      = cdfDefIrregularGridAxisSetup(streamptr, gridID, xtype, CDI_YAXIS, gridsize, ndims, dimIDs, chunks, &gridInqsY, &delayed);

  int ncavarid = CDI_UNDEFID;
  if (gridInqPropPresence(gridID, GRID_PROP_AREA))
  {
    static const char axisname_[] = "cell_area";
    static const char units[] = "m2";
    static const char longname[] = "area of grid cell";
    static const char stdname[] = "cell_area";

    cdf_def_var(fileID, axisname_, xtype, ndims - 1, dimIDs, &ncavarid);

    cdf_put_att_text(fileID, ncavarid, "standard_name", sizeof(stdname) - 1, stdname);
    cdf_put_att_text(fileID, ncavarid, "long_name", sizeof(longname) - 1, longname);
    cdf_put_att_text(fileID, ncavarid, "units", sizeof(units) - 1, units);
    cdfFuncPtrPostDefActionGridProp mycdfPostDefActionGridProp
        = (cdfFuncPtrPostDefActionGridProp) namespaceSwitchGet(NSSWITCH_CDF_POSTDEFACTION_GRID_PROP).func;
    mycdfPostDefActionGridProp(streamptr, gridID, ncavarid, GRID_PROP_AREA, &delayed);
  }

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  return (struct cdfDefIrregularGridCommonIDs){
    .xdimID = xdimID, .ydimID = ydimID, .ncxvarid = ncxvarid, .ncyvarid = ncyvarid, .ncavarid = ncavarid, .delayed = delayed
  };
}

static struct cdfPostDefActionList *
cdfDefCurvilinear(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = gridInqSize(gridID);
  SizeType xdimlen = gridInqXsize(gridID);
  SizeType ydimlen = gridInqYsize(gridID);

  int xdimID = CDI_UNDEFID, ydimID = CDI_UNDEFID;
  int ncxvarid = CDI_UNDEFID, ncyvarid = CDI_UNDEFID, ncavarid = CDI_UNDEFID;

  size_t ofs = 0;
  do {
    struct idSearch search
        = cdfSearchIDBySize(ofs, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_CURVILINEAR, dimlen, gridInqType, gridInqSize);
    size_t index = search.foundIdx;
    if (index != SIZE_MAX)
    {
      int gridID0 = cdfGridList[index].gridID;
      if (IS_EQUAL(gridInqXval(gridID0, 0), gridInqXval(gridID, 0))
          && IS_EQUAL(gridInqXval(gridID0, dimlen - 1), gridInqXval(gridID, dimlen - 1))
          && IS_EQUAL(gridInqYval(gridID0, 0), gridInqYval(gridID, 0))
          && IS_EQUAL(gridInqYval(gridID0, dimlen - 1), gridInqYval(gridID, dimlen - 1)))
      {
        xdimID = cdfGridList[index].ncIdList[CDF_DIMID_X];
        ydimID = cdfGridList[index].ncIdList[CDF_DIMID_Y];
        ncxvarid = cdfGridList[index].ncIdList[CDF_VARID_X];
        ncyvarid = cdfGridList[index].ncIdList[CDF_VARID_Y];
        break;
      }
      ofs = search.foundIdx;
      if (ofs < (size_t) gridIndex) continue;
    }
  } while (false);

  struct cdfPostDefActionList *delayed = NULL;
  if (xdimID == CDI_UNDEFID || ydimID == CDI_UNDEFID)
  {
    struct cdfDefIrregularGridCommonIDs createdIDs = cdfDefIrregularGridCommon(
        streamptr, gridID, (size_t) xdimlen, (size_t) ydimlen, 3, "x", 4, "nv4",
        gridInqPropPresence(gridID, GRID_PROP_XBOUNDS) || gridInqPropPresence(gridID, GRID_PROP_YBOUNDS));
    xdimID = createdIDs.xdimID;
    ydimID = createdIDs.ydimID;
    ncxvarid = createdIDs.ncxvarid;
    ncyvarid = createdIDs.ncyvarid;
    ncavarid = createdIDs.ncavarid;
    delayed = createdIDs.delayed;
  }

  CdfGrid *cdfGrid = &cdfGridList[gridIndex];
  cdfGrid->gridID = gridID;
  cdfGrid->ncIdList[CDF_DIMID_X] = xdimID;
  cdfGrid->ncIdList[CDF_DIMID_Y] = ydimID;
  cdfGrid->ncIdList[CDF_VARID_X] = ncxvarid;
  cdfGrid->ncIdList[CDF_VARID_Y] = ncyvarid;
  cdfGrid->ncIdList[CDF_VARID_A] = ncavarid;

  return delayed;
}

static struct cdfPostDefActionList *
cdfDefUnstructured(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = gridInqSize(gridID);

  int dimID = CDI_UNDEFID;
  int ncxvarid = CDI_UNDEFID, ncyvarid = CDI_UNDEFID, ncavarid = CDI_UNDEFID;

  size_t ofs = 0;
  do {
    struct idSearch search
        = cdfSearchIDBySize(ofs, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_UNSTRUCTURED, dimlen, gridInqType, gridInqSize);
    size_t index = search.foundIdx;
    if (index != SIZE_MAX)
    {
      int gridID0 = cdfGridList[index].gridID;
      if (gridInqNvertex(gridID0) == gridInqNvertex(gridID) && IS_EQUAL(gridInqXval(gridID0, 0), gridInqXval(gridID, 0))
          && IS_EQUAL(gridInqXval(gridID0, dimlen - 1), gridInqXval(gridID, dimlen - 1))
          && IS_EQUAL(gridInqYval(gridID0, 0), gridInqYval(gridID, 0))
          && IS_EQUAL(gridInqYval(gridID0, dimlen - 1), gridInqYval(gridID, dimlen - 1)))
      {
        dimID = cdfGridList[index].ncIdList[CDF_DIMID_X];
        ncxvarid = cdfGridList[index].ncIdList[CDF_VARID_X];
        ncyvarid = cdfGridList[index].ncIdList[CDF_VARID_Y];
        ncavarid = cdfGridList[index].ncIdList[CDF_VARID_A];
        break;
      }
      ofs = search.foundIdx;
      if (ofs < (size_t) gridIndex) continue;
    }
  } while (false);

  struct cdfPostDefActionList *delayed = NULL;
  if (dimID == CDI_UNDEFID)
  {
    size_t nvertex = (size_t) gridInqNvertex(gridID);
    struct cdfDefIrregularGridCommonIDs createdIDs
        = cdfDefIrregularGridCommon(streamptr, gridID, (size_t) dimlen, 1, 2, "ncells", nvertex, "vertices", nvertex > 0);
    dimID = createdIDs.xdimID;
    ncxvarid = createdIDs.ncxvarid;
    ncyvarid = createdIDs.ncyvarid;
    ncavarid = createdIDs.ncavarid;
    delayed = createdIDs.delayed;
  }

  CdfGrid *cdfGrid = &cdfGridList[gridIndex];
  cdfGrid->gridID = gridID;
  cdfGrid->ncIdList[CDF_DIMID_X] = dimID;
  cdfGrid->ncIdList[CDF_VARID_X] = ncxvarid;
  cdfGrid->ncIdList[CDF_VARID_Y] = ncyvarid;
  cdfGrid->ncIdList[CDF_VARID_A] = ncavarid;

  return delayed;
}

struct attTxtTab
{
  const char *txt;
  size_t txtLen;
};

struct attTxtTab2
{
  const char *attName, *attVal;
  size_t valLen;
};

static struct cdfPostDefActionList *
cdf_def_vct_echam(stream_t *streamptr, int zaxisID)
{
  int type = zaxisInqType(zaxisID);

  int ilev;
  struct cdfPostDefActionList *delayed = NULL;
  if ((type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF) && (ilev = zaxisInqVctSize(zaxisID) / 2) != 0)
  {
    int mlev = ilev - 1;
    CdfInfo *cdfInfo = &(streamptr->cdfInfo);
    if (cdfInfo->vct.ilev > 0)
    {
      if (cdfInfo->vct.ilev != ilev) Error("More than one VCT for each file unsupported!");
      return delayed;
    }

    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    int ncdimid = -1, ncdimid2 = -1;
    int hyaiid, hybiid, hyamid = -1, hybmid = -1;

    cdf_def_dim(fileID, "nhyi", (size_t) ilev, &ncdimid2);
    cdf_def_var(fileID, "hyai", NC_DOUBLE, 1, &ncdimid2, &hyaiid);
    cdf_def_var(fileID, "hybi", NC_DOUBLE, 1, &ncdimid2, &hybiid);
    if (mlev > 0)
    {
      cdf_def_dim(fileID, "nhym", (size_t) mlev, &ncdimid);
      cdf_def_var(fileID, "hyam", NC_DOUBLE, 1, &ncdimid, &hyamid);
      cdf_def_var(fileID, "hybm", NC_DOUBLE, 1, &ncdimid, &hybmid);
    }

    cdfInfo->vct.ilev = ilev;
    cdfInfo->vct.mlev = mlev;
    cdfInfo->vct.mlevID = ncdimid;
    cdfInfo->vct.ilevID = ncdimid2;

    {
      static const char lname_n[] = "long_name", units_n[] = "units", lname_v_ai[] = "hybrid A coefficient at layer interfaces",
                        units_v_ai[] = "Pa", lname_v_bi[] = "hybrid B coefficient at layer interfaces", units_v_bi[] = "1";
      static const struct attTxtTab2 tab[] = {
        { lname_n, lname_v_ai, sizeof(lname_v_ai) - 1 },
        { units_n, units_v_ai, sizeof(units_v_ai) - 1 },
        { lname_n, lname_v_bi, sizeof(lname_v_bi) - 1 },
        { units_n, units_v_bi, sizeof(units_v_bi) - 1 },
      };
      enum
      {
        tabLen = sizeof(tab) / sizeof(tab[0])
      };
      int ids[tabLen] = { hyaiid, hyaiid, hybiid, hybiid };
      for (size_t i = 0; i < tabLen; ++i) cdf_put_att_text(fileID, ids[i], tab[i].attName, tab[i].valLen, tab[i].attVal);
    }

    {
      static const char lname_n[] = "long_name", units_n[] = "units", lname_v_am[] = "hybrid A coefficient at layer midpoints",
                        units_v_am[] = "Pa", lname_v_bm[] = "hybrid B coefficient at layer midpoints", units_v_bm[] = "1";
      static const struct attTxtTab2 tab[] = {
        { lname_n, lname_v_am, sizeof(lname_v_am) - 1 },
        { units_n, units_v_am, sizeof(units_v_am) - 1 },
        { lname_n, lname_v_bm, sizeof(lname_v_bm) - 1 },
        { units_n, units_v_bm, sizeof(units_v_bm) - 1 },
      };
      enum
      {
        tabLen = sizeof(tab) / sizeof(tab[0])
      };
      int ids[tabLen] = { hyamid, hyamid, hybmid, hybmid };
      for (size_t i = 0; i < tabLen; ++i) cdf_put_att_text(fileID, ids[i], tab[i].attName, tab[i].valLen, tab[i].attVal);
    }

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }

    const double *vctptr = zaxisInqVctPtr(zaxisID);

    cdfPostDefActionAddPutVal(&delayed, fileID, hyaiid, vctptr, (void (*)(void *))(void (*)(void)) memFree);
    cdfPostDefActionAddPutVal(&delayed, fileID, hybiid, vctptr + ilev, (void (*)(void *))(void (*)(void)) memFree);
    {
      double *restrict amidVal = (double *) Malloc((size_t) mlev * sizeof(*amidVal));
      for (size_t i = 0; i < (size_t) mlev; ++i) amidVal[i] = (vctptr[i] + vctptr[i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hyamid, amidVal, cdfDelayedPutVarDeepCleanup);
    }
    {
      double *restrict bmidVal = (double *) Malloc((size_t) mlev * sizeof(*bmidVal));
      for (size_t i = 0; i < (size_t) mlev; ++i) bmidVal[i] = (vctptr[(size_t) ilev + i] + vctptr[(size_t) ilev + i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hybmid, bmidVal, cdfDelayedPutVarDeepCleanup);
    }
  }
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_vct_cf(stream_t *streamptr, int zaxisID, int nclevID, int ncbndsID, int p0status, double p0value)
{
  int type = zaxisInqType(zaxisID);

  struct cdfPostDefActionList *delayed = NULL;
  int ilev;
  if ((type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF) && (ilev = zaxisInqVctSize(zaxisID) / 2) != 0)
  {
    int mlev = ilev - 1;
    CdfInfo *cdfInfo = &(streamptr->cdfInfo);
    if (cdfInfo->vct.ilev > 0)
    {
      if (cdfInfo->vct.ilev != ilev) Error("more than one VCT for each file unsupported!");
      return delayed;
    }

    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    int dimIDs[2] = { nclevID, ncbndsID };

    cdfInfo->vct.mlev = mlev;
    cdfInfo->vct.ilev = ilev;
    cdfInfo->vct.mlevID = nclevID;
    cdfInfo->vct.ilevID = nclevID;

    int hyamid, hybmid;
    cdf_def_var(fileID, (p0status == 0) ? "a" : "ap", NC_DOUBLE, 1, dimIDs, &hyamid);
    cdf_def_var(fileID, "b", NC_DOUBLE, 1, dimIDs, &hybmid);

    {
      static const char anametab[][10] = { "long_name", "units" };
      static const char lname_v_a[] = "vertical coordinate formula term: ap(k)",
                        lname_v_b[] = "vertical coordinate formula term: b(k)", units_v_a[] = "Pa", units_v_b[] = "1";
      static struct attTxtTab attvtab[][2] = { { { lname_v_a, sizeof(lname_v_a) - 1 }, { units_v_a, sizeof(units_v_a) - 1 } },
                                               { { lname_v_b, sizeof(lname_v_b) - 1 }, { units_v_b, sizeof(units_v_b) - 1 } } };
      int termid[] = { hyamid, hybmid };
      enum
      {
        numTerms = sizeof(termid) / sizeof(termid[0]),
        numAtts = sizeof(anametab) / sizeof(anametab[0]),
      };
      for (size_t termIdx = 0; termIdx < numTerms; ++termIdx)
        for (size_t attIdx = 0; attIdx < numAtts; ++attIdx)
          cdf_put_att_text(fileID, termid[termIdx], anametab[attIdx], attvtab[termIdx][attIdx].txtLen,
                           attvtab[termIdx][attIdx].txt);
    }
    double *restrict vctptr = (double *) zaxisInqVctPtr(zaxisID);
    if (p0status == 0 && IS_NOT_EQUAL(p0value, 0))
    {
      double *restrict temp = (double *) Malloc((size_t) 2 * ilev * sizeof(*temp));
      memcpy(temp, vctptr, (size_t) 2 * ilev * sizeof(*temp));
      for (size_t i = 0; i < (size_t) ilev; ++i) temp[i] /= p0value;
      vctptr = temp;
    }

    {
      double *restrict mlevValA = (double *) Malloc((size_t) mlev * sizeof(*mlevValA));
      for (size_t i = 0; i < (size_t) mlev; ++i) mlevValA[i] = (vctptr[i] + vctptr[i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hyamid, mlevValA, cdfDelayedPutVarDeepCleanup);
    }
    {
      double *restrict mlevValB = (double *) Malloc((size_t) mlev * sizeof(*mlevValB));
      for (size_t i = 0; i < (size_t) mlev; ++i) mlevValB[i] = (vctptr[(size_t) ilev + i] + vctptr[(size_t) ilev + i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hybmid, mlevValB, cdfDelayedPutVarDeepCleanup);
    }

    if (ncbndsID != -1)
    {
      int hyaiid, hybiid;
      cdf_def_var(fileID, (p0status == 0) ? "a_bnds" : "ap_bnds", NC_DOUBLE, 2, dimIDs, &hyaiid);
      cdf_def_var(fileID, "b_bnds", NC_DOUBLE, 2, dimIDs, &hybiid);
      static const char anametab[][10] = { "long_name", "units" };
      static const char lname_v_a[] = "vertical coordinate formula term: ap(k+1/2)",
                        lname_v_b[] = "vertical coordinate formula term: b(k+1/2)", units_v_a[] = "Pa", units_v_b[] = "1";
      static struct attTxtTab attvtab[][2] = { { { lname_v_a, sizeof(lname_v_a) - 1 }, { units_v_a, sizeof(units_v_a) - 1 } },
                                               { { lname_v_b, sizeof(lname_v_b) - 1 }, { units_v_b, sizeof(units_v_b) - 1 } } };
      int termid[] = { hyaiid, hybiid };
      enum
      {
        numTerms = sizeof(termid) / sizeof(termid[0]),
        numAtts = sizeof(anametab) / sizeof(anametab[0]),
      };
      for (size_t termIdx = 0; termIdx < numTerms; ++termIdx)
        for (size_t attIdx = 0; attIdx < numAtts; ++attIdx)
          cdf_put_att_text(fileID, termid[termIdx], anametab[attIdx], attvtab[termIdx][attIdx].txtLen,
                           attvtab[termIdx][attIdx].txt);

      {
        double *restrict ilevValA = (double *) Malloc((size_t) mlev * 2 * sizeof(*ilevValA));
        for (size_t i = 0; i < (size_t) mlev; ++i)
        {
          ilevValA[2 * i] = vctptr[i];
          ilevValA[2 * i + 1] = vctptr[i + 1];
        }
        cdfPostDefActionAddPutVal(&delayed, fileID, hyaiid, ilevValA, cdfDelayedPutVarDeepCleanup);
      }
      {
        double *restrict ilevValB = (double *) Malloc((size_t) mlev * 2 * sizeof(*ilevValB));
        for (size_t i = 0; i < (size_t) mlev; ++i)
        {
          ilevValB[2 * i] = vctptr[(size_t) ilev + i];
          ilevValB[2 * i + 1] = vctptr[(size_t) ilev + i + 1];
        }
        cdfPostDefActionAddPutVal(&delayed, fileID, hybiid, ilevValB, cdfDelayedPutVarDeepCleanup);
      }
    }
    if (p0status == 0 && IS_NOT_EQUAL(p0value, 0)) Free(vctptr);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_zaxis_hybrid_echam(stream_t *streamptr, int type, int *ncvaridp, int zaxisID, int zaxisindex, int xtype, size_t dimlen,
                           int *dimID, char *axisname)
{
  int fileID = streamptr->fileID;
  struct cdfPostDefActionList *delayed = NULL;

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    streamptr->ncmode = 1;
    cdf_redef(fileID);
  }

  cdf_def_dim(fileID, axisname, dimlen, dimID);
  cdf_def_var(fileID, axisname, (nc_type) xtype, 1, dimID, ncvaridp);
  int ncvarid = *ncvaridp;

  {
    static const char sname[] = "hybrid_sigma_pressure";
    cdf_put_att_text(fileID, ncvarid, "standard_name", sizeof(sname) - 1, sname);
  }
  {
    static const char *attName[] = { "long_name", "formula", "formula_terms" };
    enum
    {
      nAtt = sizeof(attName) / sizeof(attName[0])
    };
    static const char lname_m[] = "hybrid level at layer midpoints", formula_m[] = "hyam hybm (mlev=hyam+hybm*aps)",
                      fterms_m[] = "ap: hyam b: hybm ps: aps", lname_i[] = "hybrid level at layer interfaces",
                      formula_i[] = "hyai hybi (ilev=hyai+hybi*aps)", fterms_i[] = "ap: hyai b: hybi ps: aps";
    static const struct attTxtTab tab[2][nAtt]
        = { { { lname_i, sizeof(lname_i) - 1 }, { formula_i, sizeof(formula_i) - 1 }, { fterms_i, sizeof(fterms_i) - 1 } },
            { { lname_m, sizeof(lname_m) - 1 }, { formula_m, sizeof(formula_m) - 1 }, { fterms_m, sizeof(fterms_m) - 1 } } };

    size_t tabSelect = type == ZAXIS_HYBRID;
    for (size_t i = 0; i < nAtt; ++i)
      cdf_put_att_text(fileID, ncvarid, attName[i], tab[tabSelect][i].txtLen, tab[tabSelect][i].txt);
  }

  {
    static const char units[] = "level";
    cdf_put_att_text(fileID, ncvarid, "units", sizeof(units) - 1, units);
  }
  {
    static const char direction[] = "down";
    cdf_put_att_text(fileID, ncvarid, "positive", sizeof(direction) - 1, direction);
  }

  if (zaxisInqLevels(zaxisID, NULL))
    cdfPostDefActionAddPutVal(&delayed, fileID, ncvarid, zaxisInqLevelsPtr(zaxisID), (void (*)(void *))(void (*)(void)) memFree);

  {
    struct cdfPostDefActionList *delayedVct = cdf_def_vct_echam(streamptr, zaxisID);
    delayed = cdfPostDefActionConcat(delayed, delayedVct);
    Free(delayedVct);
  }

  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  if (*dimID == CDI_UNDEFID) cdfInfo->zaxisIdList[zaxisindex] = (type == ZAXIS_HYBRID) ? cdfInfo->vct.mlevID : cdfInfo->vct.ilevID;

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_zaxis_hybrid_cf(stream_t *streamptr, int type, int *ncvaridp, int zaxisID, int zaxisindex, int xtype, size_t dimlen,
                        int *dimID, char *axisname)
{
  char psname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_PSNAME, psname, &length);
  if (psname[0] == 0) strcpy(psname, "ps");

  int fileID = streamptr->fileID;

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    streamptr->ncmode = 1;
    cdf_redef(fileID);
  }

  char p0name[CDI_MAX_NAME];
  p0name[0] = 0;
  static double p0value = 1;
  int p0varid = CDI_UNDEFID;
  int p0status = cdiInqKeyFloat(zaxisID, CDI_GLOBAL, CDI_KEY_P0VALUE, &p0value);
  if (p0status == CDI_NOERR)
  {
    length = CDI_MAX_NAME;
    cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_P0NAME, p0name, &length);
    if (p0name[0] == 0) strcpy(p0name, "p0");
    cdf_def_var(fileID, p0name, NC_DOUBLE, 0, 0, &p0varid);
    static const char longname[] = "reference pressure";
    cdf_put_att_text(fileID, p0varid, "long_name", sizeof(longname) - 1, longname);
    static const char units[] = "Pa";
    cdf_put_att_text(fileID, p0varid, "units", sizeof(units) - 1, units);
  }

  char zname[CDI_MAX_NAME];
  char zlongname[CDI_MAX_NAME];
  char zunits[CDI_MAX_NAME];
  length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, zname, &length);
  if (zname[0]) strcpy(axisname, zname);
  zlongname[0] = 0;
  size_t zlongnameLen;
  if (zlongname[0] == 0)
  {
    static const char default_zlongname[] = "hybrid sigma pressure coordinate";
    memcpy(zlongname, default_zlongname, sizeof(default_zlongname));
    zlongnameLen = sizeof(default_zlongname) - 1;
  }
  else
    zlongnameLen = strlen(zlongname);
  length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, zunits, &length);
  size_t zunitsLen;
  if (zunits[0] == 0)
  {
    zunits[0] = '1';
    zunits[1] = '\0';
    zunitsLen = 1;
  }
  else
    zunitsLen = strlen(zunits);

  cdf_def_dim(fileID, axisname, dimlen, dimID);
  cdf_def_var(fileID, axisname, (nc_type) xtype, 1, dimID, ncvaridp);
  int ncvarid = *ncvaridp;

  {
    static const char sname[] = "standard_name", lname[] = "long_name", sname_v[] = "atmosphere_hybrid_sigma_pressure_coordinate",
                      axis[] = "axis", axis_v[] = "Z", direction[] = "positive", direction_v[] = "down", units[] = "units";
    struct attTxtTab2 tab[] = {
      { sname, sname_v, sizeof(sname_v) - 1 },
      { axis, axis_v, sizeof(axis_v) - 1 },
      { direction, direction_v, sizeof(direction_v) - 1 },
      { units, zunits, zunitsLen },
      { lname, zlongname, zlongnameLen },
    };
    enum
    {
      nAtt = sizeof(tab) / sizeof(tab[0])
    };
    for (size_t i = 0; i < nAtt; ++i) cdf_put_att_text(fileID, ncvarid, tab[i].attName, tab[i].valLen, tab[i].attVal);
  }

  size_t len = 0;
  char txt[CDI_MAX_NAME * 2 + 30];
  if (p0status == 0)
    len = (size_t) (snprintf(txt, sizeof(txt), "%s%s %s%s", "a: a b: b p0: ", p0name, "ps: ", psname));
  else
    len = (size_t) (snprintf(txt, sizeof(txt), "%s%s", "ap: ap b: b ps: ", psname));
  cdf_put_att_text(fileID, ncvarid, "formula_terms", len, txt);

  int ncbvarid = CDI_UNDEFID;
  int nvdimID = CDI_UNDEFID;

  double *buffer = (double *) Malloc(2 * dimlen * sizeof(double));
  double *lbounds = buffer;
  double *ubounds = buffer + dimlen;
  double *restrict levels;

  bool hasLevels = zaxisInqLevels(zaxisID, NULL) != 0;
  if (hasLevels) { levels = (double *) zaxisInqLevelsPtr(zaxisID); }
  else
  {
    levels = (double *) Malloc(sizeof(*levels) * dimlen);
    for (size_t i = 0; i < dimlen; ++i) levels[i] = (double) (i + 1);
  }

  if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
  {
    zaxisInqLbounds(zaxisID, lbounds);
    zaxisInqUbounds(zaxisID, ubounds);
  }
  else
  {
    for (size_t i = 0; i < dimlen; ++i) lbounds[i] = levels[i];
    for (size_t i = 0; i < dimlen - 1; ++i) ubounds[i] = levels[i + 1];
    ubounds[dimlen - 1] = levels[dimlen - 1] + 1;
  }

  // if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
  {
    size_t nvertex = 2;
    if (dimlen > 1 && nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR) cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);

    if (nvdimID != CDI_UNDEFID)
    {
      size_t axisnameLen = strlen(axisname);
      axisname[axisnameLen] = '_';
      memcpy(axisname + axisnameLen + 1, bndsName, sizeof(bndsName));
      axisnameLen += sizeof(bndsName);
      int dimIDs[2] = { *dimID, nvdimID };
      cdf_def_var(fileID, axisname, (nc_type) xtype, 2, dimIDs, &ncbvarid);
      cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen, axisname);
      size_t formulatermsLen;
      if (p0status == 0)
        formulatermsLen = (size_t) (snprintf(txt, sizeof(txt), "%s%s %s%s", "a: a_bnds b: b_bnds p0: ", p0name, "ps: ", psname));
      else
        formulatermsLen = (size_t) (snprintf(txt, sizeof(txt), "%s%s", "ap: ap_bnds b: b_bnds ps: ", psname));

      {
        static const char sname[] = "standard_name", sname_v[] = "atmosphere_hybrid_sigma_pressure_coordinate",
                          formulaterms[] = "formula_terms", units[] = "units";
        struct attTxtTab2 tab[] = {
          { sname, sname_v, sizeof(sname_v) - 1 },
          { units, zunits, zunitsLen },
          { formulaterms, txt, formulatermsLen },
        };
        enum
        {
          nAtt = sizeof(tab) / sizeof(tab[0])
        };
        for (size_t i = 0; i < nAtt; ++i) cdf_put_att_text(fileID, ncbvarid, tab[i].attName, tab[i].valLen, tab[i].attVal);
      }
    }
  }

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  struct cdfPostDefActionList *delayed = NULL;

  if (p0varid != CDI_UNDEFID)
    cdfPostDefActionAddPutVal(&delayed, fileID, p0varid, &p0value, (void (*)(void *))(void (*)(void)) memFree);

  cdfPostDefActionAddPutVal(&delayed, fileID, ncvarid, levels,
                            hasLevels ? (void (*)(void *))(void (*)(void)) memFree : cdfDelayedPutVarDeepCleanup);

  if (ncbvarid != CDI_UNDEFID)
  {
    double *zbounds = (double *) Malloc(2 * dimlen * sizeof(*zbounds));
    for (size_t i = 0; i < dimlen; ++i)
    {
      zbounds[2 * i] = lbounds[i];
      zbounds[2 * i + 1] = ubounds[i];
    }
    cdfPostDefActionAddPutVal(&delayed, fileID, ncbvarid, zbounds, cdfDelayedPutVarDeepCleanup);
  }

  {
    struct cdfPostDefActionList *delayedVct = cdf_def_vct_cf(streamptr, zaxisID, *dimID, nvdimID, p0status, p0value);
    delayed = cdfPostDefActionConcat(delayed, delayedVct);
    Free(delayedVct);
  }

  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  if (*dimID == CDI_UNDEFID) cdfInfo->zaxisIdList[zaxisindex] = (type == ZAXIS_HYBRID) ? cdfInfo->vct.mlevID : cdfInfo->vct.ilevID;

  free(buffer);
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_zaxis_hybrid(stream_t *streamptr, int type, int *ncvarid, int zaxisID, int zaxisindex, int xtype, size_t dimlen, int *dimID,
                     char *axisname)
{
  bool isHybridEcham = ((!CDI_CMOR_Mode && CDI_Convention == CDI_CONVENTION_ECHAM) || type == ZAXIS_HYBRID_HALF);
  struct cdfPostDefActionList *(*def_zaxis_hybrid_delegate)(stream_t * streamptr, int type, int *ncvarid, int zaxisID,
                                                            int zaxisindex, int xtype, size_t dimlen, int *dimID, char *axisname)
      = isHybridEcham ? cdf_def_zaxis_hybrid_echam : cdf_def_zaxis_hybrid_cf;
  return def_zaxis_hybrid_delegate(streamptr, type, ncvarid, zaxisID, zaxisindex, xtype, dimlen, dimID, axisname);
}

static void
cdfDefZaxisUUID(stream_t *streamptr, int zaxisID)
{
  unsigned char uuid[CDI_UUID_SIZE] = { 0 };
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
  if (!cdiUUIDIsNull(uuid))
  {
    char uuidStr[uuidNumHexChars + 1] = { 0 };
    if (cdiUUID2Str(uuid, uuidStr) == uuidNumHexChars)
    {
      int fileID = streamptr->fileID;

      bool switchNCMode = (streamptr->ncmode == 2);
      if (switchNCMode)
      {
        streamptr->ncmode = 1;
        cdf_redef(fileID);
      }

      cdf_put_att_text(fileID, NC_GLOBAL, "uuidOfVGrid", uuidNumHexChars, uuidStr);

      if (switchNCMode)
      {
        cdf_enddef(fileID, streamptr->self);
        streamptr->ncmode = 2;
      }
    }
  }
}

#ifndef USE_MPI
static void
cdfDefZaxisChar(stream_t *streamptr, int zaxisID, char *axisname, int *dimID, size_t dimlen, int zaxisindex)
{
  int fileID = streamptr->fileID;
  int ncvarID = CDI_UNDEFID;
  if (streamptr->ncmode == 2) cdf_redef(fileID);

  // Check StrlenID
  char strlen[8] = "strlen\0";
  size_t clen = (size_t) zaxisInqCLen(zaxisID);
  if (clen == 0)
    Error("Maximal string length value is 0.\nA given character axis requires a dimension to save the maximal string length.");
  int strlenID = CDI_UNDEFID;
  strlenID = checkDimName(fileID, clen, strlen);
  if (strlenID == CDI_UNDEFID) cdf_def_dim(fileID, strlen, clen, &strlenID);

  // Check 'areatype'dimID
  char dimname[CDI_MAX_NAME + 3];
  int length = sizeof(dimname);
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
  *dimID = checkDimName(fileID, dimlen, dimname);
  if (dimlen == 0) Error("No strings delivered for a character axis.");
  if (dimname[0] == 0)
  {
    memcpy(dimname, "area_type", 10);
    dimname[10] = 0;
  }

  if (*dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, dimlen, dimID);

  int dimIDs[2];
  dimIDs[0] = *dimID;
  dimIDs[1] = strlenID;

  // Get Stringvalues
  char **cvals = zaxisInqCValsPtr(zaxisID);

  if (cvals)
  {
    // Define variable and its attributes
    cdf_def_var(fileID, axisname, NC_CHAR, 2, dimIDs, &ncvarID);

    cdfPutGridStdAtts(fileID, ncvarID, zaxisID, 'Z');
    cdf_put_att_text(fileID, ncvarID, "axis", 1, "Z");
    cdfDefineAttributes(streamptr->filetype, zaxisID, CDI_GLOBAL, fileID, ncvarID);

    streamptr->cdfInfo.ncZvarIdList[zaxisindex] = ncvarID;
    cdf_enddef(fileID, streamptr->self);

    // Write Stringvalues
    size_t start[2] = { 0, 0 }, count[2] = { 1, clen };
    for (size_t i = 0; i < dimlen; i++)
    {
      start[0] = i;
      nc_put_vara_text(fileID, ncvarID, start, count, cvals[i]);
    }
  }

  streamptr->ncmode = 2;
}
#endif

static int
zaxis_datatype_to_xtype(int datatype)
{
  if (datatype == CDI_DATATYPE_FLT32) return NC_FLOAT;
  if (datatype == CDI_DATATYPE_INT32) return NC_INT;
  if (datatype == CDI_DATATYPE_INT16) return NC_SHORT;
  return NC_DOUBLE;
}

static int
zaxis_inq_xtype(int zaxisID)
{
  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  return zaxis_datatype_to_xtype(datatype);
}

static struct cdfPostDefActionList *
cdfDefZaxis(stream_t *streamptr, int zaxisID)
{
  // char zaxisname0[CDI_MAX_NAME];
  int ncvarid = CDI_UNDEFID, ncbvarid = CDI_UNDEFID;
  int xtype = zaxis_inq_xtype(zaxisID);

  size_t dimlen = (size_t) zaxisInqSize(zaxisID);
  int type = zaxisInqType(zaxisID);

  int ndims = 1;
  struct cdfPostDefActionList *delayed = NULL;

  if (dimlen == 1)
  {
    bool isScalar = zaxisInqScalar(zaxisID) > 0;
    if (!isScalar && CDI_CMOR_Mode)
    {
      isScalar = true;
      zaxisDefScalar(zaxisID);
    }

    if (isScalar) ndims = 0;
    if (CDI_Reduce_Dim) return delayed;

    switch (type)
    {
      case ZAXIS_SURFACE:
      case ZAXIS_CLOUD_BASE:
      case ZAXIS_CLOUD_TOP:
      case ZAXIS_ISOTHERM_ZERO:
      case ZAXIS_TROPOPAUSE:
      case ZAXIS_TOA:
      case ZAXIS_SEA_BOTTOM:
      case ZAXIS_ATMOSPHERE:
      case ZAXIS_MEANSEA:
      case ZAXIS_LAKE_BOTTOM:
      case ZAXIS_SEDIMENT_BOTTOM:
      case ZAXIS_SEDIMENT_BOTTOM_TA:
      case ZAXIS_SEDIMENT_BOTTOM_TW:
      case ZAXIS_MIX_LAYER: return delayed;
    }
  }

  int vlistID = streamptr->vlistID;
  char axisname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, axisname, &length);
  int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  int nzaxis = vlistNumZaxis(vlistID);
  int fileID = streamptr->fileID;

  checkZaxisName(axisname, fileID, vlistID, zaxisID, nzaxis);

  char dimname[CDI_MAX_NAME + 3];
  dimname[0] = 0;
  if (dimname[0] == 0) strcpy(dimname, axisname);

  if (type == ZAXIS_REFERENCE) cdfDefZaxisUUID(streamptr, zaxisID);

  int dimID = CDI_UNDEFID;
  if (type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF)
  {
    delayed = cdf_def_zaxis_hybrid(streamptr, type, &ncvarid, zaxisID, zaxisindex, xtype, dimlen, &dimID, axisname);

    int natts;
    cdiInqNatts(zaxisID, CDI_GLOBAL, &natts);
    cdfDefineAttributes(streamptr->filetype, zaxisID, CDI_GLOBAL, fileID, ncvarid);
  }
#ifndef USE_MPI
  else if (type == ZAXIS_CHAR)
    cdfDefZaxisChar(streamptr, zaxisID, axisname, &dimID, dimlen, zaxisindex);
#endif
  else
  {
    dimID = checkDimName(fileID, dimlen, dimname);

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    if (ndims && dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, dimlen, &dimID);

    if (zaxisInqLevels(zaxisID, NULL))
    {
      cdf_def_var(fileID, axisname, (nc_type) xtype, ndims, &dimID, &ncvarid);

      cdfPutGridStdAtts(fileID, ncvarid, zaxisID, 'Z');

      {
        int positive = zaxisInqPositive(zaxisID);
        static const char positive_up[] = "up", positive_down[] = "down";
        static const struct attTxtTab tab[2] = {
          { positive_up, sizeof(positive_up) - 1 },
          { positive_down, sizeof(positive_down) - 1 },
        };
        if (positive == POSITIVE_UP || positive == POSITIVE_DOWN)
        {
          size_t select = (positive == POSITIVE_DOWN);
          cdf_put_att_text(fileID, ncvarid, "positive", tab[select].txtLen, tab[select].txt);
        }
      }
      cdf_put_att_text(fileID, ncvarid, "axis", 1, "Z");
      cdfPostDefActionAddPutVal(&delayed, fileID, ncvarid, zaxisInqLevelsPtr(zaxisID), (void (*)(void *))(void (*)(void)) memFree);

      if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
      {
        int nvdimID = CDI_UNDEFID;
        size_t nvertex = 2;
        if (nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR) cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);

        if (nvdimID != CDI_UNDEFID)
        {
          {
            size_t axisnameLen = strlen(axisname);
            axisname[axisnameLen] = '_';
            memcpy(axisname + axisnameLen + 1, bndsName, sizeof(bndsName));
            int dimIDs[2];
            dimIDs[0] = dimID;
            dimIDs[ndims] = nvdimID;
            cdf_def_var(fileID, axisname, (nc_type) xtype, ndims + 1, dimIDs, &ncbvarid);
            cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), axisname);
          }
          {
            double *zbounds = (double *) Malloc(4 * dimlen * sizeof(*zbounds)), *lbounds = zbounds + 2 * dimlen,
                   *ubounds = zbounds + 3 * dimlen;
            zaxisInqLbounds(zaxisID, lbounds);
            zaxisInqUbounds(zaxisID, ubounds);
            for (size_t i = 0; i < dimlen; ++i)
            {
              zbounds[2 * i] = lbounds[i];
              zbounds[2 * i + 1] = ubounds[i];
            }
            zbounds = (double *) Realloc(zbounds, 2 * dimlen * sizeof(*zbounds));
            cdfPostDefActionAddPutVal(&delayed, fileID, ncbvarid, zbounds, cdfDelayedPutVarDeepCleanup);
          }
        }
      }
      cdfDefineAttributes(streamptr->filetype, zaxisID, CDI_GLOBAL, fileID, ncvarid);
    }

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }

    if (zaxisInqLevels(zaxisID, NULL) && ndims == 0) streamptr->cdfInfo.ncZvarIdList[zaxisindex] = ncvarid;
  }

  if (dimID != CDI_UNDEFID) streamptr->cdfInfo.zaxisIdList[zaxisindex] = dimID;
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_grid_indices(stream_t *streamptr, int gridID, int gridIndex)
{
  int fileID = streamptr->fileID;

  int dataType = CDI_UNDEFID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &dataType);
  nc_type xtype = (dataType == CDI_DATATYPE_INT32) ? NC_INT : NC_INT64;

  if (xtype == NC_INT64)
  {
    int fileFormat;
    int status = nc_inq_format(fileID, &fileFormat);
    if (status == NC_NOERR && fileFormat != NC_FORMAT_NETCDF4 && fileFormat != NC_FORMAT_NETCDF4_CLASSIC)
    {
      Warning("The grid cell indices couldn't be stored because 64-bit integer is not supported by data format!");
      return NULL;
    }
  }

  SizeType dimlen = gridInqSize(gridID);

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    cdf_redef(fileID);
    streamptr->ncmode = 1;
  }

  char dimname[CDI_MAX_NAME];
  dimname[0] = 0;
  int length = sizeof(dimname);
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
  if (dimname[0] == 0) strcpy(dimname, "cell");

  int dimID = checkDimName(fileID, (size_t) dimlen, dimname);
  if (dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);

  static const char stdname[] = "healpix_index";
  int ncivarid;
  cdf_def_var(fileID, dimname, xtype, 1, &dimID, &ncivarid);

  cdf_put_att_text(fileID, ncivarid, "standard_name", sizeof(stdname) - 1, stdname);
  // cdf_put_att_text(fileID, ncivarid, "units", sizeof(units) - 1, units);

  // if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  int64_t *cellIndices = (int64_t *) Malloc((size_t) dimlen * sizeof(int64_t));
  gridInqIndices(gridID, cellIndices);
  cdf_put_var_int64(fileID, ncivarid, cellIndices);
  Free(cellIndices);

  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;
  cdfGridList[gridIndex].gridID = gridID;
  struct cdfPostDefActionList *delayed = NULL;
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_mapping(stream_t *streamptr, int gridID)
{
  struct cdfPostDefActionList *delayed = NULL;

  int natts;
  cdiInqNatts(gridID, CDI_GLOBAL, &natts);
  if (natts == 0) return delayed;

  int datatype = -1;
  int status = cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARTYPE, &datatype);
  nc_type gmapvartype = (status == CDI_NOERR) ? (nc_type) datatype : NC_INT;
  char gmapvarname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname, &length);
  if (!gmapvarname[0]) strcpy(gmapvarname, "crs");

  int fileID = streamptr->fileID;
  if (checkGridName(gmapvarname, fileID)) { cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname); }

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    streamptr->ncmode = 1;
    cdf_redef(fileID);
  }

  int ncvarid;
  int ncerrcode = nc_def_var(fileID, gmapvarname, gmapvartype, 0, NULL, &ncvarid);
  if (ncerrcode == NC_NOERR) cdfDefineAttributes(streamptr->filetype, gridID, CDI_GLOBAL, fileID, ncvarid);

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  if (ncerrcode == NC_NOERR && !xtypeIsText(gmapvartype))
  {
    cdfPostDefActionAddPut1Int(&delayed, fileID, ncvarid, 1, (void (*)(void *))(void (*)(void)) memFree);
  }
  return delayed;
}

#ifndef USE_MPI
static void
cdfDefCharacter(stream_t *streamptr, int gridID, int gridIndex, int cdiAxisID, int strlen)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;
  if (cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] != CDI_UNDEFID) return;

  bool isXaxis = (cdiAxisID == CDI_XAXIS);

  SizeType dimlen = isXaxis ? gridInqXsize(gridID) : gridInqYsize(gridID);

  // Check for all grids up to gridIndex whether it already is defined

  for (int index = 0; index < gridIndex; index++)
  {
    int gridID0 = cdfGridList[index].gridID;
    int gridtype0 = gridInqType(gridID0);
    if (gridtype0 == GRID_CHARXY)
    {
      if (gridInqXIsc(gridID0) == strlen && gridInqXsize(gridID0) == dimlen) { return; }
      else if (gridInqYIsc(gridID0) == strlen && gridInqYsize(gridID0) == dimlen) { return; }
    }
  }

  int fileID = streamptr->fileID;

  if (streamptr->ncmode == 2) cdf_redef(fileID);

  // Define Dims

  char dimname[CDI_MAX_NAME + 3];
  int length = sizeof(dimname);
  cdiInqKeyString(gridID, cdiAxisID, CDI_KEY_DIMNAME, dimname, &length);
  if (dimname[0] == 0)
  {
    memcpy(dimname, "region", 7);
    dimname[6] = 0;
  }
  int dimID = checkDimName(fileID, (size_t) dimlen, dimname);
  if (dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);

  // Define strlength dim

  strcpy(dimname, "strlen");
  int strlenID = checkDimName(fileID, strlen, dimname);
  if (strlenID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, strlen, &strlenID);

  // Define Variable

  int dimIDs[2];
  dimIDs[0] = dimID;
  dimIDs[1] = strlenID;

  char axisname[CDI_MAX_NAME];
  char **cvals = (char **) Malloc((size_t) dimlen * sizeof(char *));
  for (size_t i = 0; i < (size_t) dimlen; i++) cvals[i] = (char *) Malloc(strlen * sizeof(char));
  int ncaxisid;
  length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, cdiAxisID, CDI_KEY_NAME, axisname, &length);
  gridInqXCvals(gridID, cvals);

  int status = nc_inq_varid(fileID, axisname, &ncaxisid);
  if (status == NC_NOERR) return;

  cdf_def_var(fileID, axisname, NC_CHAR, 2, dimIDs, &ncaxisid);
  cdfPutGridStdAtts(fileID, ncaxisid, gridID, isXaxis ? 'X' : 'Y');

  cdf_enddef(fileID, streamptr->self);

  // Write Var

  size_t start[2] = { 0, 0 }, count[2] = { 1, strlen };
  for (size_t i = 0; i < (size_t) dimlen; i++)
  {
    start[0] = i;
    (void) nc_put_vara_text(fileID, ncaxisid, start, count, cvals[i]);
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[isXaxis ? CDF_DIMID_X : CDF_DIMID_Y] = dimID;
  cdfGridList[gridIndex].ncIdList[isXaxis ? CDF_VARID_X : CDF_VARID_Y] = ncaxisid;

  streamptr->ncmode = 2;
}
#endif

static void
cdfDefReducedGrid(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  cdfGridList[gridIndex].gridID = gridID;

  {
    SizeType dimlen = gridInqSize(gridID);

    struct idSearch search = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_GAUSSIAN_REDUCED, dimlen,
                                               gridInqType, gridInqSize);
    int iz = search.numNonMatching;
    int dimID = search.foundID;

    if (dimID == CDI_UNDEFID)
    {
      int fileID = streamptr->fileID;

      char axisname[16] = "rgrid";
      size_t len = strlen(axisname);
      if (iz) snprintf(axisname + len, sizeof(axisname) - len, "%1d", iz + 1);

      bool switchNCMode = (streamptr->ncmode == 2);
      if (switchNCMode)
      {
        streamptr->ncmode = 1;
        cdf_redef(fileID);
      }

      cdf_def_dim(fileID, axisname, (size_t) dimlen, &dimID);

      if (switchNCMode)
      {
        cdf_enddef(fileID, streamptr->self);
        streamptr->ncmode = 2;
      }
    }

    cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] = dimID;
  }

  {
    SizeType dimlen = gridInqYsize(gridID);

    struct idSearch search = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_RP, GRID_GAUSSIAN_REDUCED, dimlen,
                                               gridInqType, gridInqSize);
    int iz = search.numNonMatching;
    int dimID = search.foundID;

    if (dimID == CDI_UNDEFID)
    {
      int fileID = streamptr->fileID;

      char axisname[32] = "reduced_points";
      size_t len = strlen(axisname);
      if (iz) snprintf(axisname + len, sizeof(axisname) - len, "%1d", iz + 1);

      if (streamptr->ncmode == 2) cdf_redef(fileID);

      cdf_def_dim(fileID, axisname, (size_t) dimlen, &dimID);

      int ncvarid = CDI_UNDEFID;
      cdf_def_var(fileID, axisname, NC_INT, 1, &dimID, &ncvarid);

      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;

      int *reducedPoints = (int *) Malloc((size_t) dimlen * sizeof(int));
      gridInqReducedPoints(gridID, reducedPoints);
      cdf_put_var_int(fileID, ncvarid, reducedPoints);
      Free(reducedPoints);

      cdfGridList[gridIndex].ncIdList[CDF_VARID_RP] = ncvarid;
    }

    cdfGridList[gridIndex].ncIdList[CDF_DIMID_RP] = dimID;
  }
}

static void
cdf_define_generic_dim(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;
  SizeType dimlen = gridInqSize(gridID);
  int dimID = CDI_UNDEFID;

  if (gridInqYsize(gridID) == 0)
  {
    struct idSearch search
        = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_GENERIC, dimlen, gridInqType, gridInqSize);
    dimID = search.foundID;
  }

  if (gridInqXsize(gridID) == 0)
  {
    struct idSearch search
        = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_Y, GRID_GENERIC, dimlen, gridInqType, gridInqSize);
    dimID = search.foundID;
  }

  if (dimID == CDI_UNDEFID)
  {
    char dimname[CDI_MAX_NAME];
    int length = sizeof(dimname);
    cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
    if (dimname[0] == 0) strcpy(dimname, "cell");

    int fileID = streamptr->fileID;
    dimID = checkDimName(fileID, (size_t) dimlen, dimname);
    if (dimID == CDI_UNDEFID)
    {
      bool switchNCMode = (streamptr->ncmode == 2);
      if (switchNCMode)
      {
        streamptr->ncmode = 1;
        cdf_redef(fileID);
      }

      cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);

      if (switchNCMode)
      {
        cdf_enddef(fileID, streamptr->self);
        streamptr->ncmode = 2;
      }
    }
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] = dimID;
}

static struct cdfPostDefActionList *
cdf_define_grid(stream_t *streamptr, int gridID, int gridIndex)
{
  struct cdfPostDefActionList *delayed = NULL;

  CdfGrid *ncgrid = &(streamptr->cdfInfo.cdfGridList[gridIndex]);
  if (ncgrid->ncIdList[CDF_DIMID_X] != CDI_UNDEFID) return delayed;

  int gridType = gridInqType(gridID);
  SizeType gridSize = gridInqSize(gridID);

  if (CDI_Debug) Message("gridType = %d  gridSize = %zu", gridType, gridSize);

  if (CDI_Reduce_Dim && gridSize == 1)  // no grid information
  {
    ncgrid->gridID = gridID;
    return delayed;
  }

  if (gridType == GRID_GAUSSIAN || gridType == GRID_LONLAT || gridType == GRID_PROJECTION)
  {
    int ndims = !(gridType == GRID_LONLAT && gridSize == 1 && !gridInqHasDims(gridID));
    SizeType xsize = gridInqXsize(gridID);
    SizeType ysize = gridInqYsize(gridID);

    if (xsize)
    {
      struct cdfPostDefActionList *xdelayed = cdfDefXaxis(streamptr, gridID, gridIndex, ndims, false);
      delayed = cdfPostDefActionConcat(delayed, xdelayed);
      Free(xdelayed);
    }
    if (ysize)
    {
      struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, ndims, false);
      delayed = cdfPostDefActionConcat(delayed, ydelayed);
      Free(ydelayed);
    }

    if (ndims == 1 && xsize == 0 && ysize == 0 && gridType == GRID_PROJECTION) cdf_define_generic_dim(streamptr, gridID, gridIndex);

    struct cdfPostDefActionList *mdelayed = cdf_def_mapping(streamptr, gridID);
    delayed = cdfPostDefActionConcat(delayed, mdelayed);
    Free(mdelayed);
  }
  else if (gridType == GRID_HEALPIX)
  {
    cdf_define_generic_dim(streamptr, gridID, gridIndex);

    {
      struct cdfPostDefActionList *mdelayed = cdf_def_mapping(streamptr, gridID);
      delayed = cdfPostDefActionConcat(delayed, mdelayed);
      Free(mdelayed);
    }
    if (gridSize == gridInqIndices(gridID, NULL))
    {
      struct cdfPostDefActionList *idelayed = cdf_def_grid_indices(streamptr, gridID, gridIndex);
      if (idelayed)
      {
        delayed = cdfPostDefActionConcat(delayed, idelayed);
        Free(idelayed);
      }
    }
  }
  else if (gridType == GRID_GENERIC)
  {
    SizeType xsize = gridInqXsize(gridID);
    SizeType ysize = gridInqYsize(gridID);
    if (gridSize == 1 && xsize == 0 && ysize == 0)
    {
      // no grid information
      ncgrid->gridID = gridID;
    }
    else
    {

      if (xsize > 0)
      {
        struct cdfPostDefActionList *xdelayed = cdfDefXaxis(streamptr, gridID, gridIndex, 1, false);
        delayed = cdfPostDefActionConcat(delayed, xdelayed);
        Free(xdelayed);
      }
      if (ysize > 0)
      {
        struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, 1, false);
        delayed = cdfPostDefActionConcat(delayed, ydelayed);
        Free(ydelayed);
      }

      if (xsize == 0 && ysize == 0) cdf_define_generic_dim(streamptr, gridID, gridIndex);
    }
  }
  else if (gridType == GRID_CURVILINEAR) { delayed = cdfDefCurvilinear(streamptr, gridID, gridIndex); }
  else if (gridType == GRID_UNSTRUCTURED) { delayed = cdfDefUnstructured(streamptr, gridID, gridIndex); }
  else if (gridType == GRID_GAUSSIAN_REDUCED)
  {
    cdfDefReducedGrid(streamptr, gridID, gridIndex);
    if (gridInqYsize(gridID))
    {
      struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, 1, true);
      delayed = cdfPostDefActionConcat(delayed, ydelayed);
      Free(ydelayed);
    }
  }
  else if (gridType == GRID_SPECTRAL)
  {
    cdfDefComplex(streamptr, gridID, gridIndex);
    cdfDefSP(streamptr, gridID, gridIndex);
  }
  else if (gridType == GRID_FOURIER)
  {
    cdfDefComplex(streamptr, gridID, gridIndex);
    cdfDefFC(streamptr, gridID, gridIndex);
  }
  else if (gridType == GRID_TRAJECTORY)
  {
    cdfDefTrajLon(streamptr, gridID, gridIndex);
    cdfDefTrajLat(streamptr, gridID, gridIndex);
  }
#ifndef USE_MPI
  else if (gridType == GRID_CHARXY)
  {
    int strlen = 0;
    if ((strlen = gridInqXIsc(gridID)))
      cdfDefCharacter(streamptr, gridID, gridIndex, CDI_XAXIS, strlen);
    else if (gridInqXsize(gridID))
    {
      struct cdfPostDefActionList *xdelayed = cdfDefXaxis(streamptr, gridID, gridIndex, 1, false);
      delayed = cdfPostDefActionConcat(delayed, xdelayed);
      Free(xdelayed);
    }

    if ((strlen = gridInqYIsc(gridID)))
      cdfDefCharacter(streamptr, gridID, gridIndex, CDI_YAXIS, strlen);
    else if (gridInqYsize(gridID))
    {
      struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, 1, false);
      delayed = cdfPostDefActionConcat(delayed, ydelayed);
      Free(ydelayed);
    }
  }
#endif
  else { Error("Unsupported grid type: %s", gridNamePtr(gridType)); }
  return delayed;
}

void
cdfDefCoordinateVars(stream_t *streamptr)
{
  int vlistID = streamptr->vlistID;
  if (vlistID == CDI_UNDEFID) Error("Internal problem! vlist undefined for streamptr %p", streamptr);

  if (vlistHasTime(vlistID)) cdfDefTime(streamptr);

  int ngrids = vlistNumGrids(vlistID);
  if (2 * ngrids > MAX_GRIDS_PS) Error("Internal problem! Too many grids per stream (max=%d)\n", MAX_GRIDS_PS);

  struct cdfPostDefActionList *delayed = NULL;

  CdfGrid *ncgrid = streamptr->cdfInfo.cdfGridList;
  for (int index = 0; index < 2 * ngrids; ++index)
  {
    ncgrid[index].gridID = CDI_UNDEFID;
    for (size_t i = 0; i < CDF_SIZE_NCID; ++i) ncgrid[index].ncIdList[i] = CDI_UNDEFID;
  }

  for (int index = 0; index < ngrids; ++index)
  {
    int gridID = vlistGrid(vlistID, index);
    struct cdfPostDefActionList *griddelayed = cdf_define_grid(streamptr, gridID, index);
    delayed = cdfPostDefActionConcat(delayed, griddelayed);
    Free(griddelayed);
  }

  for (int i = 0, index = ngrids - 1; i < ngrids; ++i)
  {
    int gridID = vlistGrid(vlistID, i);
    int projID = gridInqProj(gridID);
    if (projID != CDI_UNDEFID)
    {
      struct cdfPostDefActionList *griddelayed = cdf_define_grid(streamptr, projID, ++index);
      delayed = cdfPostDefActionConcat(delayed, griddelayed);
      Free(griddelayed);
    }
  }

  int nzaxis = vlistNumZaxis(vlistID);
  for (int index = 0; index < nzaxis; ++index)
  {
    int zaxisID = vlistZaxis(vlistID, index);
    if (streamptr->cdfInfo.zaxisIdList[index] == CDI_UNDEFID)
    {
      struct cdfPostDefActionList *zaxisdelayed = cdfDefZaxis(streamptr, zaxisID);
      delayed = cdfPostDefActionConcat(delayed, zaxisdelayed);
      Free(zaxisdelayed);
    }
  }

  if (streamptr->ncmode != 2)
  {
    cdf_enddef(streamptr->fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  int nvars = vlistNvars(vlistID);
  for (int varID = 0; varID < nvars; varID++) cdfDefVar(streamptr, varID);

  cdfEndDef(streamptr);
  if (delayed)
  {
    cdfPostDefActionApply(delayed->len, delayed->actions);
    cdfPostDefActionListDelete(delayed);
  }
}

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF

#include <assert.h>
#include <stdio.h>
#include <string.h>


static int
cdfDefTimeBounds(int fileID, int nctimevarid, int nctimedimid, const char *taxis_name, taxis_t *taxis)
{
  int dims[2];

  dims[0] = nctimedimid;

  static const char bndsName[] = "bnds";
  if (nc_inq_dimid(fileID, bndsName, &dims[1]) != NC_NOERR) cdf_def_dim(fileID, bndsName, 2, &dims[1]);

  const char *bndsAttName, *bndsAttVal;
  size_t bndsAttValLen;
  char tmpstr[CDI_MAX_NAME];
  if (taxis->climatology)
  {
    static const char climatology_bndsName[] = "climatology_bnds", climatology_bndsAttName[] = "climatology";
    bndsAttName = climatology_bndsAttName;
    bndsAttValLen = sizeof(climatology_bndsName) - 1;
    bndsAttVal = climatology_bndsName;
  }
  else
  {
    size_t taxisnameLen = strlen(taxis_name);
    memcpy(tmpstr, taxis_name, taxisnameLen);
    tmpstr[taxisnameLen] = '_';
    memcpy(tmpstr + taxisnameLen + 1, bndsName, sizeof(bndsName));
    size_t tmpstrLen = taxisnameLen + sizeof(bndsName);
    static const char generic_bndsAttName[] = "bounds";
    bndsAttName = generic_bndsAttName;
    bndsAttValLen = tmpstrLen;
    bndsAttVal = tmpstr;
  }

  int time_bndsid = -1;
  cdf_def_var(fileID, bndsAttVal, NC_DOUBLE, 2, dims, &time_bndsid);
  cdf_put_att_text(fileID, nctimevarid, bndsAttName, bndsAttValLen, bndsAttVal);

  return time_bndsid;
}

static const char *
cdfGetTimeUnits(taxis_t *taxis)
{
  const char *unitstr;
  if (taxis->units && taxis->units[0]) { unitstr = taxis->units; }
  else
  {
    if (taxis->type == TAXIS_ABSOLUTE)
    {
      static const char *const unitstrfmt[3] = { "year as %Y.%f", "month as %Y%m.%f", "day as %Y%m%d.%f" };
      size_t fmtidx = ((taxis->unit == TUNIT_YEAR) ? 0 : ((taxis->unit == TUNIT_MONTH) ? 1 : 2));
      unitstr = unitstrfmt[fmtidx];
    }
    else
    {
      int year = taxis->rDateTime.date.year;
      int month = taxis->rDateTime.date.month;
      int day = taxis->rDateTime.date.day;
      int hour = taxis->rDateTime.time.hour;
      int minute = taxis->rDateTime.time.minute;
      int second = taxis->rDateTime.time.second;

      int timeunit = (taxis->unit != -1) ? taxis->unit : TUNIT_HOUR;
      if (timeunit == TUNIT_QUARTER)
        timeunit = TUNIT_MINUTE;
      else if (timeunit == TUNIT_30MINUTES)
        timeunit = TUNIT_MINUTE;
      else if (timeunit == TUNIT_3HOURS || timeunit == TUNIT_6HOURS || timeunit == TUNIT_12HOURS)
        timeunit = TUNIT_HOUR;

      char *unitstr_ = ptaxisAllocUnits(taxis, CDF_MAX_TIME_UNIT_STR);
      assert(month >= 1 && month <= 12 && day > 0 && day <= 31 && hour >= 0 && hour <= 24 && minute >= 0 && minute < 60
             && second >= 0 && second <= 60);
      snprintf(unitstr_, CDF_MAX_TIME_UNIT_STR, "%s since %d-%d-%d %02d:%02d:%02d", tunitNamePtr(timeunit), year, month & 31,
               day & 63, hour & 63, minute & 63, second & 63);
      unitstr = unitstr_;
    }
  }
  return unitstr;
}

static const char *
cdfGetForecastTimeUnits(int timeunit)
{
  if (timeunit == -1)
    timeunit = TUNIT_HOUR;
  else if (timeunit == TUNIT_QUARTER)
    timeunit = TUNIT_MINUTE;
  else if (timeunit == TUNIT_30MINUTES)
    timeunit = TUNIT_MINUTE;
  else if (timeunit == TUNIT_3HOURS || timeunit == TUNIT_6HOURS || timeunit == TUNIT_12HOURS)
    timeunit = TUNIT_HOUR;

  return tunitNamePtr(timeunit);
}

static void
cdfDefCalendar(int fileID, int ncvarid, int calendar)
{
  static const struct
  {
    int calCode;
    const char *calStr;
  } calTab[] = {
    { CALENDAR_STANDARD, "standard" }, { CALENDAR_GREGORIAN, "gregorian" }, { CALENDAR_PROLEPTIC, "proleptic_gregorian" },
    { CALENDAR_NONE, "none" },         { CALENDAR_360DAYS, "360_day" },     { CALENDAR_365DAYS, "365_day" },
    { CALENDAR_366DAYS, "366_day" },
  };
  enum
  {
    calTabSize = sizeof calTab / sizeof calTab[0]
  };

  for (size_t i = 0; i < calTabSize; ++i)
    if (calTab[i].calCode == calendar)
    {
      const char *calstr = calTab[i].calStr;
      size_t len = strlen(calstr);
      cdf_put_att_text(fileID, ncvarid, "calendar", len, calstr);
      break;
    }
}

void
cdfDefTime(stream_t *streamptr)
{
  static const char defaultTimeAxisName[] = "time";

  if (streamptr->basetime.ncvarid != CDI_UNDEFID) return;

  int fileID = streamptr->fileID;

  if (streamptr->ncmode == 0) streamptr->ncmode = 1;
  if (streamptr->ncmode == 2) cdf_redef(fileID);

  taxis_t *taxis = taxis_to_pointer(vlistInqTaxis(streamptr->vlistID));

  const char *taxisName = (taxis->name && taxis->name[0]) ? taxis->name : defaultTimeAxisName;

  size_t timeDimLen = NC_UNLIMITED;
  if (streamptr->filetype == CDI_FILETYPE_NCZARR)
  {
    if (streamptr->maxSteps == CDI_UNDEFID)
      fprintf(stderr, "Max. number of timesteps undefined for NCZarr!\n");
    else
      timeDimLen = (size_t) streamptr->maxSteps;
  }

  int timeDimId;
  cdf_def_dim(fileID, taxisName, timeDimLen, &timeDimId);
  streamptr->basetime.ncdimid = timeDimId;

  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(taxis->self, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  nc_type xtype = (datatype == CDI_DATATYPE_INT32) ? NC_INT : ((datatype == CDI_DATATYPE_FLT32) ? NC_FLOAT : NC_DOUBLE);

  int timeVarId;
  cdf_def_var(fileID, taxisName, xtype, 1, &timeDimId, &timeVarId);
  streamptr->basetime.ncvarid = timeVarId;

  if (timeDimLen == NC_UNLIMITED && (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C))
  {
    static const size_t chunk = 512;
    cdf_def_var_chunking(fileID, timeVarId, NC_CHUNKED, &chunk);
  }

  static const char timeStr[] = "time";
  cdf_put_att_text(fileID, timeVarId, "standard_name", sizeof(timeStr) - 1, timeStr);

  if (taxis->longname && taxis->longname[0])
    cdf_put_att_text(fileID, timeVarId, "long_name", strlen(taxis->longname), taxis->longname);

  if (taxis->hasBounds) streamptr->basetime.ncvarboundsid = cdfDefTimeBounds(fileID, timeVarId, timeDimId, taxisName, taxis);

  char unitsStr_[CDF_MAX_TIME_UNIT_STR];
  const char *unitsStr;
  size_t unitsStrLen;
  if (taxis->units && taxis->units[0])
  {
    unitsStr = taxis->units;
    unitsStrLen = strlen(taxis->units);
  }
  else
  {
    /* define bogus value since at this time, streamDefTimestep has not been called yet
     * but since taxis->units is not set, it clearly will not exceed the size of unitstr_,
     * i.e. when defining the units attribute to this value, a later redefinition will not
     * cause a recreation of on-disk data
     */
    for (size_t i = 0; i < CDF_MAX_TIME_UNIT_STR; ++i) unitsStr_[i] = 'a';
    unitsStr_[CDF_MAX_TIME_UNIT_STR - 1] = '\0';
    unitsStr = unitsStr_;
    unitsStrLen = CDF_MAX_TIME_UNIT_STR - 1;
  }
  cdf_put_att_text(fileID, timeVarId, "units", unitsStrLen, unitsStr);

  if (taxis->calendar != -1) cdfDefCalendar(fileID, timeVarId, taxis->calendar);

  if (taxis->type == TAXIS_FORECAST)
  {
    int leadtimeid;
    cdf_def_var(fileID, "leadtime", xtype, 1, &timeDimId, &leadtimeid);
    streamptr->basetime.leadtimeid = leadtimeid;

    static const char stdName[] = "forecast_period";
    cdf_put_att_text(fileID, leadtimeid, "standard_name", sizeof(stdName) - 1, stdName);

    static const char longName[] = "Time elapsed since the start of the forecast";
    cdf_put_att_text(fileID, leadtimeid, "long_name", sizeof(longName) - 1, longName);

    unitsStr = cdfGetForecastTimeUnits(taxis->fc_unit);
    size_t len = strlen(unitsStr);
    if (len) cdf_put_att_text(fileID, leadtimeid, "units", len, unitsStr);
  }

  cdf_put_att_text(fileID, timeVarId, "axis", 1, "T");

  if (streamptr->ncmode == 2) cdf_enddef(fileID, streamptr->self);
}

void
cdfDefTimestep(stream_t *streamptr, int tsID, size_t valCount)
{
  int time_varid = streamptr->basetime.ncvarid;
  if (time_varid != CDI_UNDEFID && tsID == 0)
  {
    taxis_t *taxis = taxis_to_pointer(vlistInqTaxis(streamptr->vlistID));
    int fileID = streamptr->fileID;
    const char *unitstr = cdfGetTimeUnits(taxis);
    size_t len = strlen(unitstr);
    if (len) cdf_put_att_text(fileID, time_varid, "units", len, unitstr);
  }

  int fileID = streamptr->fileID;

  if (CDI_Debug) Message("streamID = %d, fileID = %d, tsID = %d", streamptr->self, fileID, tsID);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if (streamptr->ncmode == 1)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  if (streamptr->accessmode == 0) { cdfEndDef(streamptr); }

  const size_t start[2] = { [0] = (size_t) tsID, [1] = 0 }, count[2] = { [0] = valCount, [1] = 2 * valCount };

  double timeValue[2] = { cdi_encode_timeval(taxis->vDateTime, &streamptr->tsteps[0].taxis) };
  if (CDI_Debug) Message("tsID = %d  timeValue = %f", tsID, timeValue[0]);

  int ncvarid = streamptr->basetime.ncvarid;
  cdf_put_vara_double(fileID, ncvarid, start, count, timeValue);

  if (taxis->hasBounds)
  {
    ncvarid = streamptr->basetime.ncvarboundsid;
    if (ncvarid == CDI_UNDEFID) Error("Call to taxisWithBounds() missing!");

    timeValue[0] = cdi_encode_timeval(taxis->vDateTime_lb, &streamptr->tsteps[0].taxis);
    timeValue[1] = cdi_encode_timeval(taxis->vDateTime_ub, &streamptr->tsteps[0].taxis);
    cdf_put_vara_double(fileID, ncvarid, start, count, timeValue);
  }

  ncvarid = streamptr->basetime.leadtimeid;
  if (taxis->type == TAXIS_FORECAST && ncvarid != CDI_UNDEFID)
    cdf_put_vara_double(fileID, ncvarid, start, count, &taxis->fc_period);
}

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#if HAVE_CONFIG_H
#endif

#include <limits.h>
#include <stdio.h>


#if defined(HAVE_LIBCGRIBEX) && defined(HAVE_LIBGRIB)


typedef struct
{
  int sec0[2];
  int sec1[1024];
  size_t sec2len;
  int *sec2;
  int sec3[2];
  int sec4[512];
  double fsec2[512];
  double fsec3[2];
} cgribexrec_t;

typedef struct
{
  size_t gridsize;
  int param;
  int level1;
  int level2;
  short ltype;
  short tsteptype;
} compvar_t;

typedef struct
{
  void *gribbuffer;
  size_t gribbuffersize;
  unsigned char *pds;
  unsigned char *gds;
  unsigned char *bms;
  unsigned char *bds;
} cgribex_handle;

static void
fill_intarr(int *iarr, int val, int n)
{
  for (int i = 0; i < n; ++i) iarr[i] = val;
}

static void
cgribexInit(cgribexrec_t *cgribexp)
{
  cgribexp->sec2len = 4096;
  cgribexp->sec2 = (int *) Malloc(cgribexp->sec2len * sizeof(int));
}

void *
cgribexNew(void)
{
  cgribexrec_t *cgribexp = (cgribexrec_t *) Malloc(sizeof(cgribexrec_t));
  cgribexInit(cgribexp);
  return (void *) cgribexp;
}

void
cgribexDelete(void *cgribex)
{
  cgribexrec_t *cgribexp = (cgribexrec_t *) cgribex;
  if (cgribexp)
  {
    if (cgribexp->sec2) Free(cgribexp->sec2);
    Free(cgribexp);
  }
}

static size_t
cgribexSection2Length(void *gribbuffer, size_t gribbuffersize)
{
  size_t sec2len = 0;

  if (gribbuffersize && gribbuffer)
  {
    unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
    long gribrecsize;
    int status = grib1Sections((unsigned char *) gribbuffer, (long) gribbuffersize, &pds, &gds, &bms, &bds, &gribrecsize);
    if (status >= 0 && gds != NULL) sec2len = (size_t) ((gds[0] << 16) + (gds[1] << 8) + (gds[3]));
  }

  return sec2len;
}

void *
cgribex_handle_new_from_meassage(void *gribbuffer, size_t gribbuffersize)
{
  cgribex_handle *gh = (cgribex_handle *) Malloc(sizeof(cgribex_handle));
  gh->gribbuffer = NULL;
  gh->gribbuffersize = 0;
  gh->pds = NULL;

  if (gribbuffersize && gribbuffer)
  {
    unsigned char *pds = NULL, *gds = NULL, *bms = NULL, *bds = NULL;
    long gribrecsize;
    int status = grib1Sections((unsigned char *) gribbuffer, (long) gribbuffersize, &pds, &gds, &bms, &bds, &gribrecsize);
    if (status >= 0)
    {
      gh->gribbuffer = gribbuffer;
      gh->gribbuffersize = gribbuffersize;
      gh->pds = pds;
      gh->gds = gds;
      gh->bms = bms;
      gh->bds = bds;
    }
  }

  return (void *) gh;
}

void
cgribex_handle_delete(void *gh)
{
  if (gh) Free(gh);
}

static int
cgribexGetGridType(int *isec2)
{
  int gridtype = GRID_GENERIC;

  // clang-format off
  switch (ISEC2_GridType)
    {
    case  GRIB1_GTYPE_LATLON:     { gridtype = GRID_LONLAT;     break; }
    case  GRIB1_GTYPE_LATLON_ROT: { gridtype = GRID_PROJECTION; break; }
    case  GRIB1_GTYPE_LCC:        { gridtype = CDI_PROJ_LCC;    break; }
    case  GRIB1_GTYPE_GAUSSIAN:   { gridtype = ISEC2_Reduced ? GRID_GAUSSIAN_REDUCED : GRID_GAUSSIAN; break; }
    case  GRIB1_GTYPE_SPECTRAL:   { gridtype = GRID_SPECTRAL;   break; }
    case  GRIB1_GTYPE_GME:        { gridtype = GRID_GME;        break; }
    }
  // clang-format on

  return gridtype;
}

static bool
cgribexGetIsRotated(int *isec2)
{
  return (ISEC2_GridType == GRIB1_GTYPE_LATLON_ROT);
}

static bool
cgribexGetZaxisHasBounds(int grb_ltype)
{
  // clang-format off
  switch (grb_ltype)
    {
    case GRIB1_LTYPE_SIGMA_LAYER:
    case GRIB1_LTYPE_HYBRID_LAYER:
    case GRIB1_LTYPE_LANDDEPTH_LAYER: return true;
    }
  // clang-format on

  return false;
}

static int
cgribexGetTimeUnit(int *isec1)
{
  int timeunit = TUNIT_HOUR;
  static bool lprint = true;

  // clang-format off
  switch ( ISEC1_TimeUnit )
    {
    case ISEC1_TABLE4_MINUTE:    timeunit = TUNIT_MINUTE;    break;
    case ISEC1_TABLE4_QUARTER:   timeunit = TUNIT_QUARTER;   break;
    case ISEC1_TABLE4_30MINUTES: timeunit = TUNIT_30MINUTES; break;
    case ISEC1_TABLE4_HOUR:      timeunit = TUNIT_HOUR;      break;
    case ISEC1_TABLE4_3HOURS:    timeunit = TUNIT_3HOURS;    break;
    case ISEC1_TABLE4_6HOURS:    timeunit = TUNIT_6HOURS;    break;
    case ISEC1_TABLE4_12HOURS:   timeunit = TUNIT_12HOURS;   break;
    case ISEC1_TABLE4_DAY:       timeunit = TUNIT_DAY;       break;
    default:
      if (lprint)
	    {
	      Warning("GRIB time unit %d unsupported!", ISEC1_TimeUnit);
	      lprint = false;
	    }
      break;
    }
  // clang-format on

  return timeunit;
}

static bool
cgribexTimeIsFC(int *isec1)
{
  bool isFC = (ISEC1_TimeRange == 10 && ISEC1_TimePeriod1 == 0 && ISEC1_TimePeriod2 == 0) ? false : true;
  return isFC;
}

static int
cgribexGetTsteptype(int timerange)
{
  static bool lprint = true;

  // clang-format off
  int tsteptype = TSTEP_INSTANT;
  switch ( timerange )
    {
    case  0:  tsteptype = TSTEP_INSTANT;  break;
    case  1:  tsteptype = TSTEP_INSTANT2; break;
    case  2:  tsteptype = TSTEP_RANGE;    break;
    case  3:  tsteptype = TSTEP_AVG;      break;
    case  4:  tsteptype = TSTEP_ACCUM;    break;
    case  5:  tsteptype = TSTEP_DIFF;     break;
    case 10:  tsteptype = TSTEP_INSTANT3; break;
    default:
      if (lprint)
    	{
	      Warning("Time range indicator %d unsupported, set to 0!", timerange);
	      lprint = false;
	    }
      break;
    }
  // clang-format on

  return tsteptype;
}

static bool
cgribexGetGridRegular(int *isec2, int *isec4, grid_t *grid, int gridtype, bool compyinc)
{
  bool ijDirectionIncrementGiven = gribbyte_get_bit(ISEC2_ResFlag, 1);
  bool uvRelativeToGrid = gribbyte_get_bit(ISEC2_ResFlag, 5);

  size_t nvalues = (size_t) ISEC4_NumValues;
  size_t nlon = (size_t) ISEC2_NumLon;
  size_t nlat = (size_t) ISEC2_NumLat;
  if (nvalues != nlon * nlat) Error("numberOfPoints (%zu) and gridSize (%zu) differ!", nvalues, nlon * nlat);

  grid->size = nvalues;
  grid->x.size = nlon;
  grid->y.size = nlat;

  if (gridtype == GRID_GAUSSIAN) grid->np = ISEC2_NumPar;
  grid->x.inc = 0;
  grid->y.inc = 0;
  grid->x.flag = 0;
  // if ( ISEC2_FirstLon != 0 || ISEC2_LastLon != 0 )
  {
    if (grid->x.size > 1)
    {
      bool recompinc = true;

      if (ISEC2_LastLon < ISEC2_FirstLon)
      {
        if (ISEC2_FirstLon >= 180000)
          ISEC2_FirstLon -= 360000;
        else
          ISEC2_LastLon += 360000;
      }

      if (ijDirectionIncrementGiven && ISEC2_LonIncr > 0)
      {
        if (labs(ISEC2_LastLon - (ISEC2_FirstLon + ISEC2_LonIncr * ((long) grid->x.size - 1))) <= 2)
        {
          recompinc = false;
          grid->x.inc = ISEC2_LonIncr * 0.001;
        }
      }

      // recompute xinc if necessary
      if (recompinc) grid->x.inc = (ISEC2_LastLon - ISEC2_FirstLon) * 0.001 / ((double) grid->x.size - 1);

      // correct xinc if necessary
      if (ISEC2_FirstLon == 0 && ISEC2_LastLon > 354000 && ISEC2_LastLon < 360000)
      {
        double xinc = 360. / (double) grid->x.size;
        if (fabs(grid->x.inc - xinc) > 0.0)
        {
          grid->x.inc = xinc;
          if (CDI_Debug) Message("set xinc to %g", grid->x.inc);
        }
      }
    }
    grid->x.first = ISEC2_FirstLon * 0.001;
    grid->x.last = ISEC2_LastLon * 0.001;
    grid->x.flag = 2;
  }
  grid->y.flag = 0;
  // if ( ISEC2_FirstLat != 0 || ISEC2_LastLat != 0 )
  {
    if (grid->y.size > 1 && compyinc)
    {
      bool recompinc = true;
      if (ijDirectionIncrementGiven && ISEC2_LatIncr > 0)
      {
        if (labs(ISEC2_LastLat - (ISEC2_FirstLat + ISEC2_LatIncr * ((long) grid->y.size - 1))) <= 2)
        {
          recompinc = false;
          grid->y.inc = ISEC2_LatIncr * 0.001;
        }
      }

      // recompute yinc if necessary
      if (recompinc) grid->y.inc = (ISEC2_LastLat - ISEC2_FirstLat) * 0.001 / ((double) grid->y.size - 1);
    }
    grid->y.first = ISEC2_FirstLat * 0.001;
    grid->y.last = ISEC2_LastLat * 0.001;
    grid->y.flag = 2;
  }

  return uvRelativeToGrid;
}

static bool
cgribexGetGridReduced(int *isec2, int *isec4, grid_t *grid)
{
  bool ijDirectionIncrementGiven = gribbyte_get_bit(ISEC2_ResFlag, 1);
  bool uvRelativeToGrid = gribbyte_get_bit(ISEC2_ResFlag, 5);
  grid->np = ISEC2_NumPar;
  grid->size = (size_t) ISEC4_NumValues;

  size_t reducedPointsSize = (size_t) ISEC2_NumLat;
  grid->reducedPointsSize = ISEC2_NumLat;
  grid->reducedPoints = (int *) Malloc(reducedPointsSize * sizeof(int));
  memcpy(grid->reducedPoints, ISEC2_ReducedPointsPtr, reducedPointsSize * sizeof(int));

  grid->y.size = (size_t) ISEC2_NumLat;
  grid->x.inc = 0;
  grid->y.inc = 0;
  grid->x.flag = 0;
  // if ( ISEC2_FirstLon != 0 || ISEC2_LastLon != 0 )
  {
    if (ISEC2_LastLon < ISEC2_FirstLon)
    {
      if (ISEC2_FirstLon >= 180000)
        ISEC2_FirstLon -= 360000;
      else
        ISEC2_LastLon += 360000;
    }

    grid->x.first = ISEC2_FirstLon * 0.001;
    grid->x.last = ISEC2_LastLon * 0.001;
    grid->x.flag = 2;
  }
  grid->y.flag = 0;
  // if ( ISEC2_FirstLat != 0 || ISEC2_LastLat != 0 )
  {
    if (grid->y.size > 1)
    {
      if (ijDirectionIncrementGiven && ISEC2_LatIncr > 0)
        grid->y.inc = ISEC2_LatIncr * 0.001;
      else
        grid->y.inc = (ISEC2_LastLat - ISEC2_FirstLat) * 0.001 / ((double) grid->y.size - 1);
    }
    grid->y.first = ISEC2_FirstLat * 0.001;
    grid->y.last = ISEC2_LastLat * 0.001;
    grid->y.flag = 2;
  }

  return uvRelativeToGrid;
}

static bool
cgribexGetGridLCC(int *isec2, int *isec4, grid_t *grid)
{
  bool uvRelativeToGrid = gribbyte_get_bit(ISEC2_ResFlag, 5);

  size_t nvalues = (size_t) ISEC4_NumValues;
  size_t nlon = (size_t) ISEC2_NumLon;
  size_t nlat = (size_t) ISEC2_NumLat;
  if (nvalues != nlon * nlat) Error("numberOfPoints (%zu) and gridSize (%zu) differ!", nvalues, nlon * nlat);

  grid->size = nvalues;
  grid->x.size = nlon;
  grid->y.size = nlat;

  grid->x.first = 0;
  grid->x.last = 0;
  grid->x.inc = ISEC2_Lambert_dx;
  grid->y.first = 0;
  grid->y.last = 0;
  grid->y.inc = ISEC2_Lambert_dy;
  grid->x.flag = 2;
  grid->y.flag = 2;

  return uvRelativeToGrid;
}

static bool
cgribexGetGrid(stream_t *streamptr, int *isec2, int *isec4, grid_t *grid, int iret)
{
  bool uvRelativeToGrid = false;
  bool compyinc = true;
  int gridtype = cgribexGetGridType(isec2);
  int projtype = (gridtype == GRID_PROJECTION && cgribexGetIsRotated(isec2)) ? CDI_PROJ_RLL : CDI_UNDEFID;
  if (gridtype == CDI_PROJ_LCC)
  {
    projtype = gridtype;
    gridtype = GRID_PROJECTION;
  }

  if (streamptr->unreduced && gridtype == GRID_GAUSSIAN_REDUCED && iret != -801)
  {
    int nlon = 0;
    for (int ilat = 0; ilat < ISEC2_NumLat; ++ilat)
      if (ISEC2_ReducedPoints(ilat) > nlon) nlon = ISEC2_ReducedPoints(ilat);
    gridtype = GRID_GAUSSIAN;
    ISEC2_NumLon = nlon;
    ISEC4_NumValues = nlon * ISEC2_NumLat;
    compyinc = false;
  }

  grid_init(grid);
  cdiGridTypeInit(grid, gridtype, 0);

  if (gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || projtype == CDI_PROJ_RLL)
  {
    uvRelativeToGrid = cgribexGetGridRegular(isec2, isec4, grid, gridtype, compyinc);
  }
  else if (gridtype == GRID_GAUSSIAN_REDUCED) { uvRelativeToGrid = cgribexGetGridReduced(isec2, isec4, grid); }
  else if (projtype == CDI_PROJ_LCC) { uvRelativeToGrid = cgribexGetGridLCC(isec2, isec4, grid); }
  else if (gridtype == GRID_SPECTRAL)
  {
    grid->size = (size_t) ISEC4_NumValues;
    grid->trunc = ISEC2_PentaJ;
    grid->lcomplex = (ISEC2_RepMode == 2) ? 1 : 0;
  }
  else if (gridtype == GRID_GME)
  {
    grid->size = (size_t) ISEC4_NumValues;
    grid->gme.nd = ISEC2_GME_ND;
    grid->gme.ni = ISEC2_GME_NI;
    grid->gme.ni2 = ISEC2_GME_NI2;
    grid->gme.ni3 = ISEC2_GME_NI3;
  }
  else if (gridtype == GRID_GENERIC)
  {
    grid->size = (size_t) ISEC4_NumValues;
    grid->x.size = 0;
    grid->y.size = 0;
  }
  else { Error("Unsupported grid type: %s", gridNamePtr(gridtype)); }

  grid->type = gridtype;
  grid->projtype = projtype;

  return uvRelativeToGrid;
}

static void
cgribexGetLevel(int *isec1, int *leveltype, int *level1, int *level2)
{
  *leveltype = ISEC1_LevelType;
  *level1 = ISEC1_Level1;
  *level2 = ISEC1_Level2;
  if (*leveltype == GRIB1_LTYPE_ISOBARIC)
    *level1 *= 100;
  else if (*leveltype == GRIB1_LTYPE_99 || *leveltype == GRIB1_LTYPE_ISOBARIC_PA)
    *leveltype = GRIB1_LTYPE_ISOBARIC;
}

static void
cgribexDefProjLCC(int *isec2, int gridID)
{
  struct CDI_GridProjParams gpp;
  gridProjParamsInit(&gpp);

  bool earthIsOblate = gribbyte_get_bit(ISEC2_ResFlag, 2);
  if (earthIsOblate)
  {
    gpp.a = 6378160.0;
    gpp.b = 6356775.0;
    gpp.rf = 297.0;
  }
  else { gpp.a = 6367470.0; }

  gpp.xval_0 = ISEC2_FirstLon * 0.001;
  gpp.yval_0 = ISEC2_FirstLat * 0.001;
  gpp.lon_0 = ISEC2_Lambert_Lov * 0.001;
  gpp.lat_1 = ISEC2_Lambert_LatS1 * 0.001;
  gpp.lat_2 = ISEC2_Lambert_LatS2 * 0.001;
  bool lsouth = gribbyte_get_bit(ISEC2_Lambert_ProjFlag, 1);
  if (lsouth)
  {
    gpp.lat_1 = -gpp.lat_1;
    gpp.lat_2 = -gpp.lat_2;
  }

  gpp.lat_0 = gpp.lat_2;

  if (proj_lonlat_to_lcc_func)
  {
    double x_0 = gpp.xval_0, y_0 = gpp.yval_0;
    proj_lonlat_to_lcc_func(gpp, (size_t) 1, &x_0, &y_0);
    if (IS_NOT_EQUAL(x_0, gpp.mv) && IS_NOT_EQUAL(y_0, gpp.mv))
    {
      gpp.x_0 = -x_0;
      gpp.y_0 = -y_0;
    }
  }

  gridDefParamsLCC(gridID, gpp);
}

static size_t
cgribexGetGridsize(const int *isec4)
{
  return (size_t) ISEC4_NumValues;
}

static void
cgribexAddRecord(stream_t *streamptr, cgribexrec_t *cgribexp, int param, size_t recsize, off_t position, int comptype, int lmv,
                 int iret)
{
  int *isec1 = cgribexp->sec1;
  int *isec2 = cgribexp->sec2;
  int *isec4 = cgribexp->sec4;
  double *fsec2 = cgribexp->fsec2;
  double *fsec3 = cgribexp->fsec3;

  int datatype = (ISEC4_NumBits > 0 && ISEC4_NumBits <= 32) ? ISEC4_NumBits : CDI_DATATYPE_PACK;

  int vlistID = streamptr->vlistID;
  int tsID = streamptr->curTsID;
  int recID = recordNewEntry(streamptr, tsID);
  recinfo_t *recinfo = &(streamptr->tsteps[tsID].recinfo[recID]);
  record_t *record = &(streamptr->tsteps[tsID].records[recID]);

  int tsteptype = cgribexGetTsteptype(ISEC1_TimeRange);

  int leveltype, level1, level2;
  cgribexGetLevel(isec1, &leveltype, &level1, &level2);

  // fprintf(stderr, "param %d %d %d %d\n", param, level1, level2, leveltype);

  record->size = recsize;
  record->position = position;
  record->param = param;
  record->ilevel = level1;
  record->ilevel2 = level2;
  record->ltype = (short) leveltype;
  record->tsteptype = (short) tsteptype;
  record->gridsize = cgribexGetGridsize(cgribexp->sec4);

  grid_t *gridptr = (grid_t *) Malloc(sizeof(*gridptr));
  bool uvRelativeToGrid = cgribexGetGrid(streamptr, isec2, isec4, gridptr, iret);

  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, gridptr, 0);
  int gridID = gridAdded.Id;
  if (!gridAdded.isNew)
  {
    grid_free(gridptr);
    Free(gridptr);
  }
  else if (gridptr->projtype == CDI_PROJ_RLL)
  {
    double xpole = ISEC2_LonSP * 0.001 - 180;
    double ypole = -ISEC2_LatSP * 0.001;
    double angle = -FSEC2_RotAngle;
    gridDefParamRLL(gridID, xpole, ypole, angle);
  }
  else if (gridptr->projtype == CDI_PROJ_LCC) { cgribexDefProjLCC(isec2, gridID); }

  int zaxistype = grib1ltypeToZaxisType(leveltype);
  if (zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_HYBRID_HALF)
  {
    size_t vctsize = (size_t) ISEC2_NumVCP;
    double *vctptr = &fsec2[10];
    varDefVCT(vctsize, vctptr);
  }

  bool lbounds = cgribexGetZaxisHasBounds(leveltype);

  int varID = 0, levelID = 0;
  varAddRecord(recID, param, gridID, zaxistype, lbounds, level1, level2, 0, 0, datatype, &varID, &levelID, tsteptype, leveltype, -1,
               NULL, NULL, NULL, NULL);

  recinfo->varID = (short) varID;
  recinfo->levelID = levelID;

  varDefCompType(varID, comptype);

  if (uvRelativeToGrid) varDefKeyInt(varID, CDI_KEY_UVRELATIVETOGRID, 1);

  if (ISEC1_LocalFLag)
  {
    if (ISEC1_CenterID == 78 && isec1[36] == 253)  // DWD local extension
    {
      varDefKeyInt(varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, isec1[52]);
      varDefKeyInt(varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, isec1[53]);
      varDefKeyInt(varID, CDI_KEY_PERTURBATIONNUMBER, isec1[54]);
    }
    else if (ISEC1_CenterID == 252 && isec1[36] == 1)  // MPIM local extension
    {
      varDefKeyInt(varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, isec1[37]);
      varDefKeyInt(varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, isec1[39]);
      varDefKeyInt(varID, CDI_KEY_PERTURBATIONNUMBER, isec1[38]);
    }
  }

  if (lmv) varDefMissval(varID, FSEC3_MissVal);

  if (varInqInst(varID) == CDI_UNDEFID)
  {
    int center = ISEC1_CenterID;
    int subcenter = ISEC1_SubCenterID;
    int instID = institutInq(center, subcenter, NULL, NULL);
    if (instID == CDI_UNDEFID) instID = institutDef(center, subcenter, NULL, NULL);
    varDefInst(varID, instID);
  }

  if (varInqModel(varID) == CDI_UNDEFID)
  {
    int modelID = modelInq(varInqInst(varID), ISEC1_ModelID, NULL);
    if (modelID == CDI_UNDEFID) modelID = modelDef(varInqInst(varID), ISEC1_ModelID, NULL);
    varDefModel(varID, modelID);
  }

  if (varInqTable(varID) == CDI_UNDEFID)
  {
    int tableID = tableInq(varInqModel(varID), ISEC1_CodeTable, NULL);
    if (tableID == CDI_UNDEFID) tableID = tableDef(varInqModel(varID), ISEC1_CodeTable, NULL);
    varDefTable(varID, tableID);
  }

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;
}

static void
MCH_get_undef(int *isec1, double *undef_pds, double *undef_eps)
{
  /* 2010-01-13: Oliver Fuhrer */
  if (ISEC1_CenterID == 215)
  {
    if (isec1[34] != 0 && isec1[34] != 255)
    {
      if (isec1[34] & 2)
      {
        *undef_pds = ((isec1[34] & 1) ? -0.99 : +0.99) * pow(10.0, -isec1[35]);
        *undef_eps = pow(10.0, -isec1[35] - 1);
      }
      else
      {
        *undef_pds = ((isec1[34] & 1) ? -0.99 : +0.99) * pow(10.0, +isec1[35]);
        *undef_eps = pow(10.0, isec1[35] - 1);
      }
    }
  }
}

static void
cgribexDecodeHeader(cgribexrec_t *cgribexp, int *gribbuffer, int recsize, int *lmv, int *iret)
{
  int *isec0 = cgribexp->sec0;
  int *isec1 = cgribexp->sec1;
  int *isec2 = cgribexp->sec2;
  int *isec3 = cgribexp->sec3;
  int *isec4 = cgribexp->sec4;
  double *fsec2 = cgribexp->fsec2;
  double *fsec3 = cgribexp->fsec3;

  int ipunp = 0, iword = 0;

  fill_intarr(isec1, 0, 256);
  fill_intarr(isec2, 0, 32);

  double *fsec4 = NULL;
  gribExDP(isec0, isec1, isec2, fsec2, isec3, fsec3, isec4, fsec4, ipunp, (int *) gribbuffer, recsize, &iword, "J", iret);

  if (!(ISEC1_Sec2Or3Flag & 128)) isec2[0] = -1;  // default generic grid

  *lmv = 0;

  if (ISEC1_CenterID == 215 && (isec1[34] != 0 && isec1[34] != 255))
  {
    double undef_pds, undef_eps;
    MCH_get_undef(isec1, &undef_pds, &undef_eps);
    FSEC3_MissVal = undef_pds;
    *lmv = 1;
  }
}

static compvar_t
cgribexVarSet(int param, int level1, int level2, int leveltype, int trange, size_t gridsize)
{
  int tsteptype = cgribexGetTsteptype(trange);

  compvar_t compVar;
  compVar.param = param;
  compVar.level1 = level1;
  compVar.level2 = level2;
  compVar.ltype = (short) leveltype;
  compVar.tsteptype = (short) tsteptype;
  compVar.gridsize = gridsize;

  return compVar;
}

static inline int
cgribexVarCompare(const compvar_t *compVar, const record_t *record, int flag)
{
  bool vinst
      = (compVar->tsteptype == TSTEP_INSTANT || compVar->tsteptype == TSTEP_INSTANT2 || compVar->tsteptype == TSTEP_INSTANT3);
  bool rinst = (record->tsteptype == TSTEP_INSTANT || record->tsteptype == TSTEP_INSTANT2 || record->tsteptype == TSTEP_INSTANT3);
  int tstepDiff = (!((flag == 0) & (vinst && rinst))) & (compVar->tsteptype != record->tsteptype);
  int rstatus = (compVar->param != record->param) | (compVar->level1 != record->ilevel) | (compVar->level2 != record->ilevel2)
                | (compVar->ltype != record->ltype) | (compVar->gridsize != record->gridsize) | tstepDiff;
  return rstatus;
}

#define gribWarning(text, nrecs, timestep, paramstr, level1, level2) \
  Warning("Record %2d (id=%s lev1=%d lev2=%d) timestep %d: %s", nrecs, paramstr, level1, level2, timestep, text)

static void
cgribexSkipRecords(int fileID)
{
  int nskip = CDI_Skip_Records;
  while (nskip-- > 0)
  {
    size_t recsize = gribGetSize(fileID);
    if (recsize == 0) Error("Skipping of %d records failed!", CDI_Skip_Records);

    grib_check_recsize(0, recsize);

    off_t recpos = fileGetPos(fileID);
    fileSetPos(fileID, recpos, SEEK_CUR);
  }
}

static CdiDateTime
cgribexDateTimeX(int *isec1, CdiDateTime *sDateTime)
{
  int vdate = 0, sdate = 0, vtime = 0, stime = 0;
  gribDateTimeX(isec1, &vdate, &vtime, &sdate, &stime);

  sDateTime->date = cdiDate_set(sdate);
  sDateTime->time = cdiTime_set(stime);

  return cdiDateTime_set(vdate, vtime);
}

int
cgribexScanTimestep1(stream_t *streamptr)
{
  CdiDateTime vDateTime0;
  cdiDateTime_init(&vDateTime0);
  int lmv = 0, iret = 0;
  off_t recpos = 0;
  void *gribbuffer = NULL;
  size_t buffersize = 0;
  int leveltype = 0, level1 = 0, level2 = 0;
  unsigned recID;
  int nrecsScanned = 0;
  bool warn_time = true;
  bool warn_numavg = true;
  bool fcast = false;
  char paramstr[32];

  streamptr->curTsID = 0;

  cgribexrec_t *cgribexp = (cgribexrec_t *) streamptr->record->objectp;

  int tsID = tstepsNewEntry(streamptr);
  if (tsID != 0) Error("Internal problem! tstepsNewEntry returns %d", tsID);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  int fileID = streamptr->fileID;

  if (CDI_Skip_Records) cgribexSkipRecords(fileID);

  unsigned nrecs = 0;
  while (true)
  {
    size_t recsize = gribGetSize(fileID);
    recpos = fileGetPos(fileID);

    if (recsize == 0)
    {
      if (nrecs == 0) Error("No GRIB records found!");
      streamptr->ntsteps = 1;
      break;
    }

    grib_check_recsize(1, recsize);
    ensureBufferSize(recsize, &buffersize, &gribbuffer);

    size_t readsize = recsize;
    // Search for next 'GRIB', read the following record, and position file offset after it.
    if (gribRead(fileID, gribbuffer, &readsize)) break;

    int comptype = grbDecompress(recsize, &buffersize, &gribbuffer);

    size_t sec2len = cgribexSection2Length(gribbuffer, buffersize);
    if (sec2len > cgribexp->sec2len)
    {
      cgribexp->sec2len = sec2len;
      cgribexp->sec2 = (int *) Realloc(cgribexp->sec2, sec2len * sizeof(int));
    }

    int *isec1 = cgribexp->sec1;

    nrecsScanned++;
    cgribexDecodeHeader(cgribexp, (int *) gribbuffer, (int) recsize, &lmv, &iret);

    int param = cdiEncodeParam(ISEC1_Parameter, ISEC1_CodeTable, 255);
    cdiParamToString(param, paramstr, sizeof(paramstr));

    cgribexGetLevel(isec1, &leveltype, &level1, &level2);

    CdiDateTime sDateTime;
    CdiDateTime vDateTime = cgribexDateTimeX(isec1, &sDateTime);

    if (nrecs == 0)
    {
      vDateTime0 = vDateTime;
      fcast = cgribexTimeIsFC(isec1);
      taxis->unit = cgribexGetTimeUnit(isec1);
      taxis->rDateTime = cdiDateTime_set(gribRefDate(isec1), gribRefTime(isec1));
      taxis->sDateTime = sDateTime;
      taxis->vDateTime = vDateTime;
    }
    else
    {
      if (cdiDateTime_isLT(sDateTime, taxis->sDateTime)) taxis->sDateTime = sDateTime;

      size_t gridsize = cgribexGetGridsize(cgribexp->sec4);
      compvar_t compVar = cgribexVarSet(param, level1, level2, leveltype, ISEC1_TimeRange, gridsize);
      record_t *records = streamptr->tsteps[tsID].records;
      for (recID = 0; recID < nrecs; recID++)
      {
        if (cgribexVarCompare(&compVar, &records[recID], 0) == 0) break;
      }

      if (CDI_Inventory_Mode == 1)
      {
        if (recID < nrecs) break;
        if (warn_time)
          if (cdiDateTime_isNE(vDateTime, vDateTime0))
          {
            gribWarning("Inconsistent verification time!", nrecsScanned, tsID + 1, paramstr, level1, level2);
            warn_time = false;
          }
      }
      else
      {
        if (cdiDateTime_isNE(vDateTime, vDateTime0)) break;

        if (recID < nrecs)
        {
          gribWarning("Parameter already exist, skipped!", nrecsScanned, tsID + 1, paramstr, level1, level2);
          continue;
        }
      }
    }

    if (ISEC1_AvgNum)
    {
      if (taxis->numavg && warn_numavg && (taxis->numavg != ISEC1_AvgNum))
      {
        Warning("Changing numavg from %d to %d not supported!", taxis->numavg, ISEC1_AvgNum);
        warn_numavg = false;
      }
      else { taxis->numavg = ISEC1_AvgNum; }
    }

    nrecs++;

    if (CDI_Debug)
      Message("Read record %2d (id=%s lev1=%d lev2=%d) %s", nrecsScanned, paramstr, level1, level2, CdiDateTime_string(vDateTime));

    cgribexAddRecord(streamptr, cgribexp, param, recsize, recpos, comptype, lmv, iret);
  }

  streamptr->rtsteps = 1;

  if (nrecs == 0) return CDI_EUFSTRUCT;

  cdi_generate_vars(streamptr);

  taxis->type = fcast ? TAXIS_RELATIVE : TAXIS_ABSOLUTE;
  int taxisID = taxisCreate(taxis->type);

  int vlistID = streamptr->vlistID;
  vlistDefTaxis(vlistID, taxisID);

  streamScanResizeRecords1(streamptr);

  streamptr->record->buffer = gribbuffer;
  streamptr->record->buffersize = buffersize;

  streamScanTsFixNtsteps(streamptr, recpos);
  streamScanTimeConstAdjust(streamptr, taxis);

  return 0;
}

int
cgribexScanTimestep2(stream_t *streamptr)
{
  CdiDateTime vDateTime0;
  cdiDateTime_init(&vDateTime0);
  int lmv = 0, iret = 0;
  off_t recpos = 0;
  int leveltype = 0, level1 = 0, level2 = 0;
  int recID = 0;
  bool warn_numavg = true;
  char paramstr[32];

  streamptr->curTsID = 1;

  cgribexrec_t *cgribexp = (cgribexrec_t *) streamptr->record->objectp;
  int *isec1 = cgribexp->sec1;
  int *isec2 = cgribexp->sec2;

  int fileID = streamptr->fileID;
  int vlistID = streamptr->vlistID;

  void *gribbuffer = streamptr->record->buffer;
  size_t buffersize = streamptr->record->buffersize;

  int tsID = streamptr->rtsteps;
  if (tsID != 1) Error("Internal problem! unexpected timestep %d", tsID + 1);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

  cdi_create_records(streamptr, tsID, true);
  recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
  record_t *records = streamptr->tsteps[tsID].records;

  int nrecords = streamScanInitRecords2(streamptr);

  int nrecsScanned = nrecords;
  int rindex = 0;
  while (true)
  {
    if (rindex > nrecords) break;

    size_t recsize = gribGetSize(fileID);
    recpos = fileGetPos(fileID);
    if (recsize == 0)
    {
      streamptr->ntsteps = 2;
      break;
    }

    grib_check_recsize(1, recsize);
    ensureBufferSize(recsize, &buffersize, &gribbuffer);

    size_t readsize = recsize;
    if (gribRead(fileID, gribbuffer, &readsize)) break;

    grbDecompress(recsize, &buffersize, &gribbuffer);

    nrecsScanned++;
    cgribexDecodeHeader(cgribexp, (int *) gribbuffer, (int) recsize, &lmv, &iret);

    int param = cdiEncodeParam(ISEC1_Parameter, ISEC1_CodeTable, 255);
    cdiParamToString(param, paramstr, sizeof(paramstr));

    cgribexGetLevel(isec1, &leveltype, &level1, &level2);

    CdiDateTime sDateTime;
    CdiDateTime vDateTime = cgribexDateTimeX(isec1, &sDateTime);

    if (rindex == 0)
    {
      vDateTime0 = vDateTime;
      int taxisID = vlistInqTaxis(vlistID);
      if (taxisInqType(taxisID) == TAXIS_RELATIVE)
      {
        taxis->type = TAXIS_RELATIVE;
        taxis->rDateTime = cdiDateTime_set(gribRefDate(isec1), gribRefTime(isec1));
      }
      else { taxis->type = TAXIS_ABSOLUTE; }
      taxis->unit = cgribexGetTimeUnit(isec1);
      taxis->vDateTime = vDateTime;
      taxis->sDateTime = sDateTime;
    }
    else
    {
      if (cdiDateTime_isLT(sDateTime, taxis->sDateTime)) taxis->sDateTime = sDateTime;
    }

    int tsteptype = cgribexGetTsteptype(ISEC1_TimeRange);

    if (ISEC1_AvgNum)
    {
      if (taxis->numavg && warn_numavg && (taxis->numavg != ISEC1_AvgNum))
        warn_numavg = false;
      else
        taxis->numavg = ISEC1_AvgNum;
    }

    size_t gridsize = cgribexGetGridsize(cgribexp->sec4);
    compvar_t compVar = cgribexVarSet(param, level1, level2, leveltype, ISEC1_TimeRange, gridsize);

    for (recID = 0; recID < nrecords; recID++)
    {
      if (cgribexVarCompare(&compVar, &records[recID], 0) == 0) break;
    }

    if (recID == nrecords)
    {
      gribWarning("Parameter not defined at timestep 1!", nrecsScanned, tsID + 1, paramstr, level1, level2);
      return CDI_EUFSTRUCT;
    }

    if (CDI_Inventory_Mode == 1)
    {
      if (recinfo[recID].used) { break; }
      else
      {
        recinfo[recID].used = true;
        streamptr->tsteps[tsID].recIDs[rindex] = recID;
      }
    }
    else
    {
      if (recinfo[recID].used)
      {
        if (cdiDateTime_isNE(vDateTime, vDateTime0)) break;

        gribWarning("Parameter already exist, skipped!", nrecsScanned, tsID + 1, paramstr, level1, level2);
        continue;
      }
      else
      {
        recinfo[recID].used = true;
        streamptr->tsteps[tsID].recIDs[rindex] = recID;
      }
    }

    if (CDI_Debug)
      Message("Read record %2d (id=%s lev1=%d lev2=%d) %s", nrecsScanned, paramstr, level1, level2, CdiDateTime_string(vDateTime));

    if (cgribexVarCompare(&compVar, &streamptr->tsteps[tsID].records[recID], 0) != 0)
    {
      Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
              records[recID].ilevel, level1);
      return CDI_EUFSTRUCT;
    }

    records[recID].position = recpos;
    records[recID].size = recsize;

    int varID = recinfo[recID].varID;
    int gridID = vlistInqVarGrid(vlistID, varID);
    if (gridInqSize(gridID) == 1 && gridInqType(gridID) == GRID_LONLAT)
    {
      if (IS_NOT_EQUAL(gridInqXval(gridID, 0), ISEC2_FirstLon * 0.001)
          || IS_NOT_EQUAL(gridInqYval(gridID, 0), ISEC2_FirstLat * 0.001))
        gridChangeType(gridID, GRID_TRAJECTORY);
    }

    if (tsteptype != TSTEP_INSTANT2 && tsteptype != vlistInqVarTsteptype(vlistID, varID))
      vlistDefVarTsteptype(vlistID, varID, tsteptype);

    rindex++;
  }

  int nrecs = 0;
  for (recID = 0; recID < nrecords; recID++)
  {
    if (recinfo[recID].used)
      nrecs++;
    else
      vlistDefVarTimetype(vlistID, recinfo[recID].varID, TIME_CONSTANT);
  }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  streamptr->record->buffer = gribbuffer;
  streamptr->record->buffersize = buffersize;

  return 0;
}

int
cgribexScanTimestep(stream_t *streamptr)
{
  CdiDateTime vDateTime0;
  cdiDateTime_init(&vDateTime0);
  int lmv = 0, iret = 0;
  off_t recpos = 0;
  int leveltype = 0, level1 = 0, level2 = 0;
  int vrecID, recID = 0;
  bool warn_numavg = true;
  int nrecs = 0;
  char paramstr[32];

  cgribexrec_t *cgribexp = (cgribexrec_t *) streamptr->record->objectp;
  int *isec1 = cgribexp->sec1;

  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if (streamptr->tsteps[tsID].recordSize == 0)
  {
    void *gribbuffer = streamptr->record->buffer;
    size_t buffersize = streamptr->record->buffersize;

    cdi_create_records(streamptr, tsID, true);
    recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
    record_t *records = streamptr->tsteps[tsID].records;

    nrecs = streamScanInitRecords(streamptr, tsID);

    int fileID = streamptr->fileID;

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

    int nrecsScanned = streamptr->tsteps[0].nallrecs + streamptr->tsteps[1].nrecs * (tsID - 1);
    int rindex = 0;
    while (true)
    {
      if (rindex > nrecs) break;

      size_t recsize = gribGetSize(fileID);
      recpos = fileGetPos(fileID);
      if (recsize == 0)
      {
        streamptr->ntsteps = streamptr->rtsteps + 1;
        break;
      }

      grib_check_recsize(1, recsize);
      ensureBufferSize(recsize, &buffersize, &gribbuffer);

      size_t readsize = recsize;
      if (gribRead(fileID, gribbuffer, &readsize))
      {
        Warning("Inconsistent timestep %d (GRIB record %d/%d)!", tsID + 1, rindex + 1, streamptr->tsteps[tsID].recordSize);
        break;
      }

      grbDecompress(recsize, &buffersize, &gribbuffer);

      nrecsScanned++;
      cgribexDecodeHeader(cgribexp, (int *) gribbuffer, (int) recsize, &lmv, &iret);

      int param = cdiEncodeParam(ISEC1_Parameter, ISEC1_CodeTable, 255);
      cdiParamToString(param, paramstr, sizeof(paramstr));

      cgribexGetLevel(isec1, &leveltype, &level1, &level2);

      CdiDateTime sDateTime;
      CdiDateTime vDateTime = cgribexDateTimeX(isec1, &sDateTime);

      if (rindex == nrecs) break;

      if (rindex == 0)
      {
        vDateTime0 = vDateTime;
        int vlistID = streamptr->vlistID;
        int taxisID = vlistInqTaxis(vlistID);
        if (taxisInqType(taxisID) == TAXIS_RELATIVE)
        {
          taxis->type = TAXIS_RELATIVE;
          taxis->rDateTime = cdiDateTime_set(gribRefDate(isec1), gribRefTime(isec1));
        }
        else { taxis->type = TAXIS_ABSOLUTE; }
        taxis->unit = cgribexGetTimeUnit(isec1);
        taxis->vDateTime = vDateTime;
        taxis->sDateTime = sDateTime;
      }
      else
      {
        if (cdiDateTime_isLT(sDateTime, taxis->sDateTime)) taxis->sDateTime = sDateTime;
      }

      if (ISEC1_AvgNum)
      {
        if (taxis->numavg && warn_numavg && (taxis->numavg != ISEC1_AvgNum))
          warn_numavg = false;
        else
          taxis->numavg = ISEC1_AvgNum;
      }

      size_t gridsize = cgribexGetGridsize(cgribexp->sec4);
      compvar_t compVar = cgribexVarSet(param, level1, level2, leveltype, ISEC1_TimeRange, gridsize);

      for (vrecID = 0; vrecID < nrecs; vrecID++)
      {
        recID = streamptr->tsteps[1].recIDs[vrecID];
        if (cgribexVarCompare(&compVar, &records[recID], 0) == 0) break;
      }

      if (vrecID == nrecs)
      {
        gribWarning("Parameter not defined at timestep 1!", nrecsScanned, tsID + 1, paramstr, level1, level2);

        if (CDI_Inventory_Mode == 1)
          return CDI_EUFSTRUCT;
        else
          continue;
      }

      if (CDI_Inventory_Mode == 1)
      {
        recinfo[recID].used = true;
        streamptr->tsteps[tsID].recIDs[rindex] = recID;
      }
      else
      {
        if (recinfo[recID].used)
        {
          char paramstr_[32];
          cdiParamToString(param, paramstr_, sizeof(paramstr_));

          if (cdiDateTime_isNE(vDateTime, vDateTime0)) break;

          if (CDI_Debug) gribWarning("Parameter already exist, skipped!", nrecsScanned, tsID + 1, paramstr_, level1, level2);

          continue;
        }
        else
        {
          recinfo[recID].used = true;
          streamptr->tsteps[tsID].recIDs[rindex] = recID;
        }
      }

      if (CDI_Debug)
        Message("Read record %2d (id=%s lev1=%d lev2=%d) %s", nrecsScanned, paramstr, level1, level2,
                CdiDateTime_string(vDateTime));

      if (cgribexVarCompare(&compVar, &records[recID], 0) != 0)
      {
        Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
                records[recID].ilevel, level1);
        Error("Invalid, unsupported or inconsistent record structure");
      }

      records[recID].position = recpos;
      records[recID].size = recsize;

      rindex++;
    }

    for (vrecID = 0; vrecID < nrecs; vrecID++)
    {
      recID = streamptr->tsteps[tsID].recIDs[vrecID];
      if (!recinfo[recID].used) break;
    }

    if (vrecID < nrecs)
    {
      cdiParamToString(records[recID].param, paramstr, sizeof(paramstr));
      gribWarning("Parameter not found!", nrecsScanned, tsID + 1, paramstr, records[recID].ilevel, records[recID].ilevel2);
      return CDI_EUFSTRUCT;
    }

    streamptr->rtsteps++;

    if (streamptr->ntsteps != streamptr->rtsteps)
    {
      tsID = tstepsNewEntry(streamptr);
      if (tsID != streamptr->rtsteps) Error("Internal error. tsID = %d", tsID);

      streamptr->tsteps[tsID - 1].next = true;
      streamptr->tsteps[tsID].position = recpos;
    }

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);
    streamptr->tsteps[tsID].position = recpos;

    streamptr->record->buffer = gribbuffer;
    streamptr->record->buffersize = buffersize;
  }

  if (nrecs > 0 && nrecs < streamptr->tsteps[tsID].nrecs)
  {
    Warning("Incomplete timestep. Stop scanning at timestep %d.", tsID);
    streamptr->ntsteps = tsID;
  }

  /* this will need work for files containing more than INT_MAX timesteps! */
  assert(streamptr->ntsteps <= INT_MAX);
  return (int) streamptr->ntsteps;
}

#ifdef gribWarning
#undef gribWarning
#endif

int
cgribexDecode(int memtype, void *cgribex, void *gribbuffer, size_t gribsize, void *data, size_t datasize, int unreduced,
              size_t *numMissVals, double missval)
{
  int status = 0;

  bool lalloc = cgribex == NULL;
  cgribexrec_t *cgribexp = (cgribexrec_t *) (lalloc ? cgribexNew() : cgribex);

  int *isec0 = cgribexp->sec0;
  int *isec1 = cgribexp->sec1;
  int *isec2 = cgribexp->sec2;
  int *isec3 = cgribexp->sec3;
  int *isec4 = cgribexp->sec4;
  double *fsec2 = cgribexp->fsec2;
  double *fsec3 = cgribexp->fsec3;
  float fsec2f[sizeof(cgribexp->fsec2) / sizeof(double)];
  float fsec3f[sizeof(cgribexp->fsec3) / sizeof(double)];

  char hoper[2];
  strcpy(hoper, unreduced ? "R" : "D");

  FSEC3_MissVal = missval;

  int iret = 0, iword = 0;
  if (memtype == MEMTYPE_FLOAT)
    gribExSP(isec0, isec1, isec2, fsec2f, isec3, fsec3f, isec4, (float *) data, (int) datasize, (int *) gribbuffer, (int) gribsize,
             &iword, hoper, &iret);
  else
    gribExDP(isec0, isec1, isec2, fsec2, isec3, fsec3, isec4, (double *) data, (int) datasize, (int *) gribbuffer, (int) gribsize,
             &iword, hoper, &iret);

  *numMissVals = (ISEC1_Sec2Or3Flag & 64) ? (size_t) ISEC4_NumValues - (size_t) ISEC4_NumNonMissValues : 0;

  if (ISEC1_CenterID == 215 && (isec1[34] != 0 && isec1[34] != 255))
  {
    double undef_pds, undef_eps;
    MCH_get_undef(isec1, &undef_pds, &undef_eps);

    *numMissVals = 0;
    if (memtype == MEMTYPE_FLOAT)
    {
      float *restrict dataf = (float *) data;
      for (size_t i = 0; i < datasize; i++)
        if ((fabs(dataf[i] - undef_pds) < undef_eps) || IS_EQUAL(dataf[i], FSEC3_MissVal))
        {
          dataf[i] = (float) missval;
          (*numMissVals)++;
        }
    }
    else
    {
      double *restrict datad = (double *) data;
      for (size_t i = 0; i < datasize; i++)
        if ((fabs(datad[i] - undef_pds) < undef_eps) || IS_EQUAL(datad[i], FSEC3_MissVal))
        {
          datad[i] = missval;
          (*numMissVals)++;
        }
    }
  }

  if (lalloc) cgribexDelete(cgribexp);

  return status;
}

static void
cgribexDefInstitut(int *isec1, int vlistID, int varID)
{
  int instID = (vlistInqInstitut(vlistID) != CDI_UNDEFID) ? vlistInqInstitut(vlistID) : vlistInqVarInstitut(vlistID, varID);
  if (instID != CDI_UNDEFID)
  {
    ISEC1_CenterID = institutInqCenter(instID);
    ISEC1_SubCenterID = institutInqSubcenter(instID);
  }
}

static void
cgribexDefModel(int *isec1, int vlistID, int varID)
{
  int modelID = (vlistInqModel(vlistID) != CDI_UNDEFID) ? vlistInqModel(vlistID) : vlistInqVarModel(vlistID, varID);
  if (modelID != CDI_UNDEFID) ISEC1_ModelID = modelInqGribID(modelID);
}

static void
cgribexDefParam(int *isec1, int param)
{
  int pdis, pcat, pnum;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);
  if (pnum < 0) pnum = -pnum;

  static bool lwarn_pdis = true;
  if (pdis != 255 && lwarn_pdis)
  {
    char paramstr[32];
    cdiParamToString(param, paramstr, sizeof(paramstr));
    Warning("Can't convert GRIB2 parameter ID (%s) to GRIB1, set to %d.%d!", paramstr, pnum, pcat);
    lwarn_pdis = false;
  }

  static bool lwarn_pnum = true;
  if (pnum > 255 && lwarn_pnum)
  {
    Warning("Parameter number %d out of range (1-255), set to %d!", pnum, pnum % 256);
    lwarn_pnum = false;
    pnum = pnum % 256;
  }

  ISEC1_CodeTable = pcat;
  ISEC1_Parameter = pnum;
}

static int
cgribexDefTimerange(int tsteptype, int factor, int calendar, CdiDateTime rDateTime, CdiDateTime vDateTime, CdiDateTime sDateTime,
                    int *pip1, int *pip2)
{
  JulianDate julianDate1 = julianDate_encode(calendar, rDateTime);
  JulianDate julianDate2 = julianDate_encode(calendar, vDateTime);
  JulianDate julianDate = julianDate_sub(julianDate2, julianDate1);

  int timerange = -1;
  int ip1 = 0, ip2 = 0;
  if (!(int) (fmod(julianDate_to_seconds(julianDate), factor)))
  {
    int ip = (int) lround(julianDate_to_seconds(julianDate) / factor);
    if ((ip > 255) && (tsteptype == TSTEP_INSTANT)) tsteptype = TSTEP_INSTANT3;

    int ipx = 0;
    if (!cdiDateTime_isNull(sDateTime)
        && (tsteptype == TSTEP_RANGE || tsteptype == TSTEP_AVG || tsteptype == TSTEP_ACCUM || tsteptype == TSTEP_DIFF))
    {
      julianDate2 = julianDate_encode(calendar, sDateTime);
      ipx = (int) lround(julianDate_to_seconds(julianDate_sub(julianDate2, julianDate1)) / factor);
    }

    // clang-format off
    switch (tsteptype)
    {
      case TSTEP_INSTANT:  timerange =  0; ip1 = ip;  ip2 = 0;  break;
      case TSTEP_INSTANT2: timerange =  1; ip1 = 0;   ip2 = 0;  break;
      case TSTEP_RANGE:    timerange =  2; ip1 = 0;   ip2 = ip; break;
      case TSTEP_AVG:      timerange =  3; ip1 = 0;   ip2 = ip; break;
      case TSTEP_ACCUM:    timerange =  4; ip1 = ipx; ip2 = ip; break;
      case TSTEP_DIFF:     timerange =  5; ip1 = 0;   ip2 = ip; break;
      case TSTEP_INSTANT3:
      default:             timerange = 10; ip1 = ip/256; ip2 = ip%256; break;
    }
    // clang-format on
  }

  *pip1 = ip1;
  *pip2 = ip2;

  return timerange;
}

static int
cgribexDefDateTime(int *isec1, int timeunit, CdiDateTime dt)
{
  int year, month, day, hour, minute, second, ms;
  cdiDate_decode(dt.date, &year, &month, &day);
  cdiTime_decode(dt.time, &hour, &minute, &second, &ms);

  int century = year / 100;
  ISEC1_Year = year - century * 100;

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

  if (ISEC1_Year == 0)
  {
    century -= 1;
    ISEC1_Year = 100;
  }

  century += 1;
  if (year < 0) century = -century;

  ISEC1_Month = month;
  ISEC1_Day = day;
  ISEC1_Hour = hour;
  ISEC1_Minute = minute;

  ISEC1_Century = century;

  int factor = 1;
  // clang-format off
  switch (timeunit)
    {
    case TUNIT_MINUTE:    factor =    60; ISEC1_TimeUnit = ISEC1_TABLE4_MINUTE;    break;
    case TUNIT_QUARTER:   factor =   900; ISEC1_TimeUnit = ISEC1_TABLE4_QUARTER;   break;
    case TUNIT_30MINUTES: factor =  1800; ISEC1_TimeUnit = ISEC1_TABLE4_30MINUTES; break;
    case TUNIT_HOUR:      factor =  3600; ISEC1_TimeUnit = ISEC1_TABLE4_HOUR;      break;
    case TUNIT_3HOURS:    factor = 10800; ISEC1_TimeUnit = ISEC1_TABLE4_3HOURS;    break;
    case TUNIT_6HOURS:    factor = 21600; ISEC1_TimeUnit = ISEC1_TABLE4_6HOURS;    break;
    case TUNIT_12HOURS:   factor = 43200; ISEC1_TimeUnit = ISEC1_TABLE4_12HOURS;   break;
    case TUNIT_DAY:       factor = 86400; ISEC1_TimeUnit = ISEC1_TABLE4_DAY;       break;
    default:              factor =  3600; ISEC1_TimeUnit = ISEC1_TABLE4_HOUR;      break;
    }
  // clang-format on

  return factor;
}

static void
cgribexDefTime(int *isec1, CdiDateTime vDateTime, int tsteptype, int numavg, int taxisID)
{
  int timetype = TAXIS_ABSOLUTE;
  int timeunit = TUNIT_HOUR;

  if (taxisID != -1)
  {
    timetype = taxisInqType(taxisID);
    timeunit = taxisInqTunit(taxisID);
  }

  if (timetype == TAXIS_RELATIVE)
  {
    int ip1 = 0, ip2 = 0;
    int calendar = taxisInqCalendar(taxisID);

    CdiDateTime rDateTime = taxisInqRdatetime(taxisID);
    if (cdiDateTime_isLT(vDateTime, rDateTime)) rDateTime = vDateTime;

    CdiDateTime sDateTime = taxisInqSdatetime(taxisID);

    int factor = cgribexDefDateTime(isec1, timeunit, rDateTime);
    int timerange = cgribexDefTimerange(tsteptype, factor, calendar, rDateTime, vDateTime, sDateTime, &ip1, &ip2);

    if (ip2 > 0xFF)
    {
      rDateTime = vDateTime;
      factor = cgribexDefDateTime(isec1, timeunit, rDateTime);
      timerange = cgribexDefTimerange(tsteptype, factor, calendar, rDateTime, vDateTime, sDateTime, &ip1, &ip2);
    }
    /*
    if (ip2 > 0xFF && timeunit < TUNIT_YEAR)
      {
        timeunit++;
        factor = cgribexDefDateTime(isec1, timeunit, rDateTime);
        timerange = cgribexDefTimerange(tsteptype, factor, calendar, rDateTime, vDateTime, sDateTime, &ip1, &ip2);
      }
    */
    if (timerange == -1 || timerange == 1 || timerange == 3) timetype = TAXIS_ABSOLUTE;
    /*
    else if (timerange == 10)
      {
        if (ip1 < 0 || ip1 > 0xFFFF) timetype = TAXIS_ABSOLUTE;
        if (ip2 < 0 || ip2 > 0xFFFF) timetype = TAXIS_ABSOLUTE;
      }
    */
    else
    {
      if (ip1 < 0 || ip1 > 0xFF) timetype = TAXIS_ABSOLUTE;
      if (ip2 < 0 || ip2 > 0xFF) timetype = TAXIS_ABSOLUTE;
    }

    if (timetype != TAXIS_ABSOLUTE)
    {
      ISEC1_TimeRange = timerange;
      ISEC1_TimePeriod1 = ip1;
      ISEC1_TimePeriod2 = ip2;
    }
  }

  if (timetype == TAXIS_ABSOLUTE)
  {
    (void) cgribexDefDateTime(isec1, timeunit, vDateTime);

    /*
    if (numavg > 0)
      ISEC1_TimeRange = 0;
    else
    */
    if (ISEC1_TimeRange != 3) ISEC1_TimeRange = 10;

    ISEC1_TimePeriod1 = 0;
    ISEC1_TimePeriod2 = 0;
  }

  ISEC1_AvgNum = numavg;
  ISEC1_AvgMiss = 0;
  ISEC1_DecScaleFactor = 0;
}

static void
cgribexDefGridRegular(int *isec2, double *fsec2, int gridID, int gridtype, bool gridIsRotated, bool gridIsCurvilinear,
                      int uvRelativeToGrid)
{
  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED)
    ISEC2_GridType = GRIB1_GTYPE_GAUSSIAN;
  else if (gridtype == GRID_LONLAT && gridIsRotated)
    ISEC2_GridType = GRIB1_GTYPE_LATLON_ROT;
  else
    ISEC2_GridType = GRIB1_GTYPE_LATLON;

  double xfirst = 0.0, xlast = 0.0, xinc = 0.0;
  double yfirst = 0.0, ylast = 0.0, yinc = 0.0;

  int nlon = (int) gridInqXsize(gridID);
  int nlat = (int) gridInqYsize(gridID);

  if (gridtype == GRID_GAUSSIAN_REDUCED)
  {
    ISEC2_Reduced = true;
    if (nlon == 2)
    {
      xfirst = gridInqXval(gridID, 0);
      xlast = gridInqXval(gridID, 1);
    }
    else { xlast = 360.0 - 360.0 / (nlat * 2); }

    nlon = 0;
    gridInqReducedPoints(gridID, ISEC2_ReducedPointsPtr);
  }
  else
  {
    if (nlon == 0)
      nlon = 1;
    else
    {
      xfirst = gridInqXval(gridID, 0);
      xlast = gridInqXval(gridID, (gridIsCurvilinear ? nlon * nlat : nlon) - 1);
      xinc = fabs(gridInqXinc(gridID));
    }
  }

  if (nlat == 0)
    nlat = 1;
  else
  {
    yfirst = gridInqYval(gridID, 0);
    ylast = gridInqYval(gridID, (gridIsCurvilinear ? nlon * nlat : nlat) - 1);
    yinc = fabs(gridInqYinc(gridID));
  }

  ISEC2_NumLon = nlon;
  ISEC2_NumLat = nlat;
  ISEC2_FirstLat = (int) lround(yfirst * 1000);
  ISEC2_LastLat = (int) lround(ylast * 1000);
  ISEC2_FirstLon = (int) lround(xfirst * 1000);
  ISEC2_LastLon = (int) lround(xlast * 1000);
  // gribapi gridType detector doesn't like lonIncr for Gaussian reduced longitides
  if (gridtype != GRID_GAUSSIAN_REDUCED) ISEC2_LonIncr = (int) lround(xinc * 1000);

  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED)
  {
    int np = gridInqNP(gridID);
    if (np == 0) np = nlat / 2;
    ISEC2_NumPar = np;
  }
  else { ISEC2_LatIncr = (int) lround(yinc * 1000); }

  if (ISEC2_NumLon > 1 && ISEC2_NumLat == 1)
    if (ISEC2_LonIncr != 0 && ISEC2_LatIncr == 0) ISEC2_LatIncr = ISEC2_LonIncr;

  if (ISEC2_NumLon == 1 && ISEC2_NumLat > 1)
    if (ISEC2_LonIncr == 0 && ISEC2_LatIncr != 0) ISEC2_LonIncr = ISEC2_LatIncr;

  ISEC2_ResFlag = 0;
  if (ISEC2_LatIncr && ISEC2_LonIncr) gribbyte_set_bit(&ISEC2_ResFlag, 1);
  if (uvRelativeToGrid > 0) gribbyte_set_bit(&ISEC2_ResFlag, 5);

  if (gridIsRotated)
  {
    double xpole = 0, ypole = 0, angle = 0;
    gridInqParamRLL(gridID, &xpole, &ypole, &angle);

    ISEC2_LatSP = -(int) lround(ypole * 1000);
    ISEC2_LonSP = (int) lround((xpole + 180) * 1000);
    if (fabs(angle) > 0) angle = -angle;
    FSEC2_RotAngle = angle;
  }

  ISEC2_ScanFlag = 0;
  if (ISEC2_LastLon < ISEC2_FirstLon) gribbyte_set_bit(&ISEC2_ScanFlag, 1);  // East -> West
  if (ISEC2_LastLat > ISEC2_FirstLat) gribbyte_set_bit(&ISEC2_ScanFlag, 2);  // South -> North
}

static void
cgribexDefGridLambert(int *isec2, int gridID, int uvRelativeToGrid)
{
  int xsize = (int) gridInqXsize(gridID);
  int ysize = (int) gridInqYsize(gridID);

  struct CDI_GridProjParams gpp;
  gridInqParamsLCC(gridID, &gpp);
  if (IS_EQUAL(gpp.x_0, gpp.mv) && IS_EQUAL(gpp.y_0, gpp.mv) && (IS_EQUAL(gpp.xval_0, gpp.mv) || IS_EQUAL(gpp.yval_0, gpp.mv)))
  {
    gpp.x_0 = gridInqXval(gridID, 0);
    gpp.y_0 = gridInqYval(gridID, 0);
  }
  gridVerifyProjParamsLCC(&gpp);

  bool lsouth = (gpp.lat_1 < 0);
  if (lsouth)
  {
    gpp.lat_1 = -gpp.lat_2;
    gpp.lat_2 = -gpp.lat_2;
  }

  double xinc = gridInqXinc(gridID);
  double yinc = gridInqYinc(gridID);
  if (IS_EQUAL(xinc, 0.0)) xinc = gridInqXincInMeter(gridID);
  if (IS_EQUAL(yinc, 0.0)) yinc = gridInqYincInMeter(gridID);

  ISEC2_GridType = GRIB1_GTYPE_LCC;
  ISEC2_NumLon = xsize;
  ISEC2_NumLat = ysize;
  ISEC2_FirstLon = (int) lround(gpp.xval_0 * 1000);
  ISEC2_FirstLat = (int) lround(gpp.yval_0 * 1000);
  ISEC2_Lambert_Lov = (int) lround(gpp.lon_0 * 1000);
  ISEC2_Lambert_LatS1 = (int) lround(gpp.lat_1 * 1000);
  ISEC2_Lambert_LatS2 = (int) lround(gpp.lat_2 * 1000);
  ISEC2_Lambert_dx = (int) lround(xinc);
  ISEC2_Lambert_dy = (int) lround(yinc);
  ISEC2_Lambert_LatSP = 0;
  ISEC2_Lambert_LonSP = 0;
  ISEC2_Lambert_ProjFlag = 0;
  if (lsouth) gribbyte_set_bit(&ISEC2_Lambert_ProjFlag, 1);

  bool earthIsOblate = (IS_EQUAL(gpp.a, 6378160.0) && IS_EQUAL(gpp.rf, 297.0));
  ISEC2_ResFlag = 0;
  if (ISEC2_Lambert_dx && ISEC2_Lambert_dy) gribbyte_set_bit(&ISEC2_ResFlag, 1);
  if (earthIsOblate) gribbyte_set_bit(&ISEC2_ResFlag, 2);
  if (uvRelativeToGrid > 0) gribbyte_set_bit(&ISEC2_ResFlag, 5);

  ISEC2_ScanFlag = 0;
  gribbyte_set_bit(&ISEC2_ScanFlag, 2);  // South -> North
}

static void
cgribexDefGridSpectal(int *isec2, int *isec4, int gridID)
{
  ISEC2_GridType = GRIB1_GTYPE_SPECTRAL;
  ISEC2_PentaJ = gridInqTrunc(gridID);
  ISEC2_PentaK = ISEC2_PentaJ;
  ISEC2_PentaM = ISEC2_PentaJ;
  ISEC2_RepType = 1;
  isec4[2] = 128;
  if (gridInqComplexPacking(gridID) && ISEC2_PentaJ >= 21)
  {
    ISEC2_RepMode = 2;
    isec4[3] = 64;
    isec4[16] = 0;
    isec4[17] = 20;
    isec4[18] = 20;
    isec4[19] = 20;
  }
  else
  {
    ISEC2_RepMode = 1;
    isec4[3] = 0;
  }
}

static void
cgribexDefGridGME(int *isec2, int gridID)
{
  ISEC2_GridType = GRIB1_GTYPE_GME;
  int nd = 0, ni = 0, ni2 = 0, ni3 = 0;
  gridInqParamGME(gridID, &nd, &ni, &ni2, &ni3);
  ISEC2_GME_ND = nd;
  ISEC2_GME_NI = ni;
  ISEC2_GME_NI2 = ni2;
  ISEC2_GME_NI3 = ni3;
  ISEC2_GME_AFlag = 0;
  ISEC2_GME_LatPP = 90000;
  ISEC2_GME_LonPP = 0;
  ISEC2_GME_LonMPL = 0;
  ISEC2_GME_BFlag = 0;
}

static void
cgribexDefGrid(int *isec1, int *isec2, double *fsec2, int *isec4, int gridID, int uvRelativeToGrid)
{
  fill_intarr(isec2, 0, 16);
  ISEC1_Sec2Or3Flag = 128;
  ISEC1_GridDefinition = 255;
  ISEC2_Reduced = false;
  ISEC2_ScanFlag = 0;

  SizeType gridsize = gridInqSize(gridID);
  bool gridIsRotated = false;
  bool gridIsCurvilinear = false;
  int gridtype = grbGetGridtype(&gridID, gridsize, &gridIsRotated, &gridIsCurvilinear);

  switch (gridtype)
  {
    case GRID_LONLAT:
    case GRID_GAUSSIAN:
    case GRID_GAUSSIAN_REDUCED:
    case GRID_TRAJECTORY:
    {
      cgribexDefGridRegular(isec2, fsec2, gridID, gridtype, gridIsRotated, gridIsCurvilinear, uvRelativeToGrid);
      break;
    }
    case CDI_PROJ_LCC:
    {
      cgribexDefGridLambert(isec2, gridID, uvRelativeToGrid);
      break;
    }
    case GRID_SPECTRAL:
    {
      cgribexDefGridSpectal(isec2, isec4, gridID);
      break;
    }
    case GRID_GME:
    {
      cgribexDefGridGME(isec2, gridID);
      break;
    }
    case GRID_GENERIC:
    {
      ISEC1_Sec2Or3Flag = 0;
      break;
    }
    case CDI_PROJ_HEALPIX:
    {
      Error("CGRIBEX library doesn't support HEALPix grids!");
      break;
    }
    default:
    {
      static bool lwarn = true;
      ISEC1_Sec2Or3Flag = 0;
      if (lwarn) Warning("CGRIBEX library doesn't support %s grids, grid information will be lost!", gridNamePtr(gridtype));
      lwarn = false;
      break;
    }
  }
}

static int
level2int(double level)
{
  return (int) round(level);
}

static void
isec1DefLevel(int *isec1, int leveltype, int level1, int level2)
{
  ISEC1_LevelType = leveltype;
  ISEC1_Level1 = level1;
  ISEC1_Level2 = level2;
}

static void
cgribexDefLevel(int *isec1, int *isec2, double *fsec2, int zaxisID, int levelID)
{
  int zaxistype = zaxisInqType(zaxisID);
  int ltype = 0;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, &ltype);

  bool hasBounds = (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL));
  double level = zaxisInqLevels(zaxisID, NULL) ? zaxisInqLevel(zaxisID, levelID) : levelID + 1;
  double dlevel1 = hasBounds ? zaxisInqLbound(zaxisID, levelID) : level;
  double dlevel2 = hasBounds ? zaxisInqUbound(zaxisID, levelID) : 0.0;

  if (zaxistype == ZAXIS_GENERIC && ltype == 0)
  {
    Warning("Changed zaxis type from %s to %s", zaxisNamePtr(zaxistype), zaxisNamePtr(ZAXIS_PRESSURE));
    zaxistype = ZAXIS_PRESSURE;
    zaxisChangeType(zaxisID, zaxistype);
    cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, "Pa");
  }

  ISEC2_NumVCP = 0;

  int grib_ltype = zaxisTypeToGrib1ltype(zaxistype);

  switch (zaxistype)
  {
    case ZAXIS_SURFACE:
    case ZAXIS_MEANSEA:
    case ZAXIS_ALTITUDE:
    case ZAXIS_DEPTH_BELOW_SEA:
    case ZAXIS_ISENTROPIC:
    {
      isec1DefLevel(isec1, grib_ltype, level2int(level), 0);
      break;
    }
    case ZAXIS_CLOUD_BASE:
    case ZAXIS_CLOUD_TOP:
    case ZAXIS_ISOTHERM_ZERO:
    case ZAXIS_TROPOPAUSE:
    case ZAXIS_TOA:
    case ZAXIS_SEA_BOTTOM:
    case ZAXIS_ATMOSPHERE:
    {
      isec1DefLevel(isec1, grib_ltype, 0, 0);
      break;
    }
    case ZAXIS_HYBRID:
    case ZAXIS_HYBRID_HALF:
    {
      grib_ltype = hasBounds ? GRIB1_LTYPE_HYBRID_LAYER : GRIB1_LTYPE_HYBRID;
      isec1DefLevel(isec1, grib_ltype, level2int(dlevel1), level2int(dlevel2));

      int vctsize = zaxisInqVctSize(zaxisID);
      if (vctsize > 255)
      {
        static bool lwarning_vct = true;
        ISEC2_NumVCP = 0;
        if (lwarning_vct)
        {
          Warning("VCT size of %d is too large (maximum is 255). Set to 0!", vctsize);
          lwarning_vct = false;
        }
      }
      else
      {
        ISEC2_NumVCP = vctsize;
        zaxisInqVct(zaxisID, &fsec2[10]);
      }
      break;
    }
    case ZAXIS_PRESSURE:
    {
      if (level < 0) Warning("Pressure level of %f Pa is below zero!", level);

      if (!zaxis_units_is_Pa(zaxisID)) level *= 100.0;

      double dum;
      if (level < 32768 && (level < 100 || modf(level / 100, &dum) > 0))
        grib_ltype = GRIB1_LTYPE_ISOBARIC_PA;
      else
        level = level / 100;

      static bool lwarn = true;
      if (lwarn && modf(level, &dum) > 0.001)
      {
        lwarn = false;
        Message("GRIB1 can store only full pressure levels! Level changed from %ghPa to %dhPa.", level, level2int(level));
      }

      isec1DefLevel(isec1, grib_ltype, level2int(level), 0);
      break;
    }
    case ZAXIS_HEIGHT:
    {
      double sf = zaxis_units_to_meter(zaxisID);
      isec1DefLevel(isec1, grib_ltype, level2int(level * sf), 0);
      break;
    }
    case ZAXIS_SIGMA:
    {
      grib_ltype = hasBounds ? GRIB1_LTYPE_SIGMA_LAYER : GRIB1_LTYPE_SIGMA;
      isec1DefLevel(isec1, grib_ltype, level2int(dlevel1), level2int(dlevel2));
      break;
    }
    case ZAXIS_DEPTH_BELOW_LAND:
    {
      grib_ltype = hasBounds ? GRIB1_LTYPE_LANDDEPTH_LAYER : GRIB1_LTYPE_LANDDEPTH;
      double sf = zaxis_units_to_centimeter(zaxisID);
      isec1DefLevel(isec1, grib_ltype, level2int(sf * dlevel1), level2int(sf * dlevel2));
      break;
    }
    case ZAXIS_GENERIC:
    {
      isec1DefLevel(isec1, ltype, level2int(level), 0);
      break;
    }
    default:
    {
      Error("Unsupported zaxis type: %s", zaxisNamePtr(zaxistype));
      break;
    }
  }
}

static void
cgribexDefaultSec0(int *isec0)
{
  ISEC0_GRIB_Len = 0;
  ISEC0_GRIB_Version = 0;
}

static void
cgribexDefaultSec1(int *isec1)
{
  ISEC1_CenterID = 0;
  ISEC1_SubCenterID = 0;
  ISEC1_LocalFLag = 0;
}

static void
cgribexDefaultSec4(int *isec4)
{
  for (int i = 2; i <= 10; ++i) isec4[i] = 0;
}

static void
cgribexDefEnsembleVar(int *isec1, int vlistID, int varID)
{
  // For Ensemble info

  // 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

  if (ISEC1_CenterID == 252)
  {
    int perturbationNumber, numberOfForecastsInEnsemble, typeOfEnsembleForecast;
    int r1 = cdiInqKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, &perturbationNumber);
    int r2 = cdiInqKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, &numberOfForecastsInEnsemble);
    int r3 = cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, &typeOfEnsembleForecast);

    if (r1 == 0 && r2 == 0 && r3 == 0)
    {
      ISEC1_LocalFLag = 1;
      isec1[36] = 1;
      isec1[37] = typeOfEnsembleForecast;
      isec1[38] = perturbationNumber;
      isec1[39] = numberOfForecastsInEnsemble;
    }
  }
}

size_t
cgribexEncode(int memtype, int varID, int levelID, int vlistID, int gridID, int zaxisID, CdiDateTime vDateTime, int tsteptype,
              int numavg, SizeType datasize, const void *data, SizeType numMissVals, void *gribbuffer, size_t gribbuffersize)
{
  cgribexrec_t *cgribexp = (cgribexrec_t *) cgribexNew();

  size_t sec2len = 1024 + 2 * (size_t) gridInqYsize(gridID);  // Gaussian reduced grid
  if (sec2len > cgribexp->sec2len)
  {
    cgribexp->sec2len = sec2len;
    cgribexp->sec2 = (int *) Realloc(cgribexp->sec2, sec2len * sizeof(int));
  }

  int *isec0 = cgribexp->sec0;
  int *isec1 = cgribexp->sec1;
  int *isec2 = cgribexp->sec2;
  int *isec3 = cgribexp->sec3;
  int *isec4 = cgribexp->sec4;
  double *fsec2 = cgribexp->fsec2;
  double *fsec3 = cgribexp->fsec3;
  float fsec2f[sizeof(cgribexp->fsec2) / sizeof(double)];
  float fsec3f[sizeof(cgribexp->fsec3) / sizeof(double)];

  fill_intarr(isec1, 0, 256);
  fsec2[0] = 0;
  fsec2[1] = 0;
  fsec2f[0] = 0;
  fsec2f[1] = 0;

  int gribsize = (int) (gribbuffersize / sizeof(int));
  int param = vlistInqVarParam(vlistID, varID);

  cgribexDefaultSec0(isec0);
  cgribexDefaultSec1(isec1);
  cgribexDefaultSec4(isec4);

  cgribexDefInstitut(isec1, vlistID, varID);
  cgribexDefModel(isec1, vlistID, varID);

  int datatype = vlistInqVarDatatype(vlistID, varID);

  int uvRelativeToGrid = -1;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_UVRELATIVETOGRID, &uvRelativeToGrid);

  cgribexDefParam(isec1, param);
  cgribexDefTime(isec1, vDateTime, tsteptype, numavg, vlistInqTaxis(vlistID));
  cgribexDefGrid(isec1, isec2, fsec2, isec4, gridID, uvRelativeToGrid);
  cgribexDefLevel(isec1, isec2, fsec2, zaxisID, levelID);

  cgribexDefEnsembleVar(isec1, vlistID, varID);

  cdi_check_gridsize_int_limit("GRIB1", datasize);

  ISEC4_NumValues = (int) datasize;
  ISEC4_NumBits = grbBitsPerValue(datatype);

  if (numMissVals > 0)
  {
    FSEC3_MissVal = vlistInqVarMissval(vlistID, varID);
    ISEC1_Sec2Or3Flag |= 64;
  }

  if (isec4[2] == 128 && isec4[3] == 64)
  {
    if (memtype == MEMTYPE_FLOAT)
      isec4[16] = (int) (1000 * calculate_pfactor_float((const float *) data, ISEC2_PentaJ, isec4[17]));
    else
      isec4[16] = (int) (1000 * calculate_pfactor_double((const double *) data, ISEC2_PentaJ, isec4[17]));
    if (isec4[16] < -10000) isec4[16] = -10000;
    if (isec4[16] > 10000) isec4[16] = 10000;
  }
  // printf("isec4[16] %d\n", isec4[16]);

  if (memtype == MEMTYPE_FLOAT)
  {
    int numVCP = (ISEC2_NumVCP > 0) ? ISEC2_NumVCP : 0;
    for (int i = 0; i < numVCP; ++i) fsec2f[10 + i] = (float) fsec2[10 + i];
    fsec3f[1] = (float) fsec3[1];
  }

  int iret = 0, iword = 0;
  if (memtype == MEMTYPE_FLOAT)
    gribExSP(isec0, isec1, isec2, fsec2f, isec3, fsec3f, isec4, (float *) data, (int) datasize, (int *) gribbuffer, gribsize,
             &iword, "C", &iret);
  else
    gribExDP(isec0, isec1, isec2, fsec2, isec3, fsec3, isec4, (double *) data, (int) datasize, (int *) gribbuffer, gribsize, &iword,
             "C", &iret);

  cgribexDelete(cgribexp);

  if (iret) Error("Problem during GRIB encode (errno = %d)!", iret);

  size_t nbytes = (size_t) iword * sizeof(int);
  return nbytes;
}

void
cgribexChangeParameterIdentification(void *gh, int code, int ltype, int lev)
{
  if (!gh) return;

  unsigned char *pds = ((cgribex_handle *) gh)->pds;
  if (!pds) return;

  pds[8] = (unsigned char) code;
  pds[9] = (unsigned char) ltype;
  pds[10] = (unsigned char) lev;
}

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif



#ifdef HAVE_LIBEXTRA

static int
extInqDatatype(int prec, int number)
{
  if (number == 2)
    return (prec == EXSE_PREC_FP64) ? CDI_DATATYPE_CPX64 : CDI_DATATYPE_CPX32;
  else
    return (prec == EXSE_PREC_FP64) ? CDI_DATATYPE_FLT64 : CDI_DATATYPE_FLT32;
}

static void
extDefDatatype(int datatype, int *prec, int *number)
{
  if (datatype != CDI_DATATYPE_FLT32 && datatype != CDI_DATATYPE_FLT64 && datatype != CDI_DATATYPE_CPX32
      && datatype != CDI_DATATYPE_CPX64)
    datatype = CDI_DATATYPE_FLT32;

  *number = (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64) ? 2 : 1;

  *prec = (datatype == CDI_DATATYPE_FLT64 || datatype == CDI_DATATYPE_CPX64) ? EXSE_PREC_FP64 : EXSE_PREC_FP32;
}

static void
ext_read_recordSP(stream_t *streamptr, float *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;

  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *extp = streamptr->record->objectp;
  if (extRead(fileID, extp) < 0) Error("Failed to read EXTRA record");

  int header[4];
  extInqHeader(extp, header);
  extInqDataFP32(extp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  size_t size = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));

  if (vlistInqVarNumber(vlistID, varID) == CDI_REAL)
    *numMissVals = get_num_missvalsSP(size, data, (float) missval);
  else
    *numMissVals = get_cplx_num_missvalsSP(size, data, (float) missval);

  streamptr->numvals += (SizeType) size;
}

static void
ext_read_recordDP(stream_t *streamptr, double *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;

  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *extp = streamptr->record->objectp;
  if (extRead(fileID, extp) < 0) Error("Failed to read EXTRA record");

  int header[4];
  extInqHeader(extp, header);
  extInqDataFP64(extp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  size_t size = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));

  if (vlistInqVarNumber(vlistID, varID) == CDI_REAL)
    *numMissVals = get_num_missvalsDP(size, data, missval);
  else
    *numMissVals = get_cplx_num_missvalsDP(size, data, missval);

  streamptr->numvals += (SizeType) size;
}

void
ext_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals)
{
  if (memtype == MEMTYPE_DOUBLE)
    ext_read_recordDP(streamptr, (double *) data, numMissVals);
  else
    ext_read_recordSP(streamptr, (float *) data, numMissVals);
}

void
extCopyField(stream_t *streamptr2, stream_t *streamptr1)
{
  streamFCopyRecord(streamptr2, streamptr1, "EXTRA");
}

void
extDefField(stream_t *streamptr)
{
  Record *record = streamptr->record;

  int pdis, pcat, pnum;
  cdiDecodeParam(record->param, &pnum, &pcat, &pdis);

  int header[4];
  header[0] = record->vdate;
  header[1] = pnum;
  header[2] = record->ilevel;
  int gridID = record->gridID;
  cdi_check_gridsize_int_limit("EXTRA", gridInqSize(gridID));
  header[3] = (int) gridInqSize(gridID);

  extrec_t *extp = (extrec_t *) record->objectp;
  extDefDatatype(record->prec, &extp->prec, &extp->number);
  extDefHeader(extp, header);
}

static void
ext_write_recordSP(stream_t *streamptr, const float *data)
{
  void *extp = streamptr->record->objectp;
  extDefDataFP32(extp, data);
  extWrite(streamptr->fileID, extp);
}

static void
ext_write_recordDP(stream_t *streamptr, const double *data)
{
  void *extp = streamptr->record->objectp;
  extDefDataFP64(extp, data);
  extWrite(streamptr->fileID, extp);
}

void
ext_write_field(stream_t *streamptr, int memtype, const void *data)
{
  if (memtype == MEMTYPE_DOUBLE)
    ext_write_recordDP(streamptr, (const double *) data);
  else
    ext_write_recordSP(streamptr, (const float *) data);
}

static void
extAddRecord(stream_t *streamptr, int param, int level, size_t xysize, size_t recsize, off_t position, int prec, int number)
{
  int vlistID = streamptr->vlistID;
  int tsID = streamptr->curTsID;
  int recID = recordNewEntry(streamptr, tsID);
  recinfo_t *recinfo = &(streamptr->tsteps[tsID].recinfo[recID]);
  record_t *record = &(streamptr->tsteps[tsID].records[recID]);

  record->size = recsize;
  record->position = position;
  record->param = param;
  record->ilevel = level;

  grid_t *grid = (grid_t *) Malloc(sizeof(*grid));
  grid_init(grid);
  cdiGridTypeInit(grid, GRID_GENERIC, xysize);
  grid->x.size = xysize;
  grid->y.size = 0;
  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, grid, 0);
  int gridID = gridAdded.Id;
  if (!gridAdded.isNew)
  {
    grid_free(grid);
    Free(grid);
  }

  int leveltype = ZAXIS_GENERIC;
  int datatype = extInqDatatype(prec, number);

  int varID, levelID = 0;
  varAddRecord(recID, param, gridID, leveltype, 0, level, 0, 0, 0, datatype, &varID, &levelID, TSTEP_INSTANT, 0, -1, NULL, NULL,
               NULL, NULL);

  recinfo->varID = (short) varID;
  recinfo->levelID = levelID;

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;

  if (CDI_Debug) Message("varID = %d gridID = %d levelID = %d", varID, gridID, levelID);
}

static void
extScanTimestep1(stream_t *streamptr)
{
  CdiDateTime datetime0;
  cdiDateTime_init(&datetime0);
  int header[4];
  off_t recpos = 0;
  extrec_t *extp = (extrec_t *) streamptr->record->objectp;

  streamptr->curTsID = 0;

  int tsID = tstepsNewEntry(streamptr);
  if (tsID != 0) Error("Internal problem! tstepsNewEntry returns %d", tsID);
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  int fileID = streamptr->fileID;

  int nrecs = 0;
  while (true)
  {
    recpos = fileGetPos(fileID);
    if (extRead(fileID, extp) != 0)
    {
      streamptr->ntsteps = 1;
      break;
    }

    size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

    extInqHeader(extp, header);

    int vdate = header[0];
    int vtime = 0;
    int rcode = header[1];
    int rlevel = header[2];
    int rxysize = header[3];
    int param = cdiEncodeParam(rcode, 255, 255);
    CdiDateTime datetime = cdiDateTime_set(vdate, vtime);

    if (nrecs == 0)
    {
      datetime0 = datetime;
      taxis->vDateTime = datetime;
    }
    else
    {
      record_t *records = streamptr->tsteps[tsID].records;
      for (int recID = 0; recID < nrecs; recID++)
        if (param == records[recID].param && rlevel == records[recID].ilevel) goto tstepScanLoopFinished;

      if (cdiDateTime_isNE(datetime, datetime0)) Warning("Inconsistent verification time for code %d level %d", rcode, rlevel);
    }

    nrecs++;

    if (CDI_Debug) Message("%4d%8d%4d%8d%8d%6d", nrecs, (int) recpos, rcode, rlevel, vdate, vtime);

    extAddRecord(streamptr, param, rlevel, (size_t) rxysize, recsize, recpos, extp->prec, extp->number);
  }

tstepScanLoopFinished:
  streamptr->rtsteps = 1;

  cdi_generate_vars(streamptr);

  int taxisID = taxisCreate(TAXIS_ABSOLUTE);
  taxis->type = TAXIS_ABSOLUTE;
  taxis->rDateTime = taxis->vDateTime;

  int vlistID = streamptr->vlistID;
  vlistDefTaxis(vlistID, taxisID);

  vlist_check_contents(vlistID);

  streamScanResizeRecords1(streamptr);

  streamScanTsFixNtsteps(streamptr, recpos);
  streamScanTimeConstAdjust(streamptr, taxis);
}

static int
extScanTimestep2(stream_t *streamptr)
{
  int header[4];
  off_t recpos = 0;
  void *extp = streamptr->record->objectp;

  streamptr->curTsID = 1;

  int fileID = streamptr->fileID;
  int vlistID = streamptr->vlistID;

  int tsID = streamptr->rtsteps;
  if (tsID != 1) Error("Internal problem! unexpected timestep %d", tsID + 1);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

  cdi_create_records(streamptr, tsID, true);
  recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
  record_t *records = streamptr->tsteps[tsID].records;

  int nrecords = streamScanInitRecords2(streamptr);

  for (int rindex = 0; rindex <= nrecords; rindex++)
  {
    recpos = fileGetPos(fileID);
    if (extRead(fileID, extp) != 0)
    {
      streamptr->ntsteps = 2;
      break;
    }

    size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

    extInqHeader(extp, header);

    int vdate = header[0];
    int vtime = 0;
    int rcode = header[1];
    int rlevel = header[2];
    int param = cdiEncodeParam(rcode, 255, 255);

    if (rindex == 0)
    {
      taxis->type = TAXIS_ABSOLUTE;
      taxis->vDateTime = cdiDateTime_set(vdate, vtime);
    }

    bool nextstep = false;
    int recID;
    for (recID = 0; recID < nrecords; recID++)
    {
      if (param == records[recID].param && rlevel == records[recID].ilevel)
      {
        if (recinfo[recID].used) { nextstep = true; }
        else
        {
          recinfo[recID].used = true;
          streamptr->tsteps[tsID].recIDs[rindex] = recID;
        }
        break;
      }
    }
    if (recID == nrecords)
    {
      Warning("Code %d level %d not found at timestep %d", rcode, rlevel, tsID + 1);
      return CDI_EUFSTRUCT;
    }

    if (nextstep) break;

    if (CDI_Debug) Message("%4d%8d%4d%8d%8d%6d", rindex + 1, (int) recpos, rcode, rlevel, vdate, vtime);

    if (param != records[recID].param || rlevel != records[recID].ilevel)
    {
      Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
              records[recID].ilevel, rlevel);
      return CDI_EUFSTRUCT;
    }

    records[recID].position = recpos;
    records[recID].size = recsize;
  }

  int nrecs = 0;
  for (int recID = 0; recID < nrecords; recID++)
  {
    if (recinfo[recID].used)
      nrecs++;
    else
      vlistDefVarTimetype(vlistID, recinfo[recID].varID, TIME_CONSTANT);
  }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  return 0;
}

int
extInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  extScanTimestep1(streamptr);

  int status = (streamptr->ntsteps == -1) ? extScanTimestep2(streamptr) : 0;

  fileSetPos(streamptr->fileID, 0, SEEK_SET);

  return status;
}

static long
extScanTimestep(stream_t *streamptr)
{
  int header[4];
  off_t recpos = 0;
  int nrecs = 0;
  void *extp = streamptr->record->objectp;

  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if (streamptr->tsteps[tsID].recordSize == 0)
  {
    cdi_create_records(streamptr, tsID, true);
    record_t *records = streamptr->tsteps[tsID].records;

    nrecs = streamScanInitRecords(streamptr, tsID);

    int fileID = streamptr->fileID;

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

    for (int rindex = 0; rindex <= nrecs; rindex++)
    {
      recpos = fileGetPos(fileID);
      if (extRead(fileID, extp) != 0)
      {
        streamptr->ntsteps = streamptr->rtsteps + 1;
        break;
      }

      size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

      extInqHeader(extp, header);

      int vdate = header[0];
      int vtime = 0;
      int rcode = header[1];
      int rlevel = header[2];
      int param = cdiEncodeParam(rcode, 255, 255);

      // if ( rindex == nrecs ) break; gcc-4.5 internal compiler error
      if (rindex == nrecs) continue;
      int recID = streamptr->tsteps[tsID].recIDs[rindex];

      if (rindex == 0)
      {
        taxis->type = TAXIS_ABSOLUTE;
        taxis->vDateTime = cdiDateTime_set(vdate, vtime);
      }

      if (param != records[recID].param || rlevel != records[recID].ilevel)
      {
        Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
                records[recID].ilevel, rlevel);
        Error("Invalid, unsupported or inconsistent record structure!");
      }

      records[recID].position = recpos;
      records[recID].size = recsize;

      if (CDI_Debug) Message("%4d%8d%4d%8d%8d%6d", rindex, (int) recpos, rcode, rlevel, vdate, vtime);
    }

    streamptr->rtsteps++;

    if (streamptr->ntsteps != streamptr->rtsteps)
    {
      tsID = tstepsNewEntry(streamptr);
      if (tsID != streamptr->rtsteps) Error("Internal error. tsID = %d", tsID);

      streamptr->tsteps[tsID - 1].next = true;
      streamptr->tsteps[tsID].position = recpos;
    }

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);
    streamptr->tsteps[tsID].position = recpos;
  }

  if (nrecs > 0 && nrecs < streamptr->tsteps[tsID].nrecs)
  {
    Warning("Incomplete timestep. Stop scanning at timestep %d.", tsID);
    streamptr->ntsteps = tsID;
  }

  return streamptr->ntsteps;
}

int
extInqTimestep(stream_t *streamptr, int tsID)
{
  if (tsID == 0 && streamptr->rtsteps == 0) Error("Call to cdiInqContents missing!");

  if (CDI_Debug) Message("tsID = %d rtsteps = %d", tsID, streamptr->rtsteps);

  long ntsteps = CDI_UNDEFID;
  while ((tsID + 1) > streamptr->rtsteps && ntsteps == CDI_UNDEFID) ntsteps = extScanTimestep(streamptr);

  int nrecs = 0;
  if (!(tsID >= streamptr->ntsteps && streamptr->ntsteps != CDI_UNDEFID))
  {
    streamptr->curTsID = tsID;
    nrecs = streamptr->tsteps[tsID].nrecs;
  }

  return nrecs;
}

void
extReadVarSliceDP(stream_t *streamptr, int varID, int levID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  void *extp = streamptr->record->objectp;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  double missval = vlistInqVarMissval(vlistID, varID);
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  int tsid = streamptr->curTsID;

  off_t currentfilepos = fileGetPos(fileID);

  int recID = streamptr->vars[varID].recordTable[0].recordID[levID];
  off_t recpos = streamptr->tsteps[tsid].records[recID].position;
  fileSetPos(fileID, recpos, SEEK_SET);
  extRead(fileID, extp);
  int header[4];
  extInqHeader(extp, header);
  extInqDataFP64(extp, data);

  fileSetPos(fileID, currentfilepos, SEEK_SET);

  if (vlistInqVarNumber(vlistID, varID) == CDI_REAL)
    *numMissVals = get_num_missvalsDP(gridsize, data, missval);
  else
    *numMissVals = get_cplx_num_missvalsDP(gridsize, data, missval);
}

void
extReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevs = (size_t) streamptr->vars[varID].recordTable[0].nlevs;

  for (size_t levID = 0; levID < nlevs; levID++)
    extReadVarSliceDP(streamptr, varID, (int) levID, &data[levID * gridsize], numMissVals);
}

void
extWriteVarSliceDP(stream_t *streamptr, int varID, int levID, const double *data)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;
  CdiDateTime vDateTime = streamptr->tsteps[tsID].taxis.vDateTime;

  int pdis, pcat, pnum;
  cdiDecodeParam(vlistInqVarParam(vlistID, varID), &pnum, &pcat, &pdis);

  int header[4];
  header[0] = (int) cdiDate_get(vDateTime.date);
  header[1] = pnum;
  header[2] = (int) lround(zaxisInqLevel(vlistInqVarZaxis(vlistID, varID), levID));
  int gridID = vlistInqVarGrid(vlistID, varID);
  cdi_check_gridsize_int_limit("EXTRA", gridInqSize(gridID));
  header[3] = (int) gridInqSize(gridID);

  extrec_t *extp = (extrec_t *) streamptr->record->objectp;
  extDefDatatype(vlistInqVarDatatype(vlistID, varID), &extp->prec, &extp->number);
  extDefHeader(extp, header);

  extDefDataFP64(extp, data);
  extWrite(fileID, extp);
}

void
extWriteVarDP(stream_t *streamptr, int varID, const double *data)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevs = (size_t) zaxisInqSize(vlistInqVarZaxis(vlistID, varID));

  for (size_t levID = 0; levID < nlevs; levID++) extWriteVarSliceDP(streamptr, varID, (int) levID, &data[levID * gridsize]);
}

#endif /* HAVE_LIBEXTRA */

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef STREAM_GRIBAPI_H
#define STREAM_GRIBAPI_H

#ifdef HAVE_LIBGRIB_API


int fdbScanTimesteps(stream_t *streamptr);

long gribapiScanTimestep1(stream_t *streamptr);
long gribapiScanTimestep2(stream_t *streamptr);
long gribapiScanTimestep(stream_t *streamptr);

int gribapiDecode(int memType, void *gribbuffer, size_t gribsize, void *data, size_t datasize, int unreduced, size_t *numMissVals,
                  double missval);

size_t gribapiEncode(int memType, int varID, int levelID, int vlistID, int gridID, int zaxisID, CdiDateTime vDateTime,
                     int tsteptype, int numavg, SizeType datasize, const void *data, SizeType numMissVals, void **gribbuffer,
                     size_t *gribbuffersize, int ljpeg, void *gribContainer);

int gribapiGetScanningMode(grib_handle *gh);
void gribapiSetScanningMode(grib_handle *gh, int scanningMode);

void gribapiChangeParameterIdentification(grib_handle *gh, int code, int ltype, int lev);

#endif

#endif /* STREAM_GRIBAPI_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif


int cdiDebugExt = 0;  //  Debug level for the KNMI extensions
#ifdef HIRLAM_EXTENSIONS
// *** RELATED to GRIB only ***
int cdiGribUseTimeRangeIndicator = 0;  // normaly cdo looks in grib for attribute called "stepType"
                                       // but NWP models such as Harmonie 37h1.2, use "timeRangeIndicator"
                                       // where:  0: for instanteneous fields; 4: for accumulated fields
#endif                                 // HIRLAM_EXTENSIONS

double
zaxis_units_to_centimeter(int zaxisID)
{
  char units[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);

  double sf = 100.0;  // default: meter
  // clang-format off
  if (units[1] == 'm' && !units[2])
    {
      if      (units[0] == 'm') sf =   0.1;
      else if (units[0] == 'c') sf =   1.0;
      else if (units[0] == 'd') sf =  10.0;
    }
  // clang-format on

  return sf;
}

double
zaxis_units_to_meter(int zaxisID)
{
  char units[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);

  double sf = 1.0;  // default: meter
  // clang-format off
  if (units[1] == 'm' && !units[2])
    {
      if      (units[0] == 'm') sf /= 1000.0;
      else if (units[0] == 'c') sf /= 100.0;
      else if (units[0] == 'd') sf /= 10.0;
      else if (units[0] == 'k') sf *= 1000.0;
    }
  // clang-format on

  return sf;
}

bool
zaxis_units_is_Pa(int zaxisID)
{
  char units[CDI_MAX_NAME] = { 0 };
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);

  return (units[0] == 'P' && units[1] == 'a');
}

void
ensureBufferSize(size_t requiredSize, size_t *curSize, void **buffer)
{
  if (*curSize < requiredSize)
  {
    *curSize = requiredSize;
    *buffer = Realloc(*buffer, *curSize);
  }
}

int
grbDecompress(size_t recsize, size_t *buffersize, void **gribbuffer)
{
  int comptype = CDI_COMPRESS_NONE;

  size_t unzipsize;
  if (gribGetZip(recsize, (unsigned char *) *gribbuffer, &unzipsize) > 0)
  {
    comptype = CDI_COMPRESS_SZIP;
    unzipsize += 100;  // need 0 to 1 bytes for rounding of bds
    ensureBufferSize(unzipsize, buffersize, gribbuffer);
  }

  return comptype;
}

// Regarding operation to change parameter identification:
// change if cdiGribChangeParameterID.active
struct cdiGribParamChange cdiGribChangeParameterID;

// Used only for CDO module Selmulti
void
streamGrbChangeParameterIdentification(int code, int ltype, int lev)
{
  // NOTE this is a "PROXY" function for gribapiChangeParameterIdentification();
  // This just sets the globals. There are probably better solutions to this.
  // The parameter change is done by function  gribapiChangeParameterIdentification() in stream_gribapi.c
  // Setting this control variable to true will cause calling fnc. gribapiChangeParameterIdentification later.
  // After grib attributes have been changed this variable goes to false.
  cdiGribChangeParameterID.active = true;
  cdiGribChangeParameterID.code = code;
  cdiGribChangeParameterID.ltype = ltype;
  cdiGribChangeParameterID.lev = lev;
}

struct cdiGribScanModeChange cdiGribDataScanningMode;

void
streamGrbDefDataScanningMode(int scanmode)
{
  cdiGribDataScanningMode.active = true;
  cdiGribDataScanningMode.value = scanmode;
}

enum
{
  MapKey = 0,
  MapValue = 1
};

static const int grib1ltypeMap[][2] = {
  // clang-format off
  {  GRIB1_LTYPE_SURFACE,             ZAXIS_SURFACE            },
  {  GRIB1_LTYPE_CLOUD_BASE,          ZAXIS_CLOUD_BASE         },
  {  GRIB1_LTYPE_CLOUD_TOP,           ZAXIS_CLOUD_TOP          },
  {  GRIB1_LTYPE_ISOTHERM0,           ZAXIS_ISOTHERM_ZERO      },
  {  GRIB1_LTYPE_TROPOPAUSE,          ZAXIS_TROPOPAUSE         },
  {  GRIB1_LTYPE_TOA,                 ZAXIS_TOA                },
  {  GRIB1_LTYPE_SEA_BOTTOM,          ZAXIS_SEA_BOTTOM         },
  {  GRIB1_LTYPE_ATMOSPHERE,          ZAXIS_ATMOSPHERE         },
  {  GRIB1_LTYPE_ISOBARIC,            ZAXIS_PRESSURE           },
  {  GRIB1_LTYPE_99,                  ZAXIS_PRESSURE           },
  {  GRIB1_LTYPE_ISOBARIC_PA,         ZAXIS_PRESSURE           },
  {  GRIB1_LTYPE_MEANSEA,             ZAXIS_MEANSEA            },
  {  GRIB1_LTYPE_ALTITUDE,            ZAXIS_ALTITUDE           },
  {  GRIB1_LTYPE_HEIGHT,              ZAXIS_HEIGHT             },
  {  GRIB1_LTYPE_SIGMA,               ZAXIS_SIGMA              },
  {  GRIB1_LTYPE_SIGMA_LAYER,         ZAXIS_SIGMA              },
  {  GRIB1_LTYPE_HYBRID,              ZAXIS_HYBRID             },
  {  GRIB1_LTYPE_HYBRID_LAYER,        ZAXIS_HYBRID             },
  {  GRIB1_LTYPE_LANDDEPTH,           ZAXIS_DEPTH_BELOW_LAND   },
  {  GRIB1_LTYPE_LANDDEPTH_LAYER,     ZAXIS_DEPTH_BELOW_LAND   },
  {  GRIB1_LTYPE_ISENTROPIC,          ZAXIS_ISENTROPIC         },
  {  GRIB1_LTYPE_SEADEPTH,            ZAXIS_DEPTH_BELOW_SEA    },
  {  GRIB1_LTYPE_LAKE_BOTTOM,         ZAXIS_LAKE_BOTTOM        },
  {  GRIB1_LTYPE_SEDIMENT_BOTTOM,     ZAXIS_SEDIMENT_BOTTOM    },
  {  GRIB1_LTYPE_SEDIMENT_BOTTOM_TA,  ZAXIS_SEDIMENT_BOTTOM_TA },
  {  GRIB1_LTYPE_SEDIMENT_BOTTOM_TW,  ZAXIS_SEDIMENT_BOTTOM_TW },
  {  GRIB1_LTYPE_MIX_LAYER,           ZAXIS_MIX_LAYER          },
  // clang-format on
};

static const size_t grib1ltypeMapSize = sizeof(grib1ltypeMap) / (2 * sizeof(int));

static const int grib2ltypeMap[][2] = {
  // clang-format off
  {  GRIB2_LTYPE_SURFACE,             ZAXIS_SURFACE            },
  {  GRIB2_LTYPE_CLOUD_BASE,          ZAXIS_CLOUD_BASE         },
  {  GRIB2_LTYPE_CLOUD_TOP,           ZAXIS_CLOUD_TOP          },
  {  GRIB2_LTYPE_ISOTHERM0,           ZAXIS_ISOTHERM_ZERO      },
  {  GRIB2_LTYPE_TROPOPAUSE,          ZAXIS_TROPOPAUSE         },
  {  GRIB2_LTYPE_TOA,                 ZAXIS_TOA                },
  {  GRIB2_LTYPE_SEA_BOTTOM,          ZAXIS_SEA_BOTTOM         },
  {  GRIB2_LTYPE_ATMOSPHERE,          ZAXIS_ATMOSPHERE         },
  {  GRIB2_LTYPE_ISOBARIC,            ZAXIS_PRESSURE           },
  {  GRIB2_LTYPE_MEANSEA,             ZAXIS_MEANSEA            },
  {  GRIB2_LTYPE_ALTITUDE,            ZAXIS_ALTITUDE           },
  {  GRIB2_LTYPE_HEIGHT,              ZAXIS_HEIGHT             },
  {  GRIB2_LTYPE_SIGMA,               ZAXIS_SIGMA              },
  {  GRIB2_LTYPE_HYBRID,              ZAXIS_HYBRID             },
  {  GRIB2_LTYPE_HYBRID,              ZAXIS_HYBRID_HALF        },
  {  GRIB2_LTYPE_LANDDEPTH,           ZAXIS_DEPTH_BELOW_LAND   },
  {  GRIB2_LTYPE_ISENTROPIC,          ZAXIS_ISENTROPIC         },
  {  GRIB2_LTYPE_SEADEPTH,            ZAXIS_DEPTH_BELOW_SEA    },
  {  GRIB2_LTYPE_LAKE_BOTTOM,         ZAXIS_LAKE_BOTTOM        },
  {  GRIB2_LTYPE_SEDIMENT_BOTTOM,     ZAXIS_SEDIMENT_BOTTOM    },
  {  GRIB2_LTYPE_SEDIMENT_BOTTOM_TA,  ZAXIS_SEDIMENT_BOTTOM_TA },
  {  GRIB2_LTYPE_SEDIMENT_BOTTOM_TW,  ZAXIS_SEDIMENT_BOTTOM_TW },
  {  GRIB2_LTYPE_MIX_LAYER,           ZAXIS_MIX_LAYER          },
  {  GRIB2_LTYPE_SNOW,                ZAXIS_SNOW               },
  {  GRIB2_LTYPE_REFERENCE,           ZAXIS_REFERENCE          },
  // clang-format on
};

static const size_t grib2ltypeMapSize = sizeof(grib2ltypeMap) / (2 * sizeof(int));

static int
getInt2IntMap(int searchKey, bool keyValue, size_t mapSize, const int gribltypeMap[][2], int defaultValue)
{
  for (size_t i = 0; i < mapSize; ++i)
    if (gribltypeMap[i][keyValue] == searchKey) return gribltypeMap[i][!keyValue];

  return defaultValue;
}

int
grib1ltypeToZaxisType(int grib_ltype)
{
  return getInt2IntMap(grib_ltype, MapKey, grib1ltypeMapSize, grib1ltypeMap, ZAXIS_GENERIC);
}

int
zaxisTypeToGrib1ltype(int zaxistype)
{
  return getInt2IntMap(zaxistype, MapValue, grib1ltypeMapSize, grib1ltypeMap, -1);
}

int
grib2ltypeToZaxisType(int grib_ltype)
{
  return getInt2IntMap(grib_ltype, MapKey, grib2ltypeMapSize, grib2ltypeMap, ZAXIS_GENERIC);
}

int
zaxisTypeToGrib2ltype(int zaxistype)
{
  return getInt2IntMap(zaxistype, MapValue, grib2ltypeMapSize, grib2ltypeMap, -1);
}

int
grbBitsPerValue(int datatype)
{
  int bitsPerValue = 16;

  if (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64) Error("CDI/GRIB library does not support complex numbers!");

  if (datatype != CDI_UNDEFID)
  {
    if (datatype > 0 && datatype <= 32)
      bitsPerValue = datatype;
    else if (datatype == CDI_DATATYPE_FLT64)
      bitsPerValue = 24;
    else
      bitsPerValue = 16;
  }

  return bitsPerValue;
}

void
grbDefField(stream_t *streamptr)
{
  UNUSED(streamptr);
}

#ifdef HAVE_LIBGRIB
static long
grbScanTimestep1(stream_t *streamptr)
{
  long status = CDI_EUFTYPE;

#ifdef HAVE_LIBCGRIBEX
  int filetype = streamptr->filetype;

  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1)
    status = cgribexScanTimestep1(streamptr);
  else
#endif
#ifdef HAVE_LIBGRIB_API
    status = gribapiScanTimestep1(streamptr);
#else
  Error("GRIB_API support unavailable!");
#endif

  return status;
}

static long
grbScanTimestep2(stream_t *streamptr)
{
  long status = CDI_EUFTYPE;

#ifdef HAVE_LIBCGRIBEX
  int filetype = streamptr->filetype;

  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1)
    status = cgribexScanTimestep2(streamptr);
  else
#endif
#ifdef HAVE_LIBGRIB_API
    status = gribapiScanTimestep2(streamptr);
#else
  Error("GRIB_API support unavailable!");
#endif

  return status;
}

static long
grbScanTimestep(stream_t *streamptr)
{
  long status = CDI_EUFTYPE;

#ifdef HAVE_LIBCGRIBEX
  int filetype = streamptr->filetype;

  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1)
    status = cgribexScanTimestep(streamptr);
  else
#endif
#ifdef HAVE_LIBGRIB_API
    status = gribapiScanTimestep(streamptr);
#else
  Error("GRIB_API support unavailable!");
#endif

  return status;
}
#endif

#ifdef HAVE_LIBGRIB
long
grbInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  long status = grbScanTimestep1(streamptr);
  if (status == 0 && streamptr->ntsteps == -1) status = grbScanTimestep2(streamptr);

  int fileID = streamptr->fileID;
  fileSetPos(fileID, 0, SEEK_SET);

  return status;
}

#ifdef HAVE_LIBFDB5
int
fdbInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;
#ifdef HAVE_LIBGRIB_API
  return fdbScanTimesteps(streamptr);
#else
  return -1;
#endif
}
#endif

int
fdbInqTimestep(stream_t *streamptr, int tsID)
{
  if (tsID == 0 && streamptr->rtsteps == 0) Error("Call to cdiInqContents missing!");

  if (CDI_Debug) Message("tsid = %d rtsteps = %d", tsID, streamptr->rtsteps);

  int nrecs;
  if (tsID >= streamptr->ntsteps && streamptr->ntsteps != CDI_UNDEFID) { nrecs = 0; }
  else
  {
    streamptr->curTsID = tsID;
    nrecs = streamptr->tsteps[tsID].nrecs;
  }

  return nrecs;
}

int
grbInqTimestep(stream_t *streamptr, int tsID)
{
  if (tsID == 0 && streamptr->rtsteps == 0) Error("Call to cdiInqContents missing!");

  if (CDI_Debug) Message("tsid = %d rtsteps = %d", tsID, streamptr->rtsteps);

  long ntsteps = -1;
  while ((tsID + 1) > streamptr->rtsteps && ntsteps == -1)
  {
    ntsteps = grbScanTimestep(streamptr);
    if (ntsteps == CDI_EUFSTRUCT)
    {
      streamptr->ntsteps = streamptr->rtsteps;
      break;
    }
  }

  int nrecs;
  if (tsID >= streamptr->ntsteps && streamptr->ntsteps != -1) { nrecs = 0; }
  else
  {
    streamptr->curTsID = tsID;
    nrecs = streamptr->tsteps[tsID].nrecs;
  }

  return nrecs;
}

int
grbGetGridtype(int *gridID, SizeType gridsize, bool *gridIsRotated, bool *gridIsCurvilinear)
{
  int gridtype = gridInqType(*gridID);

  if (gridtype == GRID_GENERIC)
  {
    int xsize = (int) gridInqXsize(*gridID);
    int ysize = (int) gridInqYsize(*gridID);

    if ((ysize == 32 || ysize == 48 || ysize == 64 || ysize == 96 || ysize == 160 || ysize == 192 || ysize == 240 || ysize == 320
         || ysize == 384 || ysize == 480 || ysize == 768)
        && (xsize == 2 * ysize || xsize == 1))
    {
      gridtype = GRID_GAUSSIAN;
    }
    else if (gridsize == 1) { gridtype = GRID_LONLAT; }
    else if (gridInqXvals(*gridID, NULL) && gridInqYvals(*gridID, NULL)) { gridtype = GRID_LONLAT; }
  }
  else if (gridtype == GRID_CURVILINEAR)
  {
    int projID = gridInqProj(*gridID);
    if (projID != CDI_UNDEFID && gridInqType(projID) == GRID_PROJECTION)
    {
      *gridID = projID;
      gridtype = GRID_PROJECTION;
    }
    else
    {
      static bool lwarning = true;
      if (lwarning && gridsize > 1)
      {
        lwarning = false;
        Warning("Curvilinear grid is unsupported in GRIB format! Created wrong Grid Description Section!");
      }
      *gridIsCurvilinear = true;
      gridtype = GRID_LONLAT;
    }
  }

  if (gridtype == GRID_PROJECTION)
  {
    int projtype = gridInqProjType(*gridID);
    if (projtype == CDI_PROJ_RLL)
    {
      gridtype = GRID_LONLAT;
      *gridIsRotated = true;
    }
    else if (projtype == CDI_PROJ_LCC) { gridtype = CDI_PROJ_LCC; }
    else if (projtype == CDI_PROJ_STERE) { gridtype = CDI_PROJ_STERE; }
    else if (projtype == CDI_PROJ_HEALPIX) { gridtype = CDI_PROJ_HEALPIX; }
  }

  return gridtype;
}
#endif

// used in CDO!!!
void
streamInqGRIBinfo(int streamID, int *intnum, float *fltnum, off_t *bignum)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  if (streamptr->filetype == CDI_FILETYPE_GRB)
  {
#ifdef HAVE_LIBGRIB
    int tsID = streamptr->curTsID;
    int vrecID = streamptr->tsteps[tsID].curRecID;
    int recID = streamptr->tsteps[tsID].recIDs[vrecID];
    off_t recpos = streamptr->tsteps[tsID].records[recID].position;
    int zip = streamptr->tsteps[tsID].records[recID].zip;

    void *gribbuffer = streamptr->record->buffer;
    size_t gribbuffersize = streamptr->record->buffersize;

    if (zip > 0)
      Error("Compressed GRIB records unsupported!");
    else
      grib_info_for_grads(recpos, (long) gribbuffersize, (unsigned char *) gribbuffer, intnum, fltnum, bignum);
#else
    Error("GRIB support unavailable!");
#endif
  }
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef _SUBTYPE_H
#define _SUBTYPE_H

enum
{
  /* subtype attributes wrt. TILES */
  SUBTYPE_ATT_TILEINDEX = 0,
  SUBTYPE_ATT_TOTALNO_OF_TILEATTR_PAIRS = 1,
  SUBTYPE_ATT_TILE_CLASSIFICATION = 2,
  SUBTYPE_ATT_NUMBER_OF_TILES = 3,
  SUBTYPE_ATT_NUMBER_OF_ATTR = 4,
  SUBTYPE_ATT_TILEATTRIBUTE = 5,
  /* No. of different constants in the enumeration
     "subtype_attributes" */
  nSubtypeAttributes
};

/* Literal constants corresponding to the different constants of the
   enumeration "subtype_attributes". */
extern const char *const cdiSubtypeAttributeName[];

/* Data type specifying an attribute of a subtype (for example an
   attribute of a set of TILES) or an attribute of a subtype entry
   (for example an attribute of a single TILE). This data type is part
   of a linked list. */
struct subtype_attr_t
{
  int key, val;                /* key/value pair */
  struct subtype_attr_t *next; /* next element in linked list */
};

/* Data type specifying a single entry of a subtype, for example a
   single TILE in a set of TILES. */
struct subtype_entry_t
{
  int self;                     /* list entry index (0,...,nentries-1) */
  struct subtype_entry_t *next; /* next node in linked list */

  /* linked list with attributes for this subtype entry, ordered by its key values*/
  struct subtype_attr_t *atts;
};

/* Data type specifying a variable subtype, for example a list of
   TILES. This can be interpreted as an additional axis like the
   vertical axis. */
typedef struct
{
  int self;     /* resource handler ID */
  int subtype;  /* subtype kind: TILES, ... */
  int nentries; /* counter: total no. of entries in list */

  /* currently active subtype, e.g. GRIB2 tile index (for example for
     stream/vlist accesses): */
  int active_subtype_index;
  struct subtype_entry_t globals; /* global attributes */

  /* list of subtype entries, e.g. the list of tiles, ordered by entry->self. */
  struct subtype_entry_t *entries;
} subtype_t;

/* prototypes: allocation and destruction */
void subtypeAllocate(subtype_t **subtype_ptr2, int subtype);
int subtypePush(subtype_t *subtype_ptr);
void subtypeDestroyPtr(void *ptr);
void subtypeDuplicate(subtype_t *subtype_ptr, subtype_t **dst);
struct subtype_entry_t *subtypeEntryInsert(subtype_t *head);

/* prototypes: accessing global attributes */
void subtypePrint(int subtypeID);
void subtypePrintPtr(subtype_t *subtype_ptr);
void subtypeDefGlobalDataP(subtype_t *subtype_ptr, int key, int val);
void subtypeDefGlobalData(int subtypeID, int key, int val);
int subtypeGetGlobalData(int subtypeID, int key);
int subtypeGetGlobalDataP(subtype_t *subtype_ptr, int key);
int subtypeComparePtr(int s1_ID, subtype_t *s2);

/* prototypes: accessing subtype entries */
void subtypeDefEntryDataP(struct subtype_entry_t *subtype_entry_ptr, int key, int val);

/* prototypes: tile implementations */
void tilesetInsertP(subtype_t *s1, subtype_t *s2);

/* Construct a new subtype for a tile set. If a corresponding subtype
 * already exists, then we return this subtype ID instead. */
int vlistDefTileSubtype(int vlistID, subtype_t *tiles);

/* Insert a trivial one-tile-subtype */
int vlistInsertTrivialTileSubtype(int vlistID);

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBGRIB_API



#include <grib_api.h>

extern int CDI_Inventory_Mode;

static const var_tile_t dummy_tiles = { 0, -1, -1, -1, -1, -1 };

typedef struct
{
  size_t gridsize;
  int param;
  int level1;
  int level2;
  short ltype;
  short tsteptype;
  char name[32];
  VarScanKeys scanKeys;
  var_tile_t tiles;
} compvar2_t;

static int
gribapi_get_zaxis_type(long editionNumber, int grib_ltype)
{
  return (editionNumber <= 1) ? grib1ltypeToZaxisType(grib_ltype) : grib2ltypeToZaxisType(grib_ltype);
}

static int
get_timeunits(long unitsOfTime)
{
  switch (unitsOfTime)
  {
    case 13: return TUNIT_SECOND;
    case 0: return TUNIT_MINUTE;
    case 1: return TUNIT_HOUR;
    case 10: return TUNIT_3HOURS;
    case 11: return TUNIT_6HOURS;
    case 12: return TUNIT_12HOURS;
    case 2: return TUNIT_DAY;
  }

  return TUNIT_HOUR;
}

static double
timeunits_factor(int timeUnits1, int timeUnits2)
{
  if (timeUnits2 == TUNIT_HOUR)
  {
    switch (timeUnits1)
    {
      case TUNIT_SECOND: return 3600;
      case TUNIT_MINUTE: return 60;
      case TUNIT_HOUR: return 1;
      case TUNIT_3HOURS: return 1.0 / 3;
      case TUNIT_6HOURS: return 1.0 / 6;
      case TUNIT_12HOURS: return 1.0 / 12;
      case TUNIT_DAY: return 1.0 / 24;
    }
  }
  else if (timeUnits2 == TUNIT_MINUTE)
  {
    switch (timeUnits1)
    {
      case TUNIT_SECOND: return 60;
      case TUNIT_MINUTE: return 1;
      case TUNIT_HOUR: return 1.0 / 60;
      case TUNIT_3HOURS: return 1.0 / (3 * 60);
      case TUNIT_6HOURS: return 1.0 / (6 * 60);
      case TUNIT_12HOURS: return 1.0 / (12 * 60);
      case TUNIT_DAY: return 1.0 / (24 * 60);
    }
  }

  return 1;
}

static int
gribapi_get_timeunits(grib_handle *gh)
{
  long unitsOfTime = -1;
  grib_get_long(gh, "indicatorOfUnitOfTimeRange", &unitsOfTime);
  if (unitsOfTime == -1) grib_get_long(gh, "indicatorOfUnitForTimeIncrement", &unitsOfTime);

  if (unitsOfTime != -1)
  {
    long stepUnits = -1;
    grib_get_long(gh, "stepUnits", &stepUnits);
    if (stepUnits == -1 && stepUnits != unitsOfTime) GRIB_CHECK(my_grib_set_long(gh, "stepUnits", unitsOfTime), 0);
  }

  return get_timeunits(unitsOfTime);
}

static void
gribapiGetSteps(grib_handle *gh, int timeunits, int *startStep, int *endStep)
{
  long unitsOfTime;
  int status = grib_get_long(gh, "stepUnits", &unitsOfTime);
  int timeunits2 = (status == 0) ? get_timeunits(unitsOfTime) : timeunits;
  // timeunits2 = gribapi_get_timeunits(gh);

  long lpar;
  status = grib_get_long(gh, "forecastTime", &lpar);
  if (status == 0)
    *startStep = (int) lpar;
  else
  {
    status = grib_get_long(gh, "startStep", &lpar);
    if (status == 0) *startStep = (int) (((double) lpar * timeunits_factor(timeunits, timeunits2)) + 0.5);
  }

  *endStep = *startStep;
  status = grib_get_long(gh, "endStep", &lpar);
  if (status == 0) *endStep = (int) (((double) lpar * timeunits_factor(timeunits, timeunits2)) + 0.5);
  // printf("%d %d %ld %d %d %g\n", *startStep, *endStep, lpar, timeunits, timeunits2, timeunits_factor(timeunits, timeunits2));
}

static CdiDateTime
gribapiGetDataDateTime(grib_handle *gh)
{
  long date;
  GRIB_CHECK(grib_get_long(gh, "dataDate", &date), 0);

  long hour, minute, second;
  GRIB_CHECK(grib_get_long(gh, "hour", &hour), 0);
  GRIB_CHECK(grib_get_long(gh, "minute", &minute), 0);
  GRIB_CHECK(grib_get_long(gh, "second", &second), 0);

  CdiDateTime dt;
  dt.date = cdiDate_set(date);
  dt.time.hour = (short) hour;
  dt.time.minute = (short) minute;
  dt.time.second = (short) second;
  dt.time.ms = 0;

  return dt;
}

static void
gribapiSetDataDateTime(grib_handle *gh, CdiDateTime dataDateTime)
{
  long dataDate = (long) cdiDate_get(dataDateTime.date);
  GRIB_CHECK(my_grib_set_long(gh, "dataDate", (long) dataDate), 0);

  int hour, minute, second, ms;
  cdiTime_decode(dataDateTime.time, &hour, &minute, &second, &ms);
  GRIB_CHECK(my_grib_set_long(gh, "hour", hour), 0);
  GRIB_CHECK(my_grib_set_long(gh, "minute", minute), 0);
  GRIB_CHECK(my_grib_set_long(gh, "second", second), 0);
}

static int
gribapi_get_timeunits_factor(int timeUnits)
{
  static bool lprint = true;
  switch (timeUnits)
  {
    case TUNIT_SECOND: return 1;
    case TUNIT_MINUTE: return 60;
    case TUNIT_HOUR: return 3600;
    case TUNIT_3HOURS: return 10800;
    case TUNIT_6HOURS: return 21600;
    case TUNIT_12HOURS: return 43200;
    case TUNIT_DAY: return 86400;
    default:
      if (lprint)
      {
        Warning("Time unit %d unsupported", timeUnits);
        lprint = false;
      }
  }

  return 0;
}

static CdiDateTime
gribapiGetValidityDateTime(grib_handle *gh, CdiDateTime *sDateTime)
{
  CdiDateTime vDateTime;
  cdiDateTime_init(sDateTime);

  long sigofrtime = 3;
  if (gribEditionNumber(gh) > 1)
    GRIB_CHECK(grib_get_long(gh, "significanceOfReferenceTime", &sigofrtime), 0);
  else
    GRIB_CHECK(grib_get_long(gh, "timeRangeIndicator", &sigofrtime), 0);

  if (sigofrtime
      == 3)  // XXX: This looks like a bug to me, because timeRangeIndicator == 3 does not seem to have the same meaning as
             // significanceOfReferenceTime == 3. I would recommend replacing this condition with `if(!gribapiTimeIsFC())`.
  {
    vDateTime = gribapiGetDataDateTime(gh);
  }
  else
  {
    CdiDateTime rDateTime = gribapiGetDataDateTime(gh);

    int timeUnits = gribapi_get_timeunits(gh);
    int startStep = 0, endStep = 0;
    gribapiGetSteps(gh, timeUnits, &startStep, &endStep);

    if (rDateTime.date.day > 0)
    {
      extern int CGRIBEX_grib_calendar;
      JulianDate julianDate = julianDate_encode(CGRIBEX_grib_calendar, rDateTime);

      int64_t timeUnitFactor = gribapi_get_timeunits_factor(timeUnits);

      // if (startStep > 0)
      {
        JulianDate julianDate2 = julianDate_add_seconds(julianDate, timeUnitFactor * startStep);
        *sDateTime = julianDate_decode(CGRIBEX_grib_calendar, julianDate2);
      }

      rDateTime = julianDate_decode(CGRIBEX_grib_calendar, julianDate_add_seconds(julianDate, timeUnitFactor * endStep));
    }

    vDateTime = rDateTime;
  }

  return vDateTime;
}

static void
grib1_get_level(grib_handle *gh, int *leveltype, int *lbounds, int *level1, int *level2)
{
  *leveltype = 0;
  *lbounds = 0;
  *level1 = 0;
  *level2 = 0;

  long lpar;
  if (!grib_get_long(gh, "indicatorOfTypeOfLevel", &lpar))  // 1 byte
  {
    *leveltype = (int) lpar;

    switch (*leveltype)
    {
      case GRIB1_LTYPE_SIGMA_LAYER:
      case GRIB1_LTYPE_HYBRID_LAYER:
      case GRIB1_LTYPE_LANDDEPTH_LAYER:
      {
        *lbounds = 1;
        break;
      }
    }

    if (*lbounds)
    {
      GRIB_CHECK(grib_get_long(gh, "topLevel", &lpar), 0);  // 1 byte
      if (lpar == GRIB_MISSING_LONG) lpar = 0;
      *level1 = (int) lpar;
      GRIB_CHECK(grib_get_long(gh, "bottomLevel", &lpar), 0);  // 1 byte
      if (lpar == GRIB_MISSING_LONG) lpar = 0;
      *level2 = (int) lpar;
    }
    else
    {
      double dlevel;
      GRIB_CHECK(grib_get_double(gh, "level", &dlevel), 0);  // 2 byte
      if (*leveltype == GRIB1_LTYPE_ISOBARIC) dlevel *= 100;
      if (dlevel < -2.e9 || dlevel > 2.e9) dlevel = 0;
      if (*leveltype == GRIB1_LTYPE_99 || *leveltype == GRIB1_LTYPE_ISOBARIC_PA) *leveltype = GRIB1_LTYPE_ISOBARIC;

      *level1 = (int) dlevel;
      *level2 = 0;
    }
  }
}

static double
grib2ScaleFactor(long factor)
{
  switch (factor)
  {
    case GRIB_MISSING_LONG: return 1;
    case -9: return 1000000000;
    case -8: return 100000000;
    case -7: return 10000000;
    case -6: return 1000000;
    case -5: return 100000;
    case -4: return 10000;
    case -3: return 1000;
    case -2: return 100;
    case -1: return 10;
    case 0: return 1;
    case 1: return 0.1;
    case 2: return 0.01;
    case 3: return 0.001;
    case 4: return 0.0001;
    case 5: return 0.00001;
    case 6: return 0.000001;
    case 7: return 0.0000001;
    case 8: return 0.00000001;
    case 9: return 0.000000001;
    default: return 0;
  }
}

static int
calc_level(int level_sf, long factor, long level)
{
  double result = 0;
  if (level != GRIB_MISSING_LONG) result = (double) level * grib2ScaleFactor(factor);
  if (level_sf) result *= level_sf;
  return (int) result;
}

static void
grib2_get_level(grib_handle *gh, int *leveltype1, int *leveltype2, int *lbounds, int *level1, int *level2, int *level_sf,
                int *level_unit)
{
  *leveltype1 = 0;
  *leveltype2 = -1;
  *lbounds = 0;
  *level1 = 0;
  *level2 = 0;
  *level_sf = 0;
  *level_unit = 0;

  long lpar;
  int status = grib_get_long(gh, "typeOfFirstFixedSurface", &lpar);  // 1 byte
  if (status == 0)
  {
    *leveltype1 = (int) lpar;

    status = grib_get_long(gh, "typeOfSecondFixedSurface", &lpar);  // 1 byte
    /* FIXME: assert(lpar >= INT_MIN && lpar <= INT_MAX) */
    if (status == 0) *leveltype2 = (int) lpar;

    if (*leveltype1 != 255 && *leveltype2 != 255 && *leveltype2 > 0) *lbounds = 1;
    switch (*leveltype1)
    {
      case GRIB2_LTYPE_REFERENCE:
        if (*leveltype2 == 1) *lbounds = 0;
        break;
      case GRIB2_LTYPE_LANDDEPTH:
        *level_sf = 1000;
        *level_unit = CDI_UNIT_M;
        break;
      case GRIB2_LTYPE_ISOBARIC:
        *level_sf = 1000;
        *level_unit = CDI_UNIT_PA;
        break;
      case GRIB2_LTYPE_SIGMA:
        *level_sf = 1000;
        *level_unit = 0;
        break;
    }

    long factor, llevel;
    GRIB_CHECK(grib_get_long(gh, "scaleFactorOfFirstFixedSurface", &factor), 0);  // 1 byte
    GRIB_CHECK(grib_get_long(gh, "scaledValueOfFirstFixedSurface", &llevel), 0);  // 4 byte
    *level1 = calc_level(*level_sf, factor, llevel);

    if (*lbounds)
    {
      GRIB_CHECK(grib_get_long(gh, "scaleFactorOfSecondFixedSurface", &factor), 0);  // 1 byte
      GRIB_CHECK(grib_get_long(gh, "scaledValueOfSecondFixedSurface", &llevel), 0);  // 4 byte
      *level2 = calc_level(*level_sf, factor, llevel);
    }
  }
}

static void
grib_get_level(grib_handle *gh, int *leveltype1, int *leveltype2, int *lbounds, int *level1, int *level2, int *level_sf,
               int *level_unit, var_tile_t *tiles)
{
  if (gribEditionNumber(gh) <= 1)
  {
    grib1_get_level(gh, leveltype1, lbounds, level1, level2);
    *leveltype2 = -1;
    *level_sf = 0;
    *level_unit = 0;
  }
  else
  {
    grib2_get_level(gh, leveltype1, leveltype2, lbounds, level1, level2, level_sf, level_unit);

    // read in tiles attributes (if there are any)
    tiles->tileindex = (int) gribGetLongDefault(gh, cdiSubtypeAttributeName[SUBTYPE_ATT_TILEINDEX], 0);
    tiles->totalno_of_tileattr_pairs
        = (int) gribGetLongDefault(gh, cdiSubtypeAttributeName[SUBTYPE_ATT_TOTALNO_OF_TILEATTR_PAIRS], -1);
    tiles->tileClassification = (int) gribGetLongDefault(gh, cdiSubtypeAttributeName[SUBTYPE_ATT_TILE_CLASSIFICATION], -1);
    tiles->numberOfTiles = (int) gribGetLongDefault(gh, cdiSubtypeAttributeName[SUBTYPE_ATT_NUMBER_OF_TILES], -1);
    tiles->numberOfAttributes = (int) gribGetLongDefault(gh, cdiSubtypeAttributeName[SUBTYPE_ATT_NUMBER_OF_ATTR], -1);
    tiles->attribute = (int) gribGetLongDefault(gh, cdiSubtypeAttributeName[SUBTYPE_ATT_TILEATTRIBUTE], -1);
  }
}

static void
gribapi_get_string(grib_handle *gh, const char *key, char *string, size_t length)
{
  string[0] = 0;

  int ret = grib_get_string(gh, key, string, &length);
  if (ret != 0)
  {
    fprintf(stderr, "grib_get_string(gh, \"%s\", ...) failed!\n", key);
    GRIB_CHECK(ret, 0);
  }
  // clang-format off
  if      (length == 8 && memcmp(string, "unknown", length) == 0) string[0] = 0;
  else if (length == 2 && memcmp(string, "~", length) == 0)       string[0] = 0;
  // clang-format on
}

static void
param_to_name(int param, char *name)
{
  int pdis, pcat, pnum;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);
  if (pdis == 255) { snprintf(name, 256, "code%d", pnum); }
  else { snprintf(name, 256, "param%d.%d.%d", pnum, pcat, pdis); }
}

static int
gribapiGetEnsembleInfo(grib_handle *gh, long *numberOfForecastsInEnsemble, long *perturbationNumber, long *typeOfEnsembleForecast)
{
  int status = 0;
  if (grib_get_long(gh, "numberOfForecastsInEnsemble", numberOfForecastsInEnsemble) == 0)
  {
    if (*numberOfForecastsInEnsemble > 0) status = 1;
    grib_get_long(gh, "perturbationNumber", perturbationNumber);
    grib_get_long(gh, "typeOfEnsembleForecast", typeOfEnsembleForecast);
  }

  if (status == 0)
  {
    *numberOfForecastsInEnsemble = -1;
    *perturbationNumber = -1;
    *typeOfEnsembleForecast = -1;
  }

  return status;
}

static VarScanKeys
gribapiGetScanKeys(grib_handle *gh)
{
  VarScanKeys scanKeys;
  varScanKeysInit(&scanKeys);

  long numberOfForecastsInEnsemble = -1, perturbationNumber = -1, typeOfEnsembleForecast = -1;
  gribapiGetEnsembleInfo(gh, &numberOfForecastsInEnsemble, &perturbationNumber, &typeOfEnsembleForecast);
  scanKeys.perturbationNumber = (short) perturbationNumber;

  long typeOfGeneratingProcess = 0;
  if (grib_get_long(gh, "typeOfGeneratingProcess", &typeOfGeneratingProcess) == 0)
    scanKeys.typeOfGeneratingProcess = (short) typeOfGeneratingProcess;

  return scanKeys;
}

static void
gribapiGetNameKeys(grib_handle *gh, int varID)
{
  char string[CDI_MAX_NAME];
  size_t vlen = CDI_MAX_NAME;
  gribapi_get_string(gh, "name", string, vlen);  // longname
  if (string[0]) varDefKeyString(varID, CDI_KEY_LONGNAME, string);

  gribapi_get_string(gh, "units", string, vlen);
  if (string[0]) varDefKeyString(varID, CDI_KEY_UNITS, string);

  string[0] = 0;
  int status = grib_get_string(gh, "cfName", string, &vlen);
  if (status != 0 || vlen <= 1 || strncmp(string, "unknown", 7) == 0) string[0] = 0;
  if (string[0]) varDefKeyString(varID, CDI_KEY_STDNAME, string);
}

static void
gribapiGetKeys(grib_handle *gh, int varID)
{
  long tablesVersion = 0;
  if (grib_get_long(gh, "tablesVersion", &tablesVersion) == 0) varDefKeyInt(varID, CDI_KEY_TABLESVERSION, (int) tablesVersion);

  long localTablesVersion = 0;
  if (grib_get_long(gh, "localTablesVersion", &localTablesVersion) == 0)
    varDefKeyInt(varID, CDI_KEY_LOCALTABLESVERSION, (int) localTablesVersion);

  long typeOfGeneratingProcess = 0;
  if (grib_get_long(gh, "typeOfGeneratingProcess", &typeOfGeneratingProcess) == 0)
    varDefKeyInt(varID, CDI_KEY_TYPEOFGENERATINGPROCESS, (int) typeOfGeneratingProcess);

  long productDefinitionTemplate = 0;
  if (grib_get_long(gh, "productDefinitionTemplateNumber", &productDefinitionTemplate) == 0)
    varDefKeyInt(varID, CDI_KEY_PRODUCTDEFINITIONTEMPLATE, (int) productDefinitionTemplate);

  long typeOfProcessedData = 0;
  if (grib_get_long(gh, "typeOfProcessedData", &typeOfProcessedData) == 0)
    varDefKeyInt(varID, CDI_KEY_TYPEOFPROCESSEDDATA, (int) typeOfProcessedData);

  long shapeOfTheEarth = 0;
  if (grib_get_long(gh, "shapeOfTheEarth", &shapeOfTheEarth) == 0)
    varDefKeyInt(varID, CDI_KEY_SHAPEOFTHEEARTH, (int) shapeOfTheEarth);

  long backgroundProcess = 0;
  if (grib_get_long(gh, "backgroundProcess", &backgroundProcess) == 0)
    varDefKeyInt(varID, CDI_KEY_BACKGROUNDPROCESS, (int) backgroundProcess);

  long typeOfTimeIncrement = 0;
  if (grib_get_long(gh, "typeOfTimeIncrement", &typeOfTimeIncrement) == 0)
    varDefKeyInt(varID, CDI_KEY_TYPEOFTIMEINCREMENT, (int) typeOfTimeIncrement);
  /*
  long constituentType = 0;
  if ( grib_get_long(gh, "constituentType", &constituentType) == 0 )
    varDefKeyInt(varID, CDI_KEY_CONSTITUENTTYPE, (int) constituentType);
  */

  /*
    Get the ensemble information from the grib-2 Tables and update the intermediate datastructure.
    Further update to the "vlist" is handled in the same way as for GRIB-1 by "cdi_generate_vars"
  */
  long numberOfForecastsInEnsemble = -1, perturbationNumber = -1, typeOfEnsembleForecast = -1;
  gribapiGetEnsembleInfo(gh, &numberOfForecastsInEnsemble, &perturbationNumber, &typeOfEnsembleForecast);
  if (numberOfForecastsInEnsemble > 0)
  {
    varDefKeyInt(varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, (int) numberOfForecastsInEnsemble);
    varDefKeyInt(varID, CDI_KEY_PERTURBATIONNUMBER, (int) perturbationNumber);
    if (typeOfEnsembleForecast != -1) varDefKeyInt(varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, (int) typeOfEnsembleForecast);
  }

  long section2Length = 0;
  int status = grib_get_long(gh, "section2Length", &section2Length);
  if (status == 0 && section2Length > 0)
  {
    long grib2LocalSectionNumber;
    long mpimType, mpimClass, mpimUser;
    status = grib_get_long(gh, "grib2LocalSectionNumber", &grib2LocalSectionNumber);
    if (status == 0)
    {
      size_t section2PaddingLength = 0;
      status = grib_get_size(gh, "section2Padding", &section2PaddingLength);
      if (status == 0 && section2PaddingLength > 0)
      {
        varDefKeyInt(varID, CDI_KEY_GRIB2LOCALSECTIONNUMBER, (int) grib2LocalSectionNumber);
        varDefKeyInt(varID, CDI_KEY_SECTION2PADDINGLENGTH, (int) section2PaddingLength);
        unsigned char *section2Padding = (unsigned char *) Malloc(section2PaddingLength);
        grib_get_bytes(gh, "section2Padding", section2Padding, &section2PaddingLength);
        varDefKeyBytes(varID, CDI_KEY_SECTION2PADDING, section2Padding, (int) section2PaddingLength);
        Free(section2Padding);
      }
      else if (grib_get_long(gh, "mpimType", &mpimType) == 0 && grib_get_long(gh, "mpimClass", &mpimClass) == 0
               && grib_get_long(gh, "mpimUser", &mpimUser) == 0)
      {
        varDefKeyInt(varID, CDI_KEY_MPIMTYPE, (int) mpimType);
        varDefKeyInt(varID, CDI_KEY_MPIMCLASS, (int) mpimClass);
        varDefKeyInt(varID, CDI_KEY_MPIMUSER, (int) mpimUser);

        size_t revNumLen = 20;
        unsigned char revNumber[revNumLen];
        if (grib_get_bytes(gh, "revNumber", revNumber, &revNumLen) == 0)
          varDefKeyBytes(varID, CDI_KEY_REVNUMBER, revNumber, (int) revNumLen);

        long revStatus;
        grib_get_long(gh, "revStatus", &revStatus);
        varDefKeyInt(varID, CDI_KEY_REVSTATUS, (int) revStatus);
      }
    }
  }
}

static void
gribapiInqProjRLL(grib_handle *gh, int gridID)
{
  double xpole = 0, ypole = 0, angle = 0;
  grib_get_double(gh, "latitudeOfSouthernPoleInDegrees", &ypole);
  grib_get_double(gh, "longitudeOfSouthernPoleInDegrees", &xpole);
  grib_get_double(gh, "angleOfRotation", &angle);
  xpole -= 180;
  if (fabs(ypole) > 0) ypole = -ypole;  // change from south to north pole
  if (fabs(angle) > 0) angle = -angle;

  gridDefParamRLL(gridID, xpole, ypole, angle);
}

static void
decode_shapeOfTheEarth(grib_handle *gh, struct CDI_GridProjParams *gpp)
{
  long shapeOfTheEarth = 0;
  grib_get_long(gh, "shapeOfTheEarth", &shapeOfTheEarth);

  double radiusOfTheEarth = 6367470.0;
  if (shapeOfTheEarth == 1) grib_get_double(gh, "radiusOfTheEarth", &radiusOfTheEarth);

  // clang-format off
  switch (shapeOfTheEarth)
    {
    case 0:   gpp->a = radiusOfTheEarth; break;
    case 1:   gpp->a = radiusOfTheEarth; break;
    case 2:   gpp->a = 6378160.0; gpp->b = 6356775.0;   gpp->rf = 297.0; break;
    case 4:   gpp->a = 6378137.0; gpp->b = 6356752.314; gpp->rf = 298.257222101; break;
    case 6:   gpp->a = 6371229.0; break;
    case 8:   gpp->a = 6371200.0; break;
    default:  gpp->a = radiusOfTheEarth;
    }
  // clang-format on
}

static void
gribapiInqProjLCC(grib_handle *gh, int gridID)
{
  struct CDI_GridProjParams gpp;
  gridProjParamsInit(&gpp);

  decode_shapeOfTheEarth(gh, &gpp);

  long projflag = 0;
  grib_get_double(gh, "longitudeOfFirstGridPointInDegrees", &gpp.xval_0);
  grib_get_double(gh, "latitudeOfFirstGridPointInDegrees", &gpp.yval_0);
  grib_get_double(gh, "longitudeOfSouthernPoleInDegrees", &gpp.x_SP);
  grib_get_double(gh, "latitudeOfSouthernPoleInDegrees", &gpp.y_SP);
  grib_get_double(gh, "LoVInDegrees", &gpp.lon_0);
  grib_get_double(gh, "Latin1InDegrees", &gpp.lat_1);
  grib_get_double(gh, "Latin2InDegrees", &gpp.lat_2);
  grib_get_long(gh, "projectionCentreFlag", &projflag);
  bool isSouthPole = gribbyte_get_bit((int) projflag, 1);
  if (isSouthPole && gribEditionNumber(gh) == 1)
  {
    gpp.lat_1 = -gpp.lat_1;
    gpp.lat_2 = -gpp.lat_2;
  }

  gpp.lat_0 = gpp.lat_2;

  if (proj_lonlat_to_lcc_func)
  {
    double x_0 = gpp.xval_0, y_0 = gpp.yval_0;
    proj_lonlat_to_lcc_func(gpp, (size_t) 1, &x_0, &y_0);
    if (IS_NOT_EQUAL(x_0, gpp.mv) && IS_NOT_EQUAL(y_0, gpp.mv))
    {
      gpp.x_0 = -x_0;
      gpp.y_0 = -y_0;
    }
  }

  gridDefParamsLCC(gridID, gpp);
}

static void
gribapiInqProjSTERE(grib_handle *gh, int gridID)
{
  struct CDI_GridProjParams gpp;
  gridProjParamsInit(&gpp);

  decode_shapeOfTheEarth(gh, &gpp);

  grib_get_double(gh, "longitudeOfFirstGridPointInDegrees", &gpp.xval_0);
  grib_get_double(gh, "latitudeOfFirstGridPointInDegrees", &gpp.yval_0);
  grib_get_double(gh, "LaDInDegrees", &gpp.lat_1);
  grib_get_double(gh, "orientationOfTheGridInDegrees", &gpp.lon_0);

  long southPoleOnProjectionPlane;
  grib_get_long(gh, "southPoleOnProjectionPlane", &southPoleOnProjectionPlane);
  gpp.lat_0 = southPoleOnProjectionPlane ? -90.0 : 90.0;

  if (proj_lonlat_to_stere_func)
  {
    double x_0 = gpp.xval_0, y_0 = gpp.yval_0;
    proj_lonlat_to_stere_func(gpp, (size_t) 1, &x_0, &y_0);
    if (IS_NOT_EQUAL(x_0, gpp.mv) && IS_NOT_EQUAL(y_0, gpp.mv))
    {
      gpp.x_0 = -x_0;
      gpp.y_0 = -y_0;
    }
  }

  gridDefParamsSTERE(gridID, gpp);
}

static void
gribapiInqProjHEALPIX(grib_handle *gh, int gridID)
{
  struct CDI_GridProjParams gpp;
  gridProjParamsInit(&gpp);

  decode_shapeOfTheEarth(gh, &gpp);

  long lval = -1;
  grib_get_long(gh, "Nside", &lval);
  gpp.nside = (int) lval;
  lval = -1;
  grib_get_long(gh, "ordering", &lval);
  gpp.order = (int) lval;

  gridDefParamsHEALPIX(gridID, gpp);
}

static void
gribapiInqGridHEALPIX(grib_handle *gh, int gridID)
{
  long lval = -1;
  grib_get_long(gh, "Nside", &lval);
  int nside = (int) lval;
  lval = -1;
  grib_get_long(gh, "ordering", &lval);
  int order = (int) lval;

  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, "healpix");

  const char *gmapname = "healpix";
  cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname);
  cdiDefAttTxt(gridID, CDI_GLOBAL, "grid_mapping_name", (int) (strlen(gmapname)), gmapname);

  int refinementLevel = log2(nside);
  cdiDefAttInt(gridID, CDI_GLOBAL, "refinement_level", CDI_DATATYPE_INT32, 1, &refinementLevel);
  const char *orderName = (order == 1) ? "nested" : "ring";
  cdiDefAttTxt(gridID, CDI_GLOBAL, "indexing_scheme", (int) (strlen(orderName)), orderName);
}

static void
gribapiAddRecord(stream_t *streamptr, int param, grib_handle *gh, size_t recsize, off_t position, int datatype, int comptype,
                 const char *varname, int leveltype1, int leveltype2, int lbounds, int level1, int level2, int level_sf,
                 int level_unit, VarScanKeys *scanKeys, const var_tile_t *tiles, bool lread_additional_keys, int fdbItemIndex)
{
  int vlistID = streamptr->vlistID;
  int tsID = streamptr->curTsID;
  int recID = recordNewEntry(streamptr, tsID);
  recinfo_t *recinfo = &(streamptr->tsteps[tsID].recinfo[recID]);
  record_t *record = &(streamptr->tsteps[tsID].records[recID]);

  int tsteptype = gribapiGetTsteptype(gh);

  // fprintf(stderr, "param %d %d %d %d\n", param, level1, level2, leveltype1);

  record->size = recsize;
  record->position = position;
  record->param = param;
  record->ilevel = level1;
  record->ilevel2 = level2;
  record->ltype = (short) leveltype1;
  record->tsteptype = (short) tsteptype;
  record->gridsize = gribapiGetGridsize(gh);
  record->scanKeys = *scanKeys;
  record->tiles = tiles ? *tiles : dummy_tiles;
#ifdef HAVE_LIBFDB5
  record->fdbItemIndex = fdbItemIndex;
#else
  (void) fdbItemIndex;
#endif

  strncpy(record->varname, varname, sizeof(record->varname) - 1);
  record->varname[sizeof(record->varname) - 1] = 0;

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;

  // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  struct CdiQuery *query = streamptr->query;
  if (query && cdiQueryName(query, varname) < 0)
  {
    recinfo->used = false;
    return;
  }

  // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  grid_t *gridptr = (grid_t *) Malloc(sizeof(*gridptr));
  bool uvRelativeToGrid = gribapiGetGrid(gh, gridptr);

  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, gridptr, 0);
  int gridID = gridAdded.Id;
  if (!gridAdded.isNew)
  {
    grid_free(gridptr);
    Free(gridptr);
  }
  else if (gridptr->projtype == CDI_PROJ_RLL) { gribapiInqProjRLL(gh, gridID); }
  else if (gridptr->projtype == CDI_PROJ_LCC) { gribapiInqProjLCC(gh, gridID); }
  else if (gridptr->projtype == CDI_PROJ_STERE) { gribapiInqProjSTERE(gh, gridID); }
  else if (gridptr->projtype == CDI_PROJ_HEALPIX) { gribapiInqProjHEALPIX(gh, gridID); }
  else if (gridptr->type == GRID_HEALPIX) { gribapiInqGridHEALPIX(gh, gridID); }

  int zaxistype = gribapi_get_zaxis_type(gribEditionNumber(gh), leveltype1);

  switch (zaxistype)
  {
    case ZAXIS_HYBRID:
    case ZAXIS_HYBRID_HALF:
    {
      long lpar;
      GRIB_CHECK(grib_get_long(gh, "NV", &lpar), 0);
      /* FIXME: assert(lpar >= 0) */
      size_t vctsize = (size_t) lpar;
      if (vctsize > 0)
      {
        double *vctptr = (double *) Malloc(vctsize * sizeof(double));
        size_t dummy = vctsize;
        GRIB_CHECK(grib_get_double_array(gh, "pv", vctptr, &dummy), 0);
        varDefVCT(vctsize, vctptr);
        Free(vctptr);
      }
      break;
    }
    case ZAXIS_REFERENCE:
    {
      unsigned char uuid[CDI_UUID_SIZE];
      long lpar;
      GRIB_CHECK(grib_get_long(gh, "NV", &lpar), 0);
      // if (lpar != 6) fprintf(stderr, "Warning ...\n");
      GRIB_CHECK(grib_get_long(gh, "nlev", &lpar), 0);
      int nhlev = (int) lpar;
      GRIB_CHECK(grib_get_long(gh, "numberOfVGridUsed", &lpar), 0);
      int nvgrid = (int) lpar;
      size_t len = (size_t) CDI_UUID_SIZE;
      memset(uuid, 0, CDI_UUID_SIZE);
      GRIB_CHECK(grib_get_bytes(gh, "uuidOfVGrid", uuid, &len), 0);
      varDefZAxisReference(nhlev, nvgrid, uuid);
      break;
    }
  }

  // if ( datatype > 32 ) datatype = CDI_DATATYPE_PACK32;
  if (datatype < 0) datatype = CDI_DATATYPE_PACK;

  // add the previously read record data to the (intermediate) list of records
  int tile_index = 0;
  int varID = 0, levelID = 0;
  varAddRecord(recID, param, gridID, zaxistype, lbounds, level1, level2, level_sf, level_unit, datatype, &varID, &levelID,
               tsteptype, leveltype1, leveltype2, varname, scanKeys, tiles, &tile_index);

  recinfo->varID = (short) varID;
  recinfo->levelID = levelID;

  varDefCompType(varID, comptype);

  if (uvRelativeToGrid) varDefKeyInt(varID, CDI_KEY_UVRELATIVETOGRID, 1);

  if (varname[0]) gribapiGetNameKeys(gh, varID);
  gribapiGetKeys(gh, varID);

  if (lread_additional_keys)
  {
    long lval;
    double dval;

    // Handle individual long and double keys
    for (int i = 0; i < cdiNAdditionalGRIBKeys; i++)
    {
      // Handling individual integer values
      if (grib_get_long(gh, cdiAdditionalGRIBKeys[i], &lval) == 0)
        varDefOptGribInt(varID, tile_index, lval, cdiAdditionalGRIBKeys[i]);

      // Handling individual double values
      if (grib_get_double(gh, cdiAdditionalGRIBKeys[i], &dval) == 0)
        varDefOptGribDbl(varID, tile_index, dval, cdiAdditionalGRIBKeys[i]);

      // Handling long arrays
      long *larr = NULL;
      size_t arr_len = 0;
      if (grib_get_long_array(gh, cdiAdditionalGRIBKeys[i], NULL, &arr_len) == 0)
      {
        // Now that we know the array length, allocate memory
        larr = (long *) Malloc(arr_len * sizeof(long));
        if (grib_get_long_array(gh, cdiAdditionalGRIBKeys[i], larr, &arr_len) == 0)
        {
          varDefOptGribIntArr(varID, tile_index, larr, arr_len, cdiAdditionalGRIBKeys[i]);
        }
        Free(larr);  // Free the long array after use
      }

      // Handling double arrays
      double *darr = NULL;
      size_t darr_len = 0;
      if (grib_get_double_array(gh, cdiAdditionalGRIBKeys[i], NULL, &darr_len) == 0)
      {
        // Allocate memory for the double array
        darr = (double *) Malloc(darr_len * sizeof(double));
        if (grib_get_double_array(gh, cdiAdditionalGRIBKeys[i], darr, &darr_len) == 0)
        {
          varDefOptGribDblArr(varID, tile_index, darr, darr_len, cdiAdditionalGRIBKeys[i]);
        }
        Free(darr);  // Free the double array after use
      }
    }
  }

  if (varInqInst(varID) == CDI_UNDEFID)
  {
    long center, subcenter;
    GRIB_CHECK(grib_get_long(gh, "centre", &center), 0);
    GRIB_CHECK(grib_get_long(gh, "subCentre", &subcenter), 0);
    int instID = institutInq((int) center, (int) subcenter, NULL, NULL);
    if (instID == CDI_UNDEFID) instID = institutDef((int) center, (int) subcenter, NULL, NULL);
    varDefInst(varID, instID);
  }

  if (varInqModel(varID) == CDI_UNDEFID)
  {
    long processID;
    if (grib_get_long(gh, "generatingProcessIdentifier", &processID) == 0)
    {
      /* FIXME: assert(processID >= INT_MIN && processID <= INT_MAX) */
      int modelID = modelInq(varInqInst(varID), (int) processID, NULL);
      if (modelID == CDI_UNDEFID) modelID = modelDef(varInqInst(varID), (int) processID, NULL);
      varDefModel(varID, modelID);
    }
  }

  if (varInqTable(varID) == CDI_UNDEFID)
  {
    int pdis, pcat, pnum;
    cdiDecodeParam(param, &pnum, &pcat, &pdis);

    if (pdis == 255)
    {
      int tabnum = pcat;
      int tableID = tableInq(varInqModel(varID), tabnum, NULL);
      if (tableID == CDI_UNDEFID) tableID = tableDef(varInqModel(varID), tabnum, NULL);
      varDefTable(varID, tableID);
    }
  }

  if (CDI_Debug)
    Message("varID = %d  param = %d  zaxistype = %d  gridID = %d  levelID = %d", varID, param, zaxistype, gridID, levelID);
}

static compvar2_t
gribapiVarSet(int param, int level1, int level2, int leveltype, int tsteptype, size_t gridsize, char *name, VarScanKeys scanKeys,
              var_tile_t tiles_data)
{
  compvar2_t compVar;
  memset(&compVar, 0, sizeof(compvar2_t));
  size_t maxlen = sizeof(compVar.name);
  size_t len = strlen(name);
  if (len > maxlen) len = maxlen;

  compVar.param = param;
  compVar.level1 = level1;
  compVar.level2 = level2;
  compVar.ltype = leveltype;
  compVar.tsteptype = tsteptype;
  compVar.gridsize = gridsize;
  // memset(compVar.name, 0, maxlen);
  memcpy(compVar.name, name, len);
  compVar.scanKeys = scanKeys;
  compVar.tiles = tiles_data;

  return compVar;
}

static int
gribapiVarCompare(const compvar2_t *compVar, const record_t *record, int flag)
{
  compvar2_t compVar0;
  memset(&compVar0, 0, sizeof(compvar2_t));
  compVar0.param = record->param;
  compVar0.level1 = record->ilevel;
  compVar0.level2 = record->ilevel2;
  compVar0.ltype = (short) record->ltype;
  compVar0.tsteptype = (short) record->tsteptype;
  compVar0.gridsize = record->gridsize;
  memcpy(compVar0.name, record->varname, sizeof(compVar->name));

  if (flag == 0)
  {
    if (compVar0.tsteptype == TSTEP_INSTANT && compVar->tsteptype == TSTEP_INSTANT3) compVar0.tsteptype = TSTEP_INSTANT3;
    if (compVar0.tsteptype == TSTEP_INSTANT3 && compVar->tsteptype == TSTEP_INSTANT) compVar0.tsteptype = TSTEP_INSTANT;
  }

  compVar0.scanKeys = record->scanKeys;
  compVar0.tiles = record->tiles;

  // printf("var1: level1=%d level2=%d\n", compVar.level1, compVar.level2);
  // printf("var2: level1=%d level2=%d\n", compVar0.level1, compVar0.level2);

  return memcmp(&compVar0, compVar, sizeof(compvar2_t));
}

static grib_handle *
gribapiGetDiskRepresentation(size_t recsize, size_t *buffersize, void **gribbuffer, int *outDatatype, int *outCompressionType)
{
  int gribversion = (int) ((char *) *gribbuffer)[7];
  if (gribversion <= 1) *outCompressionType = grbDecompress(recsize, buffersize, gribbuffer);

  grib_handle *gh = grib_handle_new_from_message(NULL, *gribbuffer, recsize);

  bool lieee = false;

  if (gribversion > 1)
  {
    size_t len = 256;
    char typeOfPacking[256];
    if (grib_get_string(gh, "packingType", typeOfPacking, &len) == 0)
    {
      // fprintf(stderr, "packingType %zu %s\n", len, typeOfPacking);
      if (strncmp(typeOfPacking, "grid_jpeg", len) == 0) { *outCompressionType = CDI_COMPRESS_JPEG; }
      else if (strncmp(typeOfPacking, "grid_ccsds", len) == 0) { *outCompressionType = CDI_COMPRESS_AEC; }
      else if (strncmp(typeOfPacking, "grid_ieee", len) == 0) { lieee = true; }
    }
  }

  if (lieee)
  {
    long precision;
    int status = grib_get_long(gh, "precision", &precision);
    *outDatatype = (status == 0 && precision == 1) ? CDI_DATATYPE_FLT32 : CDI_DATATYPE_FLT64;
  }
  else
  {
    *outDatatype = CDI_DATATYPE_PACK;
    long bitsPerValue;
    if (grib_get_long(gh, "bitsPerValue", &bitsPerValue) == 0)
    {
      if (bitsPerValue > 0 && bitsPerValue <= 32) *outDatatype = (int) bitsPerValue;
    }
  }

  return gh;
}

typedef enum
{
  CHECKTIME_OK,
  CHECKTIME_SKIP,
  CHECKTIME_STOP,
  CHECKTIME_INCONSISTENT
} checkTimeResult;

static checkTimeResult
checkTime(stream_t *streamptr, const compvar2_t *compVar, CdiDateTime verificationTime, CdiDateTime expectedVTime)
{
  // First determine whether the current record exists already.
  int recID = 0;
  for (; recID < streamptr->nrecs; recID++)
  {
    if (gribapiVarCompare(compVar, &streamptr->tsteps[0].records[recID], 1) == 0) break;
  }
  bool recordExists = (recID < streamptr->nrecs);

  // Then we need to know whether the verification time is consistent.
  bool consistentTime = cdiDateTime_isEQ(verificationTime, expectedVTime);

  // Finally, we make a decision.
  if (CDI_Inventory_Mode == 1)
  {
    if (recordExists) return CHECKTIME_STOP;
    if (!consistentTime) return CHECKTIME_INCONSISTENT;
  }
  else
  {
    if (!consistentTime) return CHECKTIME_STOP;
    if (recordExists) return CHECKTIME_SKIP;
  }

  return CHECKTIME_OK;
}

#define gribWarning(text, nrecs, timestep, varname, param, level1, level2)                                                    \
  do {                                                                                                                        \
    char paramstr[32];                                                                                                        \
    cdiParamToString(param, paramstr, sizeof(paramstr));                                                                      \
    Warning("Record %2d (name=%s id=%s lev1=%d lev2=%d) timestep %d: %s", nrecs, varname, paramstr, level1, level2, timestep, \
            text);                                                                                                            \
  } while (0)

#ifdef HAVE_LIBFDB5

int
fdbScanTimesteps(stream_t *streamptr)
{
  void *gribbuffer = NULL;
  size_t buffersize = 0;
  grib_handle *gh = NULL;

  fdb_handle_t *fdbHandle = streamptr->protocolData;

  fdb_request_t *request = cdi_create_fdb_request(streamptr->filename);

  fdbKeyValueEntry *keyValueList = NULL;
  int numItems = cdi_fdb_fill_kvlist(fdbHandle, request, &keyValueList);
  fdb_delete_request(request);
  if (numItems == 0) Error("FDB request doesn't find any database entries!");
  if (CDI_Debug)
  {
    printf("Original FDB items:\n");
    print_keyvalueList(numItems, keyValueList);
  }

  // if (check_keyvalueList(numItems, keyValueList) != 0) return CDI_EUFSTRUCT;

  RecordInfoEntry *recordInfoList = (RecordInfoEntry *) malloc(numItems * sizeof(RecordInfoEntry));
  if (decode_keyvalue(numItems, keyValueList, recordInfoList) != 0) return CDI_EUFSTRUCT;

  cdi_fdb_sort_datetime(numItems, recordInfoList);

  if (CDI_Debug)
  {
    printf("Sorted FDB items:\n");
    print_keyvalueList_sorted(numItems, keyValueList, recordInfoList);
  }

  int numRecords = get_num_records(numItems, recordInfoList);
  if (numRecords == 0) return CDI_EUFSTRUCT;

  int numTimesteps = numItems / numRecords;
  if (CDI_Debug) Message("numRecords=%d  numTimesteps=%d", numRecords, numTimesteps);

  int *timestepRecordOffset = (int *) malloc(numTimesteps * sizeof(int));
  for (int i = 0; i < numTimesteps; i++) timestepRecordOffset[i] = i * numRecords;
  numTimesteps = remove_duplicate_timesteps(recordInfoList, numRecords, numTimesteps, timestepRecordOffset);
  if (CDI_Debug) Message("numRecords=%d  numTimesteps=%d", numRecords, numTimesteps);
  // Message("numRecords=%d  numTimesteps=%d", numRecords, numTimesteps);

  // CdiDateTime vDateTime0;
  // cdiDateTime_init(&vDateTime0);
  int fcast = 0;

  streamptr->curTsID = 0;

  int tsIDnew = tstepsNewEntry(streamptr);
  if (tsIDnew != 0) Error("Internal problem! tstepsNewEntry returns %d", tsIDnew);

  taxis_t *taxis = &streamptr->tsteps[tsIDnew].taxis;

  for (int recID = 0; recID < numRecords; recID++)
  {
    int fdbItem = recordInfoList[recID].fdbIndex;
    int vdate = recordInfoList[recID].date;
    int vtime = recordInfoList[recID].time * 100;

    long recsize = cdi_fdb_read_record(fdbHandle, &keyValueList[fdbItem], &buffersize, &gribbuffer);

    int datatype, comptype = 0;
    gh = gribapiGetDiskRepresentation(recsize, &buffersize, &gribbuffer, &datatype, &comptype);

    GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

    int leveltype1 = -1, leveltype2 = -1, lbounds, level_sf, level_unit;
    var_tile_t tiles = dummy_tiles;
    int level1 = 0, level2 = 0;
    grib_get_level(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

    char varname[256];
    gribapi_get_string(gh, "shortName", varname, sizeof(varname));

    int param = gribapiGetParam(gh);

    CdiDateTime sDateTime;
    // CdiDateTime vDateTime = gribapiGetValidityDateTime(gh, &sDateTime);
    CdiDateTime vDateTime = cdiDateTime_set(vdate, vtime);
    sDateTime = vDateTime;

    VarScanKeys scanKeys = gribapiGetScanKeys(gh);

    if (recID == 0)
    {
      // vDateTime0 = vDateTime;
      taxis->rDateTime = gribapiGetDataDateTime(gh);
      fcast = gribapiTimeIsFC(gh);
      if (fcast) taxis->unit = gribapi_get_timeunits(gh);
      taxis->fDateTime = taxis->rDateTime;
      taxis->sDateTime = sDateTime;
      taxis->vDateTime = vDateTime;
    }

    if (CDI_Debug)
    {
      char paramstr[32];
      cdiParamToString(param, paramstr, sizeof(paramstr));
      Message("%4d name=%s id=%s ltype=%d lev1=%d lev2=%d vDateTime=%s", recID + 1, varname, paramstr, leveltype1, level1, level2,
              CdiDateTime_string(vDateTime));
    }

    var_tile_t *ptiles = memcmp(&tiles, &dummy_tiles, sizeof(var_tile_t)) ? &tiles : NULL;
    int recpos = 0;
    gribapiAddRecord(streamptr, param, gh, recsize, recpos, datatype, comptype, varname, leveltype1, leveltype2, lbounds, level1,
                     level2, level_sf, level_unit, &scanKeys, ptiles, true, fdbItem);

    grib_handle_delete(gh);
    gh = NULL;
  }

  if (gh) grib_handle_delete(gh);

  cdi_generate_vars(streamptr);

  taxis->type = fcast ? TAXIS_RELATIVE : TAXIS_ABSOLUTE;
  int taxisID = taxisCreate(taxis->type);

  vlistDefTaxis(streamptr->vlistID, taxisID);

  streamScanResizeRecords1(streamptr);

  streamptr->record->buffer = gribbuffer;
  streamptr->record->buffersize = buffersize;

  if (numTimesteps == 1) streamptr->ntsteps = 1;
  streamScanTimeConstAdjust(streamptr, taxis);

  for (int tsID = 1; tsID < numTimesteps; tsID++)
  {
    int recordOffset = timestepRecordOffset[tsID];
    int vdate = recordInfoList[recordOffset].date;
    int vtime = recordInfoList[recordOffset].time * 100;
    // printf("timestep=%d recOffset=%d date=%d time=%d\n", tsID + 1, recordOffset, vdate, vtime);

    int tsIDnext = tstepsNewEntry(streamptr);
    if (tsIDnext != tsID) Error("Internal error. tsID = %d", tsID);

    streamptr->tsteps[tsID - 1].next = true;
    streamptr->tsteps[tsID].position = 0;

    taxis = &streamptr->tsteps[tsID].taxis;

    cdi_create_records(streamptr, tsID, true);
    recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
    record_t *records = streamptr->tsteps[tsID].records;

    int nrecs = (tsID == 1) ? streamScanInitRecords2(streamptr) : streamScanInitRecords(streamptr, tsID);
    if (nrecs != numRecords) Error("Internal error. nrecs = %d", nrecs);

    taxis->vDateTime = cdiDateTime_set(vdate, vtime);

    int rindex = 0;
    for (int recID = 0; recID < numRecords; recID++)
    {
      recinfo[recID].used = true;
      streamptr->tsteps[tsID].recIDs[rindex] = recID;
      rindex++;

      records[recID].position = 0;
      records[recID].size = 0;
      records[recID].fdbItemIndex = recordInfoList[recordOffset + recID].fdbIndex;
    }

    if (tsID == 1) streamptr->tsteps[1].nrecs = numRecords;
  }

  streamptr->rtsteps = numTimesteps;
  streamptr->ntsteps = numTimesteps;

  streamptr->fdbNumItems = numItems;
  streamptr->fdbKeyValueList = keyValueList;

  if (recordInfoList) free(recordInfoList);
  if (timestepRecordOffset) free(timestepRecordOffset);

  return 0;
}
#endif

/*
static int
records_cmp_varname(const void *s1, const void *s2)
{
  const record_t *x = (const record_t *) s1, *y = (const record_t *) s2;
  return strcmp(x->varname, y->varname);
}

static void
sort_records(stream_t *streamptr)
{
  record_t *records = streamptr->tsteps[0].records;
  size_t numRecords = (size_t) streamptr->tsteps[0].recordSize;
  qsort(records, numRecords, sizeof(records[0]), records_cmp_varname);
}
*/

long
gribapiScanTimestep1(stream_t *streamptr)
{
  CdiDateTime vDateTime0;
  cdiDateTime_init(&vDateTime0);
  off_t recpos = 0;
  void *gribbuffer = NULL;
  size_t buffersize = 0;
  int nrecsScanned = 0;  // Only used for debug output.
  bool warn_time = true;
  int fcast = 0;
  grib_handle *gh = NULL;

  streamptr->curTsID = 0;

  int tsID = tstepsNewEntry(streamptr);
  if (tsID != 0) Error("Internal problem! tstepsNewEntry returns %d", tsID);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  int fileID = streamptr->fileID;

  unsigned nrecs = 0;
  while (true)
  {
    size_t recsize = gribGetSize(fileID);
    recpos = fileGetPos(fileID);

    if (recsize == 0)
    {
      streamptr->ntsteps = 1;
      break;
    }

    grib_check_recsize(2, recsize);
    ensureBufferSize(recsize, &buffersize, &gribbuffer);

    size_t readsize = recsize;
    // Search for next 'GRIB', read the following record, and position file offset after it.
    if (gribRead(fileID, gribbuffer, &readsize)) break;

    nrecsScanned++;

    int datatype, comptype = 0;
    gh = gribapiGetDiskRepresentation(recsize, &buffersize, &gribbuffer, &datatype, &comptype);

    GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

    int leveltype1 = -1, leveltype2 = -1, lbounds, level_sf, level_unit;
    var_tile_t tiles = dummy_tiles;
    int level1 = 0, level2 = 0;
    grib_get_level(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

    char varname[256];
    gribapi_get_string(gh, "shortName", varname, sizeof(varname));

    int param = gribapiGetParam(gh);
    if (!varname[0]) param_to_name(param, varname);

    CdiDateTime sDateTime;
    CdiDateTime vDateTime = gribapiGetValidityDateTime(gh, &sDateTime);

    VarScanKeys scanKeys = gribapiGetScanKeys(gh);

    if (nrecs == 0)
    {
      vDateTime0 = vDateTime;
      taxis->rDateTime = gribapiGetDataDateTime(gh);
      fcast = gribapiTimeIsFC(gh);
      if (fcast) taxis->unit = gribapi_get_timeunits(gh);
      taxis->fDateTime = taxis->rDateTime;
      taxis->sDateTime = sDateTime;
      taxis->vDateTime = vDateTime;
    }
    else
    {
      if (cdiDateTime_isLT(sDateTime, taxis->sDateTime)) taxis->sDateTime = sDateTime;

      int tsteptype = gribapiGetTsteptype(gh);
      size_t gridsize = gribapiGetGridsize(gh);
      compvar2_t compVar = gribapiVarSet(param, level1, level2, leveltype1, tsteptype, gridsize, varname, scanKeys, tiles);
      checkTimeResult result = checkTime(streamptr, &compVar, vDateTime, vDateTime0);
      if (result == CHECKTIME_STOP)
      {
        nrecsScanned--;
        break;
      }
      else if (result == CHECKTIME_SKIP)
      {
        gribWarning("Parameter already exist, skipped!", nrecsScanned, tsID + 1, varname, param, level1, level2);
        continue;
      }
      else if (result == CHECKTIME_INCONSISTENT && warn_time)
      {
        gribWarning("Inconsistent verification time!", nrecsScanned, tsID + 1, varname, param, level1, level2);
        warn_time = false;
      }
      assert(result == CHECKTIME_OK || result == CHECKTIME_INCONSISTENT);
    }

    nrecs++;

    if (CDI_Debug)
    {
      char paramstr[32];
      cdiParamToString(param, paramstr, sizeof(paramstr));
      Message("%4u %8d name=%s id=%s ltype=%d lev1=%d lev2=%d vDateTime=%s", nrecs, (int) recpos, varname, paramstr, leveltype1,
              level1, level2, CdiDateTime_string(vDateTime));
    }

    var_tile_t *ptiles = memcmp(&tiles, &dummy_tiles, sizeof(var_tile_t)) ? &tiles : NULL;
    gribapiAddRecord(streamptr, param, gh, recsize, recpos, datatype, comptype, varname, leveltype1, leveltype2, lbounds, level1,
                     level2, level_sf, level_unit, &scanKeys, ptiles, true, -1);

    grib_handle_delete(gh);
    gh = NULL;
  }

  if (gh) grib_handle_delete(gh);

  streamptr->rtsteps = 1;

  if (nrecs == 0) return CDI_EUFSTRUCT;

  if (streamptr->query)
  {
    int numEntries = cdiQueryNumEntries(streamptr->query);
    int numEntriesFound = cdiQueryNumEntriesFound(streamptr->query);
    cdiQueryPrintEntriesNotFound(streamptr->query);
    if (numEntriesFound == 0 || (CDI_Query_Abort && numEntries != numEntriesFound)) return CDI_EQENF;
  }

  cdi_generate_vars(streamptr);

  taxis->type = fcast ? TAXIS_RELATIVE : TAXIS_ABSOLUTE;
  int taxisID = taxisCreate(taxis->type);

  int vlistID = streamptr->vlistID;
  vlistDefTaxis(vlistID, taxisID);

  streamScanResizeRecords1(streamptr);

  streamptr->record->buffer = gribbuffer;
  streamptr->record->buffersize = buffersize;

  streamScanTsFixNtsteps(streamptr, recpos);
  streamScanTimeConstAdjust(streamptr, taxis);

  // if (streamptr->sortname) sort_records(streamptr);

  return 0;
}

long
gribapiScanTimestep2(stream_t *streamptr)
{
  CdiDateTime vDateTime0;
  cdiDateTime_init(&vDateTime0);
  int rstatus = 0;
  off_t recpos = 0;
  // int gridID;
  int recID;
  grib_handle *gh = NULL;

  streamptr->curTsID = 1;

  int fileID = streamptr->fileID;
  int vlistID = streamptr->vlistID;

  void *gribbuffer = streamptr->record->buffer;
  size_t buffersize = streamptr->record->buffersize;

  int tsID = streamptr->rtsteps;
  if (tsID != 1) Error("Internal problem! unexpected timestep %d", tsID + 1);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

  cdi_create_records(streamptr, tsID, true);
  recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
  record_t *records = streamptr->tsteps[tsID].records;

  int nrecords = streamScanInitRecords2(streamptr);

  int nrecsScanned = nrecords;  // Only used for debug output
  for (int rindex = 0; rindex <= nrecords; ++rindex)
  {
    size_t recsize = gribGetSize(fileID);
    recpos = fileGetPos(fileID);
    if (recsize == 0)
    {
      streamptr->ntsteps = 2;
      break;
    }

    grib_check_recsize(2, recsize);
    ensureBufferSize(recsize, &buffersize, &gribbuffer);

    size_t readsize = recsize;
    if (gribRead(fileID, gribbuffer, &readsize)) break;

    grbDecompress(recsize, &buffersize, &gribbuffer);

    nrecsScanned++;
    gh = grib_handle_new_from_message(NULL, gribbuffer, recsize);
    GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

    int level1 = 0, level2 = 0, leveltype1, leveltype2, lbounds, level_sf, level_unit;
    var_tile_t tiles = dummy_tiles;
    grib_get_level(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

    char varname[256];
    gribapi_get_string(gh, "shortName", varname, sizeof(varname));

    int param = gribapiGetParam(gh);
    if (!varname[0]) param_to_name(param, varname);

    CdiDateTime sDateTime;
    CdiDateTime vDateTime = gribapiGetValidityDateTime(gh, &sDateTime);

    if (rindex == 0)
    {
      vDateTime0 = vDateTime;
      int taxisID = vlistInqTaxis(vlistID);
      if (taxisInqType(taxisID) == TAXIS_RELATIVE)
      {
        taxis->type = TAXIS_RELATIVE;
        taxis->unit = gribapi_get_timeunits(gh);
        taxis->rDateTime = gribapiGetDataDateTime(gh);
      }
      else { taxis->type = TAXIS_ABSOLUTE; }
      taxis->fDateTime = taxis->rDateTime;
      taxis->vDateTime = vDateTime;
      taxis->sDateTime = sDateTime;
    }
    else
    {
      if (cdiDateTime_isLT(sDateTime, taxis->sDateTime)) taxis->sDateTime = sDateTime;
    }

    VarScanKeys scanKeys = gribapiGetScanKeys(gh);

    int tsteptype = gribapiGetTsteptype(gh);
    size_t gridsize = gribapiGetGridsize(gh);
    compvar2_t compVar = gribapiVarSet(param, level1, level2, leveltype1, tsteptype, gridsize, varname, scanKeys, tiles);

    for (recID = 0; recID < nrecords; recID++)
      if (gribapiVarCompare(&compVar, &records[recID], 0) == 0) break;

    if (recID == nrecords)
    {
      if (CDI_Inventory_Mode == 1)
      {
        gribWarning("Parameter not defined at timestep 1!", nrecsScanned, tsID + 1, varname, param, level1, level2);
        return CDI_EUFSTRUCT;
      }
      else
      {
        gribWarning("Parameter not defined at timestep 1, skipped!", nrecsScanned, tsID + 1, varname, param, level1, level2);
        continue;
      }
    }

    if (recinfo[recID].used)
    {
      if (CDI_Inventory_Mode == 1)
        break;
      else
      {
        if (cdiDateTime_isNE(vDateTime, vDateTime0)) break;

        gribWarning("Parameter already exist, skipped!", nrecsScanned, tsID + 1, varname, param, level1, level2);
        continue;
      }
    }

    recinfo[recID].used = true;
    streamptr->tsteps[tsID].recIDs[rindex] = recID;

    if (CDI_Debug)
    {
      char paramstr[32];
      cdiParamToString(param, paramstr, sizeof(paramstr));
      Message("%4d %8lld name=%s id=%s ltype=%d lev1=%d lev2=%d vDateTime=%s", nrecsScanned, (long long) recpos, varname, paramstr,
              leveltype1, level1, level2, CdiDateTime_string(vDateTime));
    }

    if (gribapiVarCompare(&compVar, &records[recID], 0) != 0)
    {
      Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
              records[recID].ilevel, level1);
      return CDI_EUFSTRUCT;
    }

    records[recID].position = recpos;
    records[recID].size = recsize;

    int varID = recinfo[recID].varID;

    if (tsteptype != vlistInqVarTsteptype(vlistID, varID)) vlistDefVarTsteptype(vlistID, varID, tsteptype);

    grib_handle_delete(gh);
    gh = NULL;
  }

  if (gh) grib_handle_delete(gh);

  int nrecs = 0;
  for (recID = 0; recID < nrecords; recID++)
  {
    if (recinfo[recID].used)
      nrecs++;
    else
      vlistDefVarTimetype(vlistID, recinfo[recID].varID, TIME_CONSTANT);
  }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  streamptr->record->buffer = gribbuffer;
  streamptr->record->buffersize = buffersize;

  return rstatus;
}

long
gribapiScanTimestep(stream_t *streamptr)
{
  int vrecID, recID = -1;
  int nrecs = 0;
  int vlistID = streamptr->vlistID;
  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if (streamptr->tsteps[tsID].recordSize == 0)
  {
    void *gribbuffer = streamptr->record->buffer;
    size_t buffersize = streamptr->record->buffersize;

    cdi_create_records(streamptr, tsID, true);
    recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
    record_t *records = streamptr->tsteps[tsID].records;

    nrecs = streamScanInitRecords(streamptr, tsID);

    int fileID = streamptr->fileID;

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

    int nrecsScanned = streamptr->tsteps[0].nallrecs + streamptr->tsteps[1].nrecs * (tsID - 1);  // Only used for debug output.
    off_t recpos = 0;
    CdiDateTime vDateTime0;
    cdiDateTime_init(&vDateTime0);
    grib_handle *gh = NULL;
    char varname[256];
    for (int rindex = 0; rindex <= nrecs; ++rindex)
    {
      varname[0] = 0;
      size_t recsize = gribGetSize(fileID);
      recpos = fileGetPos(fileID);
      if (recsize == 0)
      {
        streamptr->ntsteps = streamptr->rtsteps + 1;
        break;
      }

      grib_check_recsize(2, recsize);
      ensureBufferSize(recsize, &buffersize, &gribbuffer);

      size_t readsize = recsize;
      if (gribRead(fileID, gribbuffer, &readsize))
      {
        Warning("Inconsistent timestep %d (GRIB record %d/%d)!", tsID + 1, rindex + 1, streamptr->tsteps[tsID].recordSize);
        break;
      }

      grbDecompress(recsize, &buffersize, &gribbuffer);

      nrecsScanned++;
      gh = grib_handle_new_from_message(NULL, gribbuffer, recsize);
      GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

      int level1 = 0, level2 = 0, leveltype1, leveltype2 = -1, lbounds, level_sf, level_unit;
      var_tile_t tiles = dummy_tiles;
      grib_get_level(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

      CdiDateTime sDateTime;
      CdiDateTime vDateTime = gribapiGetValidityDateTime(gh, &sDateTime);

      if (rindex == nrecs) break;

      gribapi_get_string(gh, "shortName", varname, sizeof(varname));

      int param = gribapiGetParam(gh);
      if (!varname[0]) param_to_name(param, varname);

      if (rindex == 0)
      {
        vDateTime0 = vDateTime;
        int taxisID = vlistInqTaxis(vlistID);
        if (taxisInqType(taxisID) == TAXIS_RELATIVE)
        {
          taxis->type = TAXIS_RELATIVE;
          taxis->unit = gribapi_get_timeunits(gh);
          taxis->rDateTime = gribapiGetDataDateTime(gh);
        }
        else { taxis->type = TAXIS_ABSOLUTE; }
        taxis->fDateTime = taxis->rDateTime;
        taxis->vDateTime = vDateTime;
        taxis->sDateTime = sDateTime;
      }
      else
      {
        if (cdiDateTime_isLT(sDateTime, taxis->sDateTime)) taxis->sDateTime = sDateTime;
      }

      VarScanKeys scanKeys = gribapiGetScanKeys(gh);

      int tsteptype = gribapiGetTsteptype(gh);
      size_t gridsize = gribapiGetGridsize(gh);
      compvar2_t compVar = gribapiVarSet(param, level1, level2, leveltype1, tsteptype, gridsize, varname, scanKeys, tiles);

      for (vrecID = 0; vrecID < nrecs; vrecID++)
      {
        recID = streamptr->tsteps[1].recIDs[vrecID];
        if (gribapiVarCompare(&compVar, &records[recID], 0) == 0) break;
      }

      if (vrecID == nrecs)
      {
        if (CDI_Inventory_Mode == 1)
        {
          gribWarning("Parameter not defined at timestep 1!", nrecsScanned, tsID + 1, varname, param, level1, level2);
          return CDI_EUFSTRUCT;
        }
        else
        {
          gribWarning("Parameter not defined at timestep 1, skipped!", nrecsScanned, tsID + 1, varname, param, level1, level2);
          continue;
        }
      }

      if (CDI_Inventory_Mode != 1)
      {
        if (recinfo[recID].used)
        {
          if (cdiDateTime_isNE(vDateTime, vDateTime0)) break;

          if (CDI_Debug) gribWarning("Parameter already exist, skipped!", nrecsScanned, tsID + 1, varname, param, level1, level2);

          continue;
        }
      }

      recinfo[recID].used = true;
      streamptr->tsteps[tsID].recIDs[rindex] = recID;

      if (CDI_Debug) Message("%4d %8lld %4d %8d %8s", rindex + 1, (long long) recpos, param, level1, CdiDateTime_string(vDateTime));

      if (gribapiVarCompare(&compVar, &records[recID], 0) != 0)
      {
        Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
                records[recID].ilevel, level1);
        Error("Invalid, unsupported or inconsistent record structure");
      }

      records[recID].position = recpos;
      records[recID].size = recsize;

      if (CDI_Debug) Message("%4d %8lld %4d %8d %s", rindex, (long long) recpos, param, level1, CdiDateTime_string(vDateTime));

      grib_handle_delete(gh);
      gh = NULL;
    }

    if (gh) grib_handle_delete(gh);

    for (vrecID = 0; vrecID < nrecs; vrecID++)
    {
      recID = streamptr->tsteps[tsID].recIDs[vrecID];
      if (!recinfo[recID].used) break;
    }

    if (vrecID < nrecs)
    {
      gribWarning("Parameter not found!", nrecsScanned, tsID + 1, varname, records[recID].param, records[recID].ilevel,
                  records[recID].ilevel2);
      return CDI_EUFSTRUCT;
    }

    streamptr->rtsteps++;

    if (streamptr->ntsteps != streamptr->rtsteps)
    {
      tsID = tstepsNewEntry(streamptr);
      if (tsID != streamptr->rtsteps) Error("Internal error. tsID = %d", tsID);

      streamptr->tsteps[tsID - 1].next = true;
      streamptr->tsteps[tsID].position = recpos;
    }

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);
    streamptr->tsteps[tsID].position = recpos;

    streamptr->record->buffer = gribbuffer;
    streamptr->record->buffersize = buffersize;
  }

  if (nrecs > 0 && nrecs < streamptr->tsteps[tsID].nrecs)
  {
    Warning("Incomplete timestep. Stop scanning at timestep %d.", tsID);
    streamptr->ntsteps = tsID;
  }

  return streamptr->ntsteps;
}

#ifdef gribWarning
#undef gribWarning
#endif

static void
unpack_alternative_rows(grib_handle *gh, int memType, void *data)
{
  long xsize = 0, ysize = 0;
  grib_get_long(gh, "Nx", &xsize);
  grib_get_long(gh, "Ny", &ysize);

  if (memType == MEMTYPE_FLOAT)
  {
    float *pdata = (float *) data;
    for (int j = 1; j < ysize; j += 2)
      for (int i = 0; i < xsize / 2; i++)
      {
        float tmp = pdata[j * xsize + i];
        pdata[j * xsize + i] = pdata[j * xsize + xsize - i - 1];
        pdata[j * xsize + xsize - i - 1] = tmp;
      }
  }
  else
  {
    double *pdata = (double *) data;
    for (int j = 1; j < ysize; j += 2)
      for (int i = 0; i < xsize / 2; i++)
      {
        double tmp = pdata[j * xsize + i];
        pdata[j * xsize + i] = pdata[j * xsize + xsize - i - 1];
        pdata[j * xsize + xsize - i - 1] = tmp;
      }
  }
}

int
gribapiDecode(int memType, void *gribbuffer, size_t gribsize, void *data, size_t gridsize, int unreduced, size_t *numMissVals,
              double missval)
{
  int status = 0;

  if (unreduced)
  {
    static bool lwarn = true;
    if (lwarn)
    {
      lwarn = false;
      Warning("Conversion of gaussian reduced grids unsupported!");
    }
  }

  size_t recsize = (size_t) gribsize;
  grib_handle *gh = grib_handle_new_from_message(NULL, gribbuffer, recsize);
  GRIB_CHECK(my_grib_set_double(gh, "missingValue", missval), 0);

  // get the size of the values array
  size_t datasize;
  GRIB_CHECK(grib_get_size(gh, "values", &datasize), 0);
  // long numberOfPoints;
  // GRIB_CHECK(grib_get_long(gh, "numberOfPoints", &numberOfPoints), 0);
  // printf("values_size = %d  numberOfPoints = %ld\n", datasize, numberOfPoints);

  if (datasize != gridsize) Error("numberOfPoint (%zu) and gridSize (%zu) differ!", datasize, gridsize);
  size_t dummy = datasize;

  if (memType == MEMTYPE_FLOAT)
  {
#ifdef HAVE_GRIBAPI_FLOAT_INTERFACE
    GRIB_CHECK(grib_get_float_array(gh, "values", (float *) data, &dummy), 0);
#else
    Error("grib_get_float_array() not found!");
#endif
  }
  else { GRIB_CHECK(grib_get_double_array(gh, "values", (double *) data, &dummy), 0); }

  if (gribEditionNumber(gh) > 1)
  {
    long alternativeRowScanning = false;
    grib_get_long(gh, "alternativeRowScanning", &alternativeRowScanning);
    if (alternativeRowScanning) unpack_alternative_rows(gh, memType, data);
  }

  long lpar;
  GRIB_CHECK(grib_get_long(gh, "gridDefinitionTemplateNumber", &lpar), 0);
  int gridtype = (int) lpar;

  *numMissVals = 0;
  if (gridtype < 50 || gridtype > 53)
  {
    GRIB_CHECK(grib_get_long(gh, "numberOfMissing", &lpar), 0);
    *numMissVals = (size_t) lpar;
    // printf("gridtype %d, numMissVals %d\n", gridtype, numMissVals);
  }

  grib_handle_delete(gh);

  return status;
}

static void
gribapiDefInstitut(grib_handle *gh, int vlistID, int varID)
{
  int instID = vlistInqInstitut(vlistID);

  if (instID == CDI_UNDEFID) instID = vlistInqVarInstitut(vlistID, varID);

  if (instID != CDI_UNDEFID)
  {
    long center = institutInqCenter(instID);
    long subcenter = institutInqSubcenter(instID);

    long center0, subcenter0;
    GRIB_CHECK(grib_get_long(gh, "centre", &center0), 0);
    GRIB_CHECK(grib_get_long(gh, "subCentre", &subcenter0), 0);

    if (center != center0) GRIB_CHECK(my_grib_set_long(gh, "centre", center), 0);
    if (subcenter != subcenter0) GRIB_CHECK(my_grib_set_long(gh, "subCentre", subcenter), 0);
  }

  int status;
  int centre, subCentre;
  status = cdiInqKeyInt(vlistID, CDI_GLOBAL, CDI_KEY_CENTRE, &centre);
  if (status == 0) grib_set_long(gh, "centre", centre);
  status = cdiInqKeyInt(vlistID, CDI_GLOBAL, CDI_KEY_SUBCENTRE, &subCentre);
  if (status == 0) grib_set_long(gh, "subCentre", subCentre);

  status = cdiInqKeyInt(vlistID, varID, CDI_KEY_CENTRE, &centre);
  if (status == 0) grib_set_long(gh, "centre", centre);
  status = cdiInqKeyInt(vlistID, varID, CDI_KEY_SUBCENTRE, &subCentre);
  if (status == 0) grib_set_long(gh, "subCentre", subCentre);
}

static void
gribapiDefModel(grib_handle *gh, int vlistID, int varID)
{
  int modelID = vlistInqModel(vlistID);
  if (modelID == CDI_UNDEFID) modelID = vlistInqVarModel(vlistID, varID);

  if (modelID != CDI_UNDEFID) GRIB_CHECK(my_grib_set_long(gh, "generatingProcessIdentifier", modelInqGribID(modelID)), 0);
}

static void
gribapiDefParam(int editionNumber, grib_handle *gh, int param, const char *name, const char *stdname)
{
  bool ldefined = false;

  int pdis, pcat, pnum;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);

  if (pnum < 0)
  {
    size_t len = strlen(stdname);
    if (len)
    {
      int status = my_grib_set_string(gh, "cfName", stdname, &len);
      if (status == 0)
        ldefined = true;
      else
        Warning("grib_api: No match for cfName=%s", stdname);
    }

    if (ldefined == false)
    {
      len = strlen(name);
      int status = my_grib_set_string(gh, "shortName", name, &len);
      if (status == 0)
        ldefined = true;
      else
        Warning("grib_api: No match for shortName=%s", name);
    }
  }

  if (ldefined == false)
  {
    if (pnum < 0) pnum = -pnum;

    if (pnum > 255)
    {
      static bool lwarn_pnum = true;
      if (lwarn_pnum)
      {
        Warning("Parameter number %d out of range (1-255), set to %d!", pnum, pnum % 256);
        lwarn_pnum = false;
      }
      pnum = pnum % 256;
    }

    if (editionNumber <= 1)
    {
      static bool lwarn_pdis = true;
      if (pdis != 255 && lwarn_pdis)
      {
        char paramstr[32];
        cdiParamToString(param, paramstr, sizeof(paramstr));
        Warning("Can't convert GRIB2 parameter ID (%s) to GRIB1, set to %d.%d!", paramstr, pnum, pcat);
        lwarn_pdis = false;
      }

      GRIB_CHECK(my_grib_set_long(gh, "table2Version", pcat), 0);
      GRIB_CHECK(my_grib_set_long(gh, "indicatorOfParameter", pnum), 0);
    }
    else
    {
      GRIB_CHECK(my_grib_set_long(gh, "discipline", pdis), 0);
      GRIB_CHECK(my_grib_set_long(gh, "parameterCategory", pcat), 0);
      GRIB_CHECK(my_grib_set_long(gh, "parameterNumber", pnum), 0);
    }
  }

  // printf("param: %d.%d.%d %s\n", pnum, pcat, pdis, name);
}

static int
getTimeunitFactor(int timeunit)
{
  switch (timeunit)
  {
    case TUNIT_SECOND: return 1;
    case TUNIT_MINUTE: return 60;
    case TUNIT_HOUR: return 3600;
    case TUNIT_3HOURS: return 10800;
    case TUNIT_6HOURS: return 21600;
    case TUNIT_12HOURS: return 43200;
    case TUNIT_DAY: return 86400;
  }

  return 3600;
}

static int
grib2ProDefTempHasStatisticalDef(int proDefTempNum)
{
  switch (proDefTempNum)
  {
    case 8:
    case 9:
    case 10:
    case 11:
    case 12:
    case 13:
    case 14:
    case 34:
    case 42:
    case 43:
    case 46:
    case 47:
    case 61:
    case 67:
    case 68:
    case 91:
    case 1001:
    case 1101:
    case 40034: return 1;
  }

  return 0;
}

static int
getUnitsOfTime(int timeunit)
{
  switch (timeunit)
  {
    case TUNIT_SECOND: return 13;
    case TUNIT_MINUTE: return 0;
    case TUNIT_HOUR: return 1;
    case TUNIT_3HOURS: return 10;
    case TUNIT_6HOURS: return 11;
    case TUNIT_12HOURS: return 12;
    case TUNIT_DAY: return 2;
  }

  return 1;
}

static void
gribapiDefStepUnits(int editionNumber, grib_handle *gh, int timeunit, int proDefTempNum, int gcinit)
{
  if (!gcinit)
  {
    long unitsOfTime = getUnitsOfTime(timeunit);

    grib_set_long(gh, "stepUnits", unitsOfTime);
    if (editionNumber == 1) { grib_set_long(gh, "unitOfTimeRange", unitsOfTime); }
    else if (grib2ProDefTempHasStatisticalDef(proDefTempNum))
    {
      grib_set_long(gh, "indicatorOfUnitForTimeRange", unitsOfTime);
      grib_set_long(gh, "indicatorOfUnitOfTimeRange", unitsOfTime);
    }
    else
    {
      // NOTE KNMI:  HIRLAM model files LAMH_D11 are in grib1 and do NOT have key indicatorOfUnitForTimeRange
      // Watch out for compatibility issues.
      grib_set_long(gh, "indicatorOfUnitOfTimeRange", unitsOfTime);
    }
  }
}

static int
gribapiDefSteptype(int editionNumber, grib_handle *gh, int productDefinitionTemplate, int typeOfGeneratingProcess, int tsteptype,
                   int gcinit)
{
  const char *stepType = "instant";
  long proDefTempNum = 0;

  if (tsteptype >= TSTEP_INSTANT && tsteptype <= TSTEP_SUM)
  {
    stepType = cdiGribAPI_ts_str_map[tsteptype].sname;
    proDefTempNum = cdiGribAPI_ts_str_map[tsteptype].productionTemplate;
  }

  if (productDefinitionTemplate != -1)
    proDefTempNum = productDefinitionTemplate;
  else if (typeOfGeneratingProcess == 4)
    proDefTempNum = (proDefTempNum == 8) ? 11 : 1;

  if (!gcinit)
  {
    if (editionNumber > 1) GRIB_CHECK(my_grib_set_long(gh, "productDefinitionTemplateNumber", proDefTempNum), 0);
    size_t len = strlen(stepType);
    int status = my_grib_set_string(gh, "stepType", stepType, &len);
    if (status != 0) GRIB_CHECK(my_grib_set_long(gh, "productDefinitionTemplateNumber", 0), 0);
  }

  return (int) proDefTempNum;
}

static void
gribapiDefDateTimeAbs(int editionNumber, grib_handle *gh, CdiDateTime dateTime, int productDefinitionTemplate,
                      int typeOfGeneratingProcess, int tsteptype, int gcinit)
{
  (void) gribapiDefSteptype(editionNumber, gh, productDefinitionTemplate, typeOfGeneratingProcess, tsteptype, gcinit);

  if (editionNumber > 1) GRIB_CHECK(my_grib_set_long(gh, "significanceOfReferenceTime", 0), 0);
  if (editionNumber > 1) GRIB_CHECK(my_grib_set_long(gh, "stepRange", 0), 0);

  if (cdiDateTime_isNull(dateTime)) dateTime.date = cdiDate_set(10101);
  gribapiSetDataDateTime(gh, dateTime);
}

static int
gribapiDefDateTimeRel(int editionNumber, grib_handle *gh, CdiDateTime fDateTime, CdiDateTime vDateTime, CdiDateTime sDateTime,
                      int productDefinitionTemplate, int typeOfGeneratingProcess, int tsteptype, int timeunit, int calendar,
                      int gcinit)
{
  int status = -1;

  JulianDate julianDate1 = julianDate_encode(calendar, fDateTime);

  if (cdiDateTime_isNull(vDateTime)) vDateTime = fDateTime;

  JulianDate julianDate2 = julianDate_encode(calendar, vDateTime);
  JulianDate julianDate = julianDate_sub(julianDate2, julianDate1);

  int factor = getTimeunitFactor(timeunit);

  if (!(int) (fmod(julianDate_to_seconds(julianDate), factor)))
  {
    int proDefTempNum
        = gribapiDefSteptype(editionNumber, gh, productDefinitionTemplate, typeOfGeneratingProcess, tsteptype, gcinit);
    gribapiDefStepUnits(editionNumber, gh, timeunit, proDefTempNum, gcinit);

    long startStep = 0;
    double endStepF = julianDate_to_seconds(julianDate) / factor;
    long maxStep = (editionNumber > 1) ? INT_MAX : 65000;
    if (endStepF > maxStep) return status;
    long endStep = lround(endStepF);

    bool hasStartDate = (tsteptype == TSTEP_RANGE || tsteptype == TSTEP_AVG || tsteptype == TSTEP_ACCUM || tsteptype == TSTEP_DIFF
                         || tsteptype == TSTEP_MIN || tsteptype == TSTEP_MAX || tsteptype == TSTEP_RMS || tsteptype == TSTEP_SD
                         || tsteptype == TSTEP_COV || tsteptype == TSTEP_RATIO || tsteptype == TSTEP_SUM);
    if (!cdiDateTime_isNull(sDateTime) && hasStartDate)
    {
      julianDate2 = julianDate_encode(calendar, sDateTime);
      startStep = lround(julianDate_to_seconds(julianDate_sub(julianDate2, julianDate1)) / factor);
    }

    if (editionNumber > 1) GRIB_CHECK(my_grib_set_long(gh, "significanceOfReferenceTime", 1), 0);
    if (editionNumber > 1) GRIB_CHECK(my_grib_set_long(gh, "stepRange", 0), 0);

    if (cdiDateTime_isNull(fDateTime)) fDateTime.date = cdiDate_set(10101);
    gribapiSetDataDateTime(gh, fDateTime);

    // printf(">>>>> tsteptype %d  startStep %ld  endStep %ld\n", tsteptype, startStep, endStep);

    // Product Definition Template Number: defined in GRIB_API file 4.0.table point in time products:
    if ((proDefTempNum >= 0 && proDefTempNum <= 7) || proDefTempNum == 55 || proDefTempNum == 40055)  // Tile
      startStep = endStep;

    if (endStep < startStep) return status;

    if (editionNumber == 1 && (startStep > 255 || endStep > 255))
    {
      startStep = 0;
      endStep = 0;
    }
    else { status = 0; }

    if (editionNumber > 1) GRIB_CHECK(my_grib_set_long(gh, "forecastTime", startStep), 0);
    // if ( editionNumber == 1 && startStep > 0) GRIB_CHECK(my_grib_set_long(gh, "startStep", startStep), 0);
    if (editionNumber == 1) GRIB_CHECK(my_grib_set_long(gh, "startStep", startStep), 0);
    GRIB_CHECK(my_grib_set_long(gh, "endStep", endStep), 0);
  }

  return status;
}

static void
gribapiDefTime(int editionNumber, int productDefinitionTemplate, int typeOfGeneratingProcess, grib_handle *gh,
               CdiDateTime vDateTime, int tsteptype, int numavg, int taxisID, int gcinit)
{
  UNUSED(numavg);

  int taxistype = (taxisID == -1) ? TAXIS_ABSOLUTE : taxisInqType(taxisID);

  if (typeOfGeneratingProcess == 196)
  {
    vDateTime = cdiDateTime_set(10101, 0);
    taxistype = TAXIS_ABSOLUTE;
  }

  if (taxistype == TAXIS_RELATIVE)
  {
    int timeunit = taxisInqTunit(taxisID);
    int calendar = taxisInqCalendar(taxisID);

    CdiDateTime fDateTime = taxisInqFdatetime(taxisID);
    if (cdiDateTime_isNull(fDateTime)) fDateTime = taxisInqRdatetime(taxisID);
    if (cdiDateTime_isLT(vDateTime, fDateTime)) fDateTime = vDateTime;

    CdiDateTime sDateTime = taxisInqSdatetime(taxisID);

    int status = gribapiDefDateTimeRel(editionNumber, gh, fDateTime, vDateTime, sDateTime, productDefinitionTemplate,
                                       typeOfGeneratingProcess, tsteptype, timeunit, calendar, gcinit);
    if (status != 0) taxistype = TAXIS_ABSOLUTE;
  }

  if (taxistype == TAXIS_ABSOLUTE)
    gribapiDefDateTimeAbs(editionNumber, gh, vDateTime, productDefinitionTemplate, typeOfGeneratingProcess, tsteptype, gcinit);
}

static void
gribapiDefGridRegular(grib_handle *gh, int gridID, int gridtype, bool gridIsRotated, bool gridIsCurvilinear, int uvRelativeToGrid)
{
  const char *mesg;
  // clang-format off
  if      (gridtype == GRID_GAUSSIAN)         mesg = "regular_gg";
  else if (gridtype == GRID_GAUSSIAN_REDUCED) mesg = "reduced_gg";
  else if (gridIsRotated)                     mesg = "rotated_ll";
  else                                        mesg = "regular_ll";
  // clang-format on
  size_t len = strlen(mesg);
  GRIB_CHECK(my_grib_set_string(gh, "gridType", mesg, &len), 0);

  double xfirst = 0.0, xlast = 0.0, xinc = 0.0;
  double yfirst = 0.0, ylast = 0.0, yinc = 0.0;

  SizeType nlon = gridInqXsize(gridID), nlat = gridInqYsize(gridID);

  if (gridtype == GRID_GAUSSIAN_REDUCED)
  {
    xfirst = (nlon == 2) ? gridInqXval(gridID, 0) : 0.0;
    xlast = (nlon == 2) ? gridInqXval(gridID, 1) : 360.0 - 360.0 * 0.5 / (double) nlat;

    nlon = 0;

    long *pl = (long *) Malloc((size_t) nlat * sizeof(long) + (size_t) nlat * sizeof(int));
    int *reducedPoints = (int *) (pl + nlat);
    gridInqReducedPoints(gridID, reducedPoints);
    for (SizeType i = 0; i < nlat; ++i) pl[i] = reducedPoints[i];

    GRIB_CHECK(grib_set_long_array(gh, "pl", pl, (size_t) nlat), 0);

    Free(pl);
  }
  else
  {
    if (nlon == 0)
      nlon = 1;
    else
    {
      xfirst = gridInqXval(gridID, 0);
      xlast = gridInqXval(gridID, (gridIsCurvilinear ? nlon * nlat : nlon) - 1);
      xinc = fabs(gridInqXinc(gridID));
    }
  }

  if (nlat == 0)
    nlat = 1;
  else
  {
    yfirst = gridInqYval(gridID, 0);
    ylast = gridInqYval(gridID, (gridIsCurvilinear ? nlon * nlat : nlat) - 1);
    yinc = fabs(gridInqYinc(gridID));
  }

  double xfirsto = xfirst;
  double xlasto = xlast;
  while (xfirsto > 360.0) xfirsto -= 360.0;
  while (xlasto > 360.0) xlasto -= 360.0;

  if (gridtype != GRID_GAUSSIAN_REDUCED) GRIB_CHECK(my_grib_set_long(gh, "Ni", nlon), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfFirstGridPointInDegrees", xfirsto), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfLastGridPointInDegrees", xlasto), 0);
  if (gridtype != GRID_GAUSSIAN_REDUCED) GRIB_CHECK(my_grib_set_double(gh, "iDirectionIncrementInDegrees", xinc), 0);

  GRIB_CHECK(my_grib_set_long(gh, "Nj", nlat), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees", yfirst), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfLastGridPointInDegrees", ylast), 0);

  if (uvRelativeToGrid >= 0) GRIB_CHECK(my_grib_set_long(gh, "uvRelativeToGrid", uvRelativeToGrid), 0);

  GRIB_CHECK(my_grib_set_long(gh, "iScansNegatively", (xfirst > xlast)), 0);
  GRIB_CHECK(my_grib_set_long(gh, "jScansPositively", (yfirst < ylast)), 0);

  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED)
  {
    long np = gridInqNP(gridID);
    if (np == 0) np = nlat / 2;
    GRIB_CHECK(my_grib_set_long(gh, "numberOfParallelsBetweenAPoleAndTheEquator", np), 0);
  }
  else { GRIB_CHECK(my_grib_set_double(gh, "jDirectionIncrementInDegrees", yinc), 0); }

  if (gridIsRotated)
  {
    double xpole = 0.0, ypole = 0.0, angle = 0.0;
    gridInqParamRLL(gridID, &xpole, &ypole, &angle);

    xpole += 180.0;
    if (fabs(ypole) > 0.0) ypole = -ypole;  // change from north to south pole
    if (fabs(angle) > 0.0) angle = -angle;
    GRIB_CHECK(my_grib_set_double(gh, "latitudeOfSouthernPoleInDegrees", ypole), 0);
    GRIB_CHECK(my_grib_set_double(gh, "longitudeOfSouthernPoleInDegrees", xpole), 0);
    GRIB_CHECK(my_grib_set_double(gh, "angleOfRotation", angle), 0);
  }
}

static int
encode_shapeOfTheEarth(struct CDI_GridProjParams *gpp)
{
  int shapeOfTheEarth = 1;
  int a = (int) lround(gpp->a);
  int b = (int) lround(gpp->b);
  int rf = (int) lround(gpp->rf);

  // clang-format off
  if      (a == 6367470) shapeOfTheEarth = 0;
  else if (a == 6371229) shapeOfTheEarth = 6;
  else if (a == 6371200) shapeOfTheEarth = 8;
  else if (a == 6378160 && b == 6356775 && rf == 297) shapeOfTheEarth = 2;
  else if (a == 6378137 && b == 6356752 && rf == 298) shapeOfTheEarth = 4;
  // clang-format on

  return shapeOfTheEarth;
}

static void
gribapiDefProjLCC(grib_handle *gh, int editionNumber, int gridID, int uvRelativeToGrid)
{
  long xsize = (long) gridInqXsize(gridID);
  long ysize = (long) gridInqYsize(gridID);

  struct CDI_GridProjParams gpp;
  gridInqParamsLCC(gridID, &gpp);
  if (IS_EQUAL(gpp.x_0, gpp.mv) && IS_EQUAL(gpp.y_0, gpp.mv) && (IS_EQUAL(gpp.xval_0, gpp.mv) || IS_EQUAL(gpp.yval_0, gpp.mv)))
  {
    gpp.x_0 = gridInqXval(gridID, 0);
    gpp.y_0 = gridInqYval(gridID, 0);
  }
  gridVerifyProjParamsLCC(&gpp);
  if (gpp.xval_0 < 0.0) gpp.xval_0 += 360.0;
  if (gpp.lon_0 < 0.0) gpp.lon_0 += 360.0;

  bool isSouthPole = (gpp.lat_1 < 0.0);
  if (isSouthPole && gribEditionNumber(gh) == 1)
  {
    gpp.lat_1 = -gpp.lat_2;
    gpp.lat_2 = -gpp.lat_2;
  }
  int projflag = 0;
  if (isSouthPole) gribbyte_set_bit(&projflag, 1);

  double xinc = gridInqXinc(gridID);
  double yinc = gridInqYinc(gridID);
  if (IS_EQUAL(xinc, 0.0)) xinc = gridInqXincInMeter(gridID);
  if (IS_EQUAL(yinc, 0.0)) yinc = gridInqYincInMeter(gridID);

  static const char mesg[] = "lambert";
  size_t len = sizeof(mesg) - 1;
  GRIB_CHECK(my_grib_set_string(gh, "gridType", mesg, &len), 0);

  GRIB_CHECK(my_grib_set_long(gh, "Nx", xsize), 0);
  GRIB_CHECK(my_grib_set_long(gh, "Ny", ysize), 0);
  GRIB_CHECK(my_grib_set_double(gh, "DxInMetres", fabs(xinc)), 0);
  GRIB_CHECK(my_grib_set_double(gh, "DyInMetres", fabs(yinc)), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfFirstGridPointInDegrees", gpp.xval_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees", gpp.yval_0), 0);
  if (editionNumber > 1) GRIB_CHECK(my_grib_set_double(gh, "LaDInDegrees", gpp.lat_1), 0);
  GRIB_CHECK(my_grib_set_double(gh, "LoVInDegrees", gpp.lon_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "Latin1InDegrees", gpp.lat_1), 0);
  GRIB_CHECK(my_grib_set_double(gh, "Latin2InDegrees", gpp.lat_2), 0);
  GRIB_CHECK(my_grib_set_long(gh, "projectionCentreFlag", projflag), 0);

  if (gpp.x_SP >= -180 && gpp.x_SP <= 360) GRIB_CHECK(my_grib_set_double(gh, "longitudeOfSouthernPoleInDegrees", gpp.x_SP), 0);
  if (gpp.y_SP >= -90 && gpp.y_SP <= 90) GRIB_CHECK(my_grib_set_double(gh, "latitudeOfSouthernPoleInDegrees", gpp.y_SP), 0);

  long shapeOfTheEarth = encode_shapeOfTheEarth(&gpp);
  if (shapeOfTheEarth) GRIB_CHECK(my_grib_set_long(gh, "shapeOfTheEarth", shapeOfTheEarth), 0);
  if (shapeOfTheEarth == 1) GRIB_CHECK(my_grib_set_long(gh, "radiusOfTheEarth", (long) gpp.a), 0);

  long earthIsOblate = (shapeOfTheEarth == 2 || shapeOfTheEarth == 3 || shapeOfTheEarth == 4);
  if (earthIsOblate) GRIB_CHECK(my_grib_set_long(gh, "earthIsOblate", earthIsOblate), 0);

  if (uvRelativeToGrid >= 0) GRIB_CHECK(my_grib_set_long(gh, "uvRelativeToGrid", uvRelativeToGrid), 0);

  GRIB_CHECK(my_grib_set_long(gh, "iScansNegatively", (xinc < 0)), 0);
  GRIB_CHECK(my_grib_set_long(gh, "jScansPositively", (yinc > 0)), 0);
}

static void
gribapiDefProjSTERE(grib_handle *gh, int editionNumber, int gridID, int uvRelativeToGrid)
{
  long xsize = (long) gridInqXsize(gridID);
  long ysize = (long) gridInqYsize(gridID);

  struct CDI_GridProjParams gpp;
  gridInqParamsSTERE(gridID, &gpp);
  gridVerifyProjParamsSTERE(&gpp);
  if (gpp.xval_0 < 0.0) gpp.xval_0 += 360.0;
  int projflag = 0;

  double xinc = gridInqXinc(gridID);
  double yinc = gridInqYinc(gridID);

  static const char mesg[] = "polar_stereographic";
  size_t len = sizeof(mesg) - 1;
  GRIB_CHECK(my_grib_set_string(gh, "gridType", mesg, &len), 0);

  GRIB_CHECK(my_grib_set_long(gh, "Nx", xsize), 0);
  GRIB_CHECK(my_grib_set_long(gh, "Ny", ysize), 0);
  GRIB_CHECK(my_grib_set_double(gh, "DxInMetres", xinc), 0);
  GRIB_CHECK(my_grib_set_double(gh, "DyInMetres", yinc), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfFirstGridPointInDegrees", gpp.xval_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees", gpp.yval_0), 0);
  if (editionNumber > 1) GRIB_CHECK(my_grib_set_double(gh, "LaDInDegrees", gpp.lat_1), 0);
  GRIB_CHECK(my_grib_set_double(gh, "orientationOfTheGridInDegrees", gpp.lon_0), 0);
  long southPoleOnProjectionPlane = IS_EQUAL(gpp.lat_0, -90.0);
  GRIB_CHECK(my_grib_set_double(gh, "southPoleOnProjectionPlane", (double) southPoleOnProjectionPlane), 0);
  GRIB_CHECK(my_grib_set_long(gh, "projectionCentreFlag", projflag), 0);

  long shapeOfTheEarth = encode_shapeOfTheEarth(&gpp);
  if (shapeOfTheEarth) GRIB_CHECK(my_grib_set_long(gh, "shapeOfTheEarth", shapeOfTheEarth), 0);
  if (shapeOfTheEarth == 1) GRIB_CHECK(my_grib_set_long(gh, "radiusOfTheEarth", (long) gpp.a), 0);

  long earthIsOblate = (shapeOfTheEarth == 2 || shapeOfTheEarth == 3 || shapeOfTheEarth == 4);
  if (earthIsOblate) GRIB_CHECK(my_grib_set_long(gh, "earthIsOblate", earthIsOblate), 0);

  if (uvRelativeToGrid >= 0) GRIB_CHECK(my_grib_set_long(gh, "uvRelativeToGrid", uvRelativeToGrid), 0);

  GRIB_CHECK(my_grib_set_long(gh, "iScansNegatively", (xinc < 0)), 0);
  GRIB_CHECK(my_grib_set_long(gh, "jScansPositively", (yinc > 0)), 0);
}

static void
define_healpix_grid(grib_handle *gh, int nside, int order, int uvRelativeToGrid)
{
  static const char mesg[] = "healpix";
  size_t len = sizeof(mesg) - 1;
  GRIB_CHECK(my_grib_set_string(gh, "gridType", mesg, &len), 0);
  GRIB_CHECK(my_grib_set_long(gh, "Nside", nside), 0);
  GRIB_CHECK(my_grib_set_long(gh, "ordering", order), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfFirstGridPointInDegrees", 45.0), 0);
  if (uvRelativeToGrid >= 0) GRIB_CHECK(my_grib_set_long(gh, "uvRelativeToGrid", uvRelativeToGrid), 0);
}

static void
gribapiDefProjHEALPIX(grib_handle *gh, int gridID, int uvRelativeToGrid)
{
  struct CDI_GridProjParams gpp;
  gridInqParamsHEALPIX(gridID, &gpp);
  gridVerifyProjParamsHEALPIX(&gpp);

  define_healpix_grid(gh, gpp.nside, gpp.order, uvRelativeToGrid);
}

static void
gribapiDefGridHEALPIX(grib_handle *gh, int gridID, int uvRelativeToGrid)
{
  int order = 0;
  int nside = 0;
  char attstring[256];
  if (cdiInqAttTxt(gridID, CDI_GLOBAL, "indexing_scheme", (int) sizeof(attstring), attstring) == 0)
  {
    int attlen = cdiInqAttLen(gridID, CDI_GLOBAL, "indexing_scheme");
    attstring[attlen] = 0;
    order = strStartsWith(attstring, "nest");
  }
  int attint = 0;
  if (cdiInqAttInt(gridID, CDI_GLOBAL, "refinement_level", 1, &attint) == 0) nside = lround(pow(2.0, attint));

  define_healpix_grid(gh, nside, order, uvRelativeToGrid);
}

static void
gribapiDefGridGME(grib_handle *gh, int gridID, long gridsize)
{
  GRIB_CHECK(my_grib_set_long(gh, "gridDefinitionTemplateNumber", GRIB2_GTYPE_GME), 0);

  int nd = 0, ni = 0, ni2 = 0, ni3 = 0;
  gridInqParamGME(gridID, &nd, &ni, &ni2, &ni3);
  GRIB_CHECK(my_grib_set_long(gh, "nd", nd), 0);
  GRIB_CHECK(my_grib_set_long(gh, "Ni", ni), 0);
  GRIB_CHECK(my_grib_set_long(gh, "n2", ni2), 0);
  GRIB_CHECK(my_grib_set_long(gh, "n3", ni3), 0);
  GRIB_CHECK(my_grib_set_long(gh, "latitudeOfThePolePoint", 90000000), 0);
  GRIB_CHECK(my_grib_set_long(gh, "longitudeOfThePolePoint", 0), 0);

  GRIB_CHECK(my_grib_set_long(gh, "numberOfDataPoints", gridsize), 0);
  GRIB_CHECK(my_grib_set_long(gh, "totalNumberOfGridPoints", gridsize), 0);
}

static void
gribapiDefGridUnstructured(grib_handle *gh, int gridID)
{
  static bool warning = true;

  int status = my_grib_set_long(gh, "gridDefinitionTemplateNumber", GRIB2_GTYPE_UNSTRUCTURED);
  if (status != 0 && warning)
  {
    warning = false;
    Warning("Can't write reference grid!");
    Warning("gridDefinitionTemplateNumber %d not found (grib2/template.3.%d.def)!", GRIB2_GTYPE_UNSTRUCTURED,
            GRIB2_GTYPE_UNSTRUCTURED);
  }
  else
  {
    int errCount = 0;
    int number = 0;
    status = cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, &number);
    if (status < 0) errCount++;
    if (number < 0) number = 0;
    GRIB_CHECK(my_grib_set_long(gh, "numberOfGridUsed", number), 0);

    int position = 0;
    status = cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDINREFERENCE, &position);
    if (status < 0) errCount++;
    if (position < 0) position = 0;
    GRIB_CHECK(my_grib_set_long(gh, "numberOfGridInReference", position), 0);

    unsigned char uuid[CDI_UUID_SIZE];
    size_t len = CDI_UUID_SIZE;
    memset(uuid, 0, len);
    int length = CDI_UUID_SIZE;
    status = cdiInqKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
    if (status < 0) errCount++;
    if (grib_set_bytes(gh, "uuidOfHGrid", uuid, &len) != 0) Warning("Can't write UUID!");

    if (warning && errCount > 0)
    {
      warning = false;
      char uuidStr[uuidNumHexChars + 1] = { 0 };
      cdiUUID2Str(uuid, uuidStr);
      Warning("GRIB2 grid parameter missing: numberOfGridUsed=%d numberOfGridInReference=%d uuidOfHGrid=%s", number, position,
              uuidStr);
    }
  }
}

static void
gribapiDefGridSpectral(grib_handle *gh, int gridID)
{
  int trunc = gridInqTrunc(gridID);
  enum
  {
    numTruncAtt = 3
  };
  static const char truncAttNames[numTruncAtt][2] = { "J", "K", "M" };
  for (size_t i = 0; i < numTruncAtt; ++i) GRIB_CHECK(my_grib_set_long(gh, truncAttNames[i], trunc), 0);

  if (gridInqComplexPacking(gridID))
  {
    static const char truncAttNames2[numTruncAtt][3] = { "JS", "KS", "MS" };
    for (size_t i = 0; i < numTruncAtt; ++i) GRIB_CHECK(my_grib_set_long(gh, truncAttNames2[i], 20), 0);
  }
}

static void
gribapiDefPackingType(grib_handle *gh, bool lieee, bool lspectral, bool lcomplex, int comptype, SizeType gridsize)
{
  static const char mesg_spectral_complex[] = "spectral_complex";
  static const char mesg_spectral_simple[] = "spectral_simple";
  static const char mesg_grid_jpeg[] = "grid_jpeg";
  static const char mesg_grid_ccsds[] = "grid_ccsds";
  static const char mesg_ieee[] = "grid_ieee";
  static const char mesg_simple[] = "grid_simple";
  const char *mesg = mesg_simple;

  if (lspectral) { mesg = lcomplex ? mesg_spectral_complex : mesg_spectral_simple; }
  else if (comptype == CDI_COMPRESS_JPEG && gridsize > 1) { mesg = mesg_grid_jpeg; }
  else if ((comptype == CDI_COMPRESS_SZIP || comptype == CDI_COMPRESS_AEC) && gridsize > 1) { mesg = mesg_grid_ccsds; }
  else if (lieee) { mesg = mesg_ieee; }

  size_t len = strlen(mesg);
  GRIB_CHECK(my_grib_set_string(gh, "packingType", mesg, &len), 0);
}

static void
gribapiDefGrid(int editionNumber, grib_handle *gh, int gridID, int comptype, int datatype, int uvRelativeToGrid)
{
  SizeType gridsize = gridInqSize(gridID);
  bool gridIsRotated = false;
  bool gridIsCurvilinear = false;
  int gridtype = grbGetGridtype(&gridID, gridsize, &gridIsRotated, &gridIsCurvilinear);

  bool lieee = (editionNumber == 2 && (datatype == CDI_DATATYPE_FLT32 || datatype == CDI_DATATYPE_FLT64));
  bool lspectral = (gridtype == GRID_SPECTRAL);
  bool lcomplex = (lspectral && gridInqComplexPacking(gridID));

  if (lieee) comptype = 0;
  if (lspectral) lieee = false;

  if (lspectral)  // gridType needs to be defined before packingType !!!
  {
    static const char mesg[] = "sh";
    size_t len = sizeof(mesg) - 1;
    GRIB_CHECK(my_grib_set_string(gh, "gridType", mesg, &len), 0);
  }

  gribapiDefPackingType(gh, lieee, lspectral, lcomplex, comptype, gridsize);

  if (lieee) GRIB_CHECK(my_grib_set_long(gh, "precision", datatype == CDI_DATATYPE_FLT64 ? 2 : 1), 0);

  if (editionNumber == 2) GRIB_CHECK(my_grib_set_long(gh, "numberOfValues", (long) gridsize), 0);

  switch (gridtype)
  {
    case GRID_LONLAT:
    case GRID_GAUSSIAN:
    case GRID_GAUSSIAN_REDUCED:
    case GRID_TRAJECTORY:
    {
      gribapiDefGridRegular(gh, gridID, gridtype, gridIsRotated, gridIsCurvilinear, uvRelativeToGrid);
      break;
    }
    case CDI_PROJ_LCC:
    {
      gribapiDefProjLCC(gh, editionNumber, gridID, uvRelativeToGrid);
      break;
    }
    case CDI_PROJ_STERE:
    {
      gribapiDefProjSTERE(gh, editionNumber, gridID, uvRelativeToGrid);
      break;
    }
    case CDI_PROJ_HEALPIX:
    {
      if (editionNumber <= 1) Error("HEALPix grid can't be stored in GRIB edition %d!", editionNumber);
      gribapiDefProjHEALPIX(gh, gridID, uvRelativeToGrid);
      break;
    }
    case GRID_HEALPIX:
    {
      if (editionNumber <= 1) Error("HEALPix grid can't be stored in GRIB edition %d!", editionNumber);
      gribapiDefGridHEALPIX(gh, gridID, uvRelativeToGrid);
      break;
    }
    case GRID_SPECTRAL:
    {
      gribapiDefGridSpectral(gh, gridID);
      break;
    }
    case GRID_GME:
    {
      if (editionNumber <= 1) Error("GME grid can't be stored in GRIB edition %d!", editionNumber);
      gribapiDefGridGME(gh, gridID, (long) gridsize);
      break;
    }
    case GRID_UNSTRUCTURED:
    {
      if (editionNumber <= 1) Error("Unstructured grid can't be stored in GRIB edition %d!", editionNumber);
      gribapiDefGridUnstructured(gh, gridID);
      break;
    }
    default:
    {
      Error("Unsupported grid type: %s", gridNamePtr(gridtype));
      break;
    }
  }
}

static void
getLevelFactor(double level, long *factor, long *out_scaled_value)
{
  const double eps = 1.0e-7;

  double scaled_value = level;
  long iscaled_value = lround(scaled_value);

  long i;
  for (i = 0; (iscaled_value < (4294967295 / 10)) && (fabs(scaled_value - (double) iscaled_value) >= eps) && i < 7; i++)
  {
    scaled_value *= 10.0;
    iscaled_value = lround(scaled_value);
  }

  (*factor) = i;
  (*out_scaled_value) = iscaled_value;
}

static void
gribapiDefLevelType(grib_handle *gh, int gcinit, const char *keyname, long leveltype)
{
  bool lset = false;
  if ((leveltype == GRIB1_LTYPE_ISOBARIC_PA || leveltype == 99 || leveltype == 100) && gribEditionNumber(gh) == 1)
  {
    if (gribGetLong(gh, "indicatorOfTypeOfLevel") != leveltype) lset = true;
  }

  if (!gcinit || lset) GRIB_CHECK(my_grib_set_long(gh, keyname, leveltype), 0);
}

static void
grib1DefLevel(grib_handle *gh, int gcinit, long leveltype1, long leveltype2, bool hasBounds, double level, double dlevel1,
              double dlevel2)
{
  (void) leveltype2;
  gribapiDefLevelType(gh, gcinit, "indicatorOfTypeOfLevel", leveltype1);

  if (hasBounds)
  {
    GRIB_CHECK(my_grib_set_long(gh, "topLevel", lround(dlevel1)), 0);
    GRIB_CHECK(my_grib_set_long(gh, "bottomLevel", lround(dlevel2)), 0);
  }
  else { GRIB_CHECK(my_grib_set_long(gh, "level", lround(level)), 0); }
}

static void
grib2DefLevel(grib_handle *gh, int gcinit, long leveltype1, long leveltype2, bool hasBounds, double level, double dlevel1,
              double dlevel2)
{
  gribapiDefLevelType(gh, gcinit, "typeOfFirstFixedSurface", leveltype1);
  if (hasBounds) gribapiDefLevelType(gh, gcinit, "typeOfSecondFixedSurface", leveltype2);

  if (!hasBounds) dlevel1 = level;

  long scaled_level, factor;
  getLevelFactor(dlevel1, &factor, &scaled_level);
  GRIB_CHECK(my_grib_set_long(gh, "scaleFactorOfFirstFixedSurface", factor), 0);
  GRIB_CHECK(my_grib_set_long(gh, "scaledValueOfFirstFixedSurface", scaled_level), 0);

  if (hasBounds)
  {
    getLevelFactor(dlevel2, &factor, &scaled_level);
    GRIB_CHECK(my_grib_set_long(gh, "scaleFactorOfSecondFixedSurface", factor), 0);
    GRIB_CHECK(my_grib_set_long(gh, "scaledValueOfSecondFixedSurface", scaled_level), 0);
  }
}

static void
gribapiDefLevel(int editionNumber, grib_handle *gh, int zaxisID, int levelID, int gcinit, int proddef_template_num)
{
  int zaxistype = zaxisInqType(zaxisID);
  int ltype = 0, ltype2 = -1;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, &ltype);
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFSECONDFIXEDSURFACE, &ltype2);

  bool hasBounds = (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL));
  double level = zaxisInqLevels(zaxisID, NULL) ? zaxisInqLevel(zaxisID, levelID) : levelID + 1;
  double dlevel1 = hasBounds ? zaxisInqLbound(zaxisID, levelID) : level;
  double dlevel2 = hasBounds ? zaxisInqUbound(zaxisID, levelID) : 0.0;

  if (zaxistype == ZAXIS_GENERIC && ltype == 0)
  {
    Warning("Changed zaxis type from %s to %s", zaxisNamePtr(zaxistype), zaxisNamePtr(ZAXIS_PRESSURE));
    zaxistype = ZAXIS_PRESSURE;
    zaxisChangeType(zaxisID, zaxistype);
    cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, "Pa");
  }

  long grib_ltype;
  {
    int (*ltypeMap)(int grib_ltype) = editionNumber <= 1 ? zaxisTypeToGrib1ltype : zaxisTypeToGrib2ltype;
    grib_ltype = ltypeMap(zaxistype);
  }
  long grib_ltype2 = (ltype != ltype2 && ltype2 != -1) ? ltype2 : grib_ltype;

  void (*defLevel)(grib_handle * gh, int gcinit, long leveltype1, long leveltype2, bool hasBounds, double level, double dlevel1,
                   double dlevel2)
      = (editionNumber <= 1) ? grib1DefLevel : grib2DefLevel;

  switch (zaxistype)
  {
    case ZAXIS_SURFACE:
    case ZAXIS_MEANSEA:
    case ZAXIS_HEIGHT:
    case ZAXIS_ALTITUDE:
    case ZAXIS_SIGMA:
    case ZAXIS_DEPTH_BELOW_SEA:
    case ZAXIS_ISENTROPIC:
    {
      if (zaxistype == ZAXIS_HEIGHT)
      {
        double sf = zaxis_units_to_meter(zaxisID);
        level *= sf;
        dlevel1 *= sf;
        dlevel2 *= sf;
      }

      /* GRIB2: PRODUCT DEFINITION TEMPLATE NUMBER 32:

         "Analysis or forecast at a horizontal level or in a
         horizontal layer at a point in time for simulate
         (synthetic) satellite data"

        GRIB2: PRODUCT DEFINITION TEMPLATE NUMBER 101/102:

         "Analysis or forecast at a horizontal level or in a
         horizontal layer at a point in time for wave 2D spectra
         with frequencies and directions defined by formulae"

         Similar for ensemble template 102.

         The key/value pairs that are set in "grib2DefLevel" do not
         exist for this template.
      */
      if (editionNumber <= 1 || (proddef_template_num != 32 && proddef_template_num != 101 && proddef_template_num != 102))
      {
        defLevel(gh, gcinit, grib_ltype, grib_ltype2, hasBounds, level, dlevel1, dlevel2);
      }

      break;
    }
    case ZAXIS_CLOUD_BASE:
    case ZAXIS_CLOUD_TOP:
    case ZAXIS_ISOTHERM_ZERO:
    case ZAXIS_TROPOPAUSE:
    case ZAXIS_TOA:
    case ZAXIS_SEA_BOTTOM:
    case ZAXIS_LAKE_BOTTOM:
    case ZAXIS_SEDIMENT_BOTTOM:
    case ZAXIS_SEDIMENT_BOTTOM_TA:
    case ZAXIS_SEDIMENT_BOTTOM_TW:
    case ZAXIS_MIX_LAYER:
    case ZAXIS_ATMOSPHERE:
    {
      defLevel(gh, gcinit, grib_ltype, grib_ltype, hasBounds, level, dlevel1, dlevel2);
    }
    break;
    case ZAXIS_HYBRID:
    case ZAXIS_HYBRID_HALF:
    {
      if (editionNumber <= 1) { grib_ltype = hasBounds ? GRIB1_LTYPE_HYBRID_LAYER : GRIB1_LTYPE_HYBRID; }
      defLevel(gh, gcinit, grib_ltype, grib_ltype, hasBounds, level, dlevel1, dlevel2);

      if (!gcinit)
      {
        int vctsize = zaxisInqVctSize(zaxisID);
        if (vctsize > 0)
        {
          GRIB_CHECK(my_grib_set_long(gh, "PVPresent", 1), 0);
          GRIB_CHECK(grib_set_double_array(gh, "pv", zaxisInqVctPtr(zaxisID), (size_t) vctsize), 0);
        }
      }

      break;
    }
    case ZAXIS_PRESSURE:
    {
      if (level < 0) Warning("Pressure level of %f Pa is below zero!", level);

      if (!zaxis_units_is_Pa(zaxisID))
      {
        level *= 100;
        dlevel1 *= 100;
        dlevel2 *= 100;
      }

      if (editionNumber <= 1)
      {
        double dum;
        if (level < 32768 && (level < 100 || modf(level / 100, &dum) > 0))
          grib_ltype = GRIB1_LTYPE_ISOBARIC_PA;
        else
          level /= 100;

        static bool lwarn = true;
        if (lwarn && modf(level, &dum) > 0.001)
        {
          lwarn = false;
          Warning("GRIB1 can store only full pressure levels! Level changed from %ghPa to %dhPa.", level, (int) lround(level));
        }
      }
      else if (ltype2 == -1)
        ltype2 = GRIB2_LTYPE_ISOBARIC;

      defLevel(gh, gcinit, grib_ltype, ltype2, hasBounds, level, dlevel1, dlevel2);

      break;
    }
    case ZAXIS_SNOW:
      if (editionNumber <= 1)
        ;  // not available
      else { grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype, hasBounds, level, dlevel1, dlevel2); }

      break;
    case ZAXIS_DEPTH_BELOW_LAND:
    {
      double sf = editionNumber <= 1 ? zaxis_units_to_centimeter(zaxisID) : zaxis_units_to_meter(zaxisID);
      grib_ltype = editionNumber <= 1 ? (hasBounds ? GRIB1_LTYPE_LANDDEPTH_LAYER : GRIB1_LTYPE_LANDDEPTH) : grib_ltype;
      defLevel(gh, gcinit, grib_ltype, grib_ltype, hasBounds, level * sf, dlevel1 * sf, dlevel2 * sf);

      break;
    }
    case ZAXIS_REFERENCE:
    {
      if (!gcinit) GRIB_CHECK(my_grib_set_long(gh, "genVertHeightCoords", 1), 0);

      if (editionNumber <= 1)
        ;  // not available
      else
      {
        if (hasBounds)
        {
          gribapiDefLevelType(gh, gcinit, "typeOfFirstFixedSurface", grib_ltype);
          gribapiDefLevelType(gh, gcinit, "typeOfSecondFixedSurface", grib_ltype2);
          GRIB_CHECK(my_grib_set_long(gh, "topLevel", (long) dlevel1), 0);
          GRIB_CHECK(my_grib_set_long(gh, "bottomLevel", (long) dlevel2), 0);
        }
        else { grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype2, hasBounds, level, dlevel1, dlevel2); }

        GRIB_CHECK(my_grib_set_long(gh, "NV", 6), 0);
        int number = 0;
        cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NUMBEROFVGRIDUSED, &number);
        GRIB_CHECK(my_grib_set_long(gh, "numberOfVGridUsed", number), 0);
        int nlev = 0;
        cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NLEV, &nlev);
        GRIB_CHECK(my_grib_set_long(gh, "nlev", nlev), 0);
        unsigned char uuid[CDI_UUID_SIZE];
        int length = CDI_UUID_SIZE;
        memset(uuid, 0, (size_t) length);
        cdiInqKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
        size_t len = CDI_UUID_SIZE;
        if (grib_set_bytes(gh, "uuidOfVGrid", uuid, &len) != 0) Warning("Can't write UUID!");
      }

      break;
    }
    case ZAXIS_GENERIC:
    {
      defLevel(gh, gcinit, ltype, ltype, hasBounds, level, dlevel1, dlevel2);
      break;
    }
    default:
    {
      Error("Unsupported zaxis type: %s", zaxisNamePtr(zaxistype));
      break;
    }
  }
}

int
gribapiGetScanningMode(grib_handle *gh)
{
  long iScansNegatively, jScansPositively, jPointsAreConsecutive;
  GRIB_CHECK(grib_get_long(gh, "iScansNegatively", &iScansNegatively), 0);
  GRIB_CHECK(grib_get_long(gh, "jScansPositively", &jScansPositively), 0);
  GRIB_CHECK(grib_get_long(gh, "jPointsAreConsecutive", &jPointsAreConsecutive), 0);
  int scanningMode = 128 * (bool) iScansNegatively + 64 * (bool) jScansPositively + 32 * (bool) jPointsAreConsecutive;
  if (cdiDebugExt >= 30)
    printf("gribapiGetScanningMode(): Scanning mode = %02d (%1d%1d%1d)*32; \n", scanningMode, (int) jPointsAreConsecutive,
           (int) jScansPositively, (int) iScansNegatively);

  return scanningMode;
}

void
gribapiSetScanningMode(grib_handle *gh, int scanningMode)
{
  // 127: reserved for testing; generated test data will be in 64 scanning mode
  // if (scanningMode== 127)  scanningMode = 64;

  long iScansNegatively = (scanningMode & 128) / 128;
  long jScansPositively = (scanningMode & 64) / 64;
  long jPointsAreConsecutive = (scanningMode & 32) / 32;

  if (cdiDebugExt >= 30 && gribEditionNumber(gh) <= 1)
  {
    long paramId, levelTypeId, levelId, uvRelativeToGrid;
    GRIB_CHECK(grib_get_long(gh, "uvRelativeToGrid", &uvRelativeToGrid), 0);
    GRIB_CHECK(grib_get_long(gh, "indicatorOfParameter", &paramId), 0);
    GRIB_CHECK(grib_get_long(gh, "indicatorOfTypeOfLevel", &levelTypeId), 0);
    GRIB_CHECK(grib_get_long(gh, "level", &levelId), 0);
    printf("gribapiSetScanningMode(): (param,ltype,level) = (%3d,%3d,%4d); Scanning mode = %02d (%1d%1d%1d)*32;  "
           "uvRelativeToGrid = %02d\n",
           (int) paramId, (int) levelTypeId, (int) levelId, scanningMode, (int) jPointsAreConsecutive, (int) jScansPositively,
           (int) iScansNegatively, (int) uvRelativeToGrid);
  }

  GRIB_CHECK(my_grib_set_long(gh, "iScansNegatively", iScansNegatively), 0);
  GRIB_CHECK(my_grib_set_long(gh, "jScansPositively", jScansPositively), 0);
  GRIB_CHECK(my_grib_set_long(gh, "jPointsAreConsecutive", jPointsAreConsecutive), 0);
}

/*
  TABLE 8. SCANNING MODE FLAG

  (GDS Octet 28)
  BIT     VALUE     MEANING
  1       0       Points scan in +i direction
          1       Points scan in -i direction
  2       0       Points scan in -j direction
          1       Points scan in +j direction
  3       0       Adjacent points in i direction are consecutive
                    (FORTRAN: (I,J))
          1       Adjacent points in j direction are consecutive
                  (FORTRAN: (J,I))

  => Scanning Mode     0 0 0 0 0 0 0 0  (00 dec)  +i, -j; i direction consecutive (row-major    order West->East   & North->South)
  => Scanning Mode     0 1 0 0 0 0 0 0  (64 dec)  +i, +j; i direction consecutive (row-major    order West->East   & South->North )
  => Scanning Mode     1 1 0 0 0 0 0 0  (96 dec)  +i, +j; j direction consecutive (column-major order South->North & West->East )

  NOTE:  South->North  - As if you would plot the data as image on the screen
                         where [0,0] of the data is the top-left pixel.

                         grib2ppm LAMH_D11_201302150000_00000_oro | display ppm:-
                         ImageMagick (display): [0,0] of an image belongs to the top-left pixel
  [DEFAULT] : 64 dec

  iScansNegatively = 0;
  jScansPositively = 1;
  jPointsAreConsecutive = 0;    => Scanning Mode 64

  cdo selindexbox,1,726,100,550 LAMH_D11_201302150000_00000_oro LAMH_D11_201302150000_00000_oro_cropped
  grib2ppm LAMH_D11_201302150000_00000_oro_cropped | /usr/bin/display ppm:- &
  # ^^^ this image will be missing the souther parts of data

  grib2ppm LAMH_D11_201302150000_00000_oro | /usr/bin/display ppm:- &
  # ^ full domain data
*/

#ifdef HIRLAM_EXTENSIONS
static void
verticallyFlipGridDefinitionWhenScanningModeChanged(grib_handle *gh, double yfirst, double ylast, double yinc)
{
  /*
  Nj = 550;
  latitudeOfFirstGridPointInDegrees = -30.8;
  latitudeOfLastGridPointInDegrees = 24.1;
  iScansNegatively = 0;
  jScansPositively = 0;
  jPointsAreConsecutive = 0;
  jDirectionIncrementInDegrees = 0.1;

  When switching from scanning mode 0 <=> 64
  yfirst = -30.8 + (550-1)*0.1

  yfirst = yfirst + (ysize-1) * yinc
  yinc   = -1.0*yinc
  */

  // long jDim=0;
  // GRIB_CHECK(grib_get_long(gh, "Nj", &jDim), 0);

  double latitudeOfFirstGridPointInDegrees;
  double latitudeOfLastGridPointInDegrees;
  double jDirectionIncrementInDegrees;

  // GRIB_CHECK(grib_get_double(gh, "latitudeOfFirstGridPointInDegrees", &latitudeOfFirstGridPointInDegrees), 0);  // yfirst
  // GRIB_CHECK(grib_get_double(gh, "latitudeOfLastGridPointInDegrees", &latitudeOfLastGridPointInDegrees), 0);    // ylast
  // GRIB_CHECK(grib_get_double(gh, "jDirectionIncrementInDegrees", &jDirectionIncrementInDegrees), 0);  // yinc

  if (cdiDebugExt >= 10) Message(" BEFORE: yfirst = %f; ylast = %f; yinc = %f; ", yfirst, ylast, yinc);

  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees", ylast), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfLastGridPointInDegrees", yfirst), 0);
  // yinc *= -1.0; // don't set yinc here ...
  // GRIB_CHECK(my_grib_set_double(gh, "jDirectionIncrementInDegrees", yinc), 0);

  if (cdiDebugExt >= 10)
  {
    GRIB_CHECK(grib_get_double(gh, "latitudeOfFirstGridPointInDegrees", &latitudeOfFirstGridPointInDegrees), 0);  // yfirst
    GRIB_CHECK(grib_get_double(gh, "latitudeOfLastGridPointInDegrees", &latitudeOfLastGridPointInDegrees), 0);    // ylast
    GRIB_CHECK(grib_get_double(gh, "jDirectionIncrementInDegrees", &jDirectionIncrementInDegrees), 0);            // yinc
    Message("CHANGED INTO:  yfirst = %f, ylast = %f, yinc = %f", latitudeOfFirstGridPointInDegrees,
            latitudeOfLastGridPointInDegrees, jDirectionIncrementInDegrees);
  }
}

static void
convertDataScanningMode(int scanModeIN, int scanModeOUT, double *data, size_t gridsize, size_t iDim, size_t jDim)
{
  size_t idxIN, idxOUT;

  // 127: reserved for testing; it will generate test data in 64 scanning mode
  if (scanModeOUT == 127)  // fill with testdata ...
  {
    scanModeOUT = 64;
    if (cdiDebugExt >= 30) printf("convertDataScanningMode(): Generating test data in 64 scanning mode..\n");
    for (size_t j = 0; j < jDim; j++)
    {
      size_t jXiDim = j * iDim;
      for (size_t i = 0; i < iDim; i++)
      {
        idxIN = i + jXiDim;
        data[idxIN] = (double) (100.0 * j + i);
      }
    }
  }

  if ((iDim * jDim) != gridsize)
  {
    if (cdiDebugExt >= 30)
      printf("convertDataScanningMode(): ERROR: (iDim*jDim)!= gridsize;  (%zu * %zu) != %zu\n", iDim, jDim, gridsize);
    return;
  }
  if (cdiDebugExt >= 30)
    printf("convertDataScanningMode(): scanModeIN=%02d => scanModeOUT=%02d ; where: (iDim * jDim == gridsize)  (%zu*%zu == %zu)\n",
           scanModeIN, scanModeOUT, iDim, jDim, gridsize);

  if (cdiDebugExt >= 100)
  {
    printf("convertDataScanningMode(): data IN:\n");
    for (size_t j = 0; j < jDim; j++)
    {
      size_t jXiDim = j * iDim;
      for (size_t i = 0; i < iDim; i++)
      {
        idxIN = i + jXiDim;
        printf("%03.0f, ", data[idxIN]);
      }
      printf("\n");
    }
  }

  if (scanModeIN == scanModeOUT)
  {
    if (cdiDebugExt >= 30) printf("convertDataScanningMode(): INFO: Nothing to do;  scanModeIN==scanModeOUT..\n");
    return;
  }

  if (0)
  {
    return;
    if (scanModeOUT == 00)
    {
      if (cdiDebugExt > 0) printf("convertDataScanningMode(): Leave data unchaged BUT set scanModeOUT=00.\n");
      // CHECK:  Looks like that GRIB-API provide (no matter what) data in the scannning mode 00, even it is store in the
      // gribfile as 64 !!
      return;
    }
  }
  double *dataCopy = (double *) Malloc(gridsize * sizeof(double));
  memcpy((void *) dataCopy, (void *) data, gridsize * sizeof(double));

  if (scanModeIN
      == 64)  // Scanning Mode (00 dec)  +i, -j; i direction consecutive (row-major    order West->East   & South->North )
  {           // Scanning Mode (64 dec)  +i, +j; i direction consecutive (row-major    order West->East   & North->South )
              // Scanning Mode (96 dec)  +i, +j; j direction consecutive (column-major order North->South & West->East )
    if (scanModeOUT == 00)
    // CHECK:  Looks like that GRIB-API provide (no matter what) data in the scannning mode 00, even it is store in the gribfile
    // as 64 !!
#define VERTICAL_FLIP
#ifdef VERTICAL_FLIP
    {  // flip the data vertically ..
      idxIN = 0;
      idxOUT = (jDim - 1) * iDim;
      if (cdiDebugExt >= 30) printf("convertDataScanningMode():  copying rows nr. (%04d : %04zu)\n", 0, jDim - 1);
      for (size_t j = 0; j < jDim; j++)
      {
        memcpy((void *) &data[idxOUT], (void *) &dataCopy[idxIN], iDim * sizeof(double));
        idxIN += iDim;
        idxOUT -= iDim;
      }
    }  // end if (scanModeOUT==00)*/
#endif
#ifdef HORIZONTAL_FLIP
    {  // flip data horizontally ...
      if (1)
      {
        if (cdiDebugExt >= 30) printf("convertDataScanningMode():  copying columns nr. (%04d : %04d);\n", 0, iDim - 1);
        for (size_t i = 0; i < iDim; i++)
        {
          for (size_t j = 0; j < jDim; j++)
          {
            size_t jXiDim = j * iDim;
            idxIN = i + jXiDim;
            // data[idxIN] = (double) (100.0*j +i);  // just some testdata ..
            idxOUT = iDim - i - 1 + jXiDim;
            // printf("[%03d=>%03d] = %f;",idxIN,idxOUT,dataCopy[idxIN]);
            data[idxOUT] = dataCopy[idxIN];
          }
        }
      }
    }  // end if (scanModeOUT==00)
#endif

    if (scanModeOUT == 96)
    {  // transpose the data
      if (cdiDebugExt >= 30)
        printf("convertDataScanningMode():  transpose data rows=>columns nr. (%04d : %04zu) => (%04d : %04zu);\n", 0, iDim - 1, 0,
               jDim - 1);
      for (size_t j = 0; j < jDim; j++)
      {
        size_t jXiDim = j * iDim;
        for (size_t i = 0; i < iDim; i++)
        {
          idxIN = i + jXiDim;
          idxOUT = j + i * jDim;
          // printf("[%03d=>%03d] = %f;",idxIN,idxOUT,dataCopy[idxIN]);
          data[idxOUT] = dataCopy[idxIN];
        }
        // printf(".\n");
      }
    }  // end if (scanModeOUT==96)
  }    // end if (scanModeIN==64)

  if (scanModeIN
      == 00)  // Scanning Mode (00 dec)  +i, -j; i direction consecutive (row-major    order West->East   & South->North )
  {           // Scanning Mode (64 dec)  +i, +j; i direction consecutive (row-major    order West->East   & North->South )
              // Scanning Mode (96 dec)  +i, +j; j direction consecutive (column-major order North->South & West->East )
    if (scanModeOUT == 64)
    {  // flip the data vertically ..
      idxIN = 0;
      idxOUT = (jDim - 1) * iDim;
      for (size_t j = 0; j < jDim; j++)
      {
        if (cdiDebugExt >= 25)
          printf("convertDataScanningMode():  copying row nr. %04zu; [idxIN=%08zu] => [idxOUT=%08zu]\n", j, idxIN, idxOUT);
        memcpy((void *) &data[idxOUT], (void *) &dataCopy[idxIN], iDim * sizeof(double));
        idxIN += iDim;
        idxOUT -= iDim;
      }
    }  // end if (scanModeOUT==64)

    if (scanModeOUT == 96)
    {  // transpose the data
      size_t jInv;
      for (size_t j = 0; j < jDim; j++)
      {
        if (cdiDebugExt >= 30) printf("convertDataScanningMode():  processing row nr. %04zu;\n", j);
        jInv = (jDim - 1) - j;
        for (size_t i = 0; i < iDim; i++) data[j + i * jDim] = dataCopy[i + jInv * iDim];  // source data has -j
      }
    }  // end if (scanModeOUT==96)
  }    // end if (scanModeIN==00)

  if (scanModeIN
      == 96)  // Scanning Mode (00 dec)  +i, -j; i direction consecutive (row-major    order West->East   & South->North )
  {           // Scanning Mode (64 dec)  +i, +j; i direction consecutive (row-major    order West->East   & North->South )
              // Scanning Mode (96 dec)  +i, +j; j direction consecutive (column-major order North->South & West->East )
    if (scanModeOUT == 64)
    {  // transpose the data
      for (size_t j = 0; j < jDim; j++)
      {
        if (cdiDebugExt >= 30) printf("convertDataScanningMode():  processing row nr. %04zu;\n", j);
        size_t jXiDim = j * iDim;
        for (size_t i = 0; i < iDim; i++)
          // data[j + i*jDim] =  dataCopy[i + j*iDim];
          data[i + jXiDim] = dataCopy[j + i * jDim];
      }
    }  // end if (scanModeOUT==64)

    if (scanModeOUT == 00)
    {  // transpose the data
      idxIN = 0;
      idxOUT = 0;
      size_t jInv;
      for (size_t j = 0; j < jDim; j++)
      {
        if (cdiDebugExt >= 30) printf("convertDataScanningMode():  processing row nr. %04zu;\n", j);
        jInv = (jDim - 1) - j;
        size_t jXiDim = j * iDim;
        for (size_t i = 0; i < iDim; i++)
          // data[jInv + iXjDim] =  dataCopy[i + jXiDim];  // target data has -j
          data[i + jXiDim] = dataCopy[jInv + i * jDim];  // target data has -j
      }
    }  // end if (scanModeOUT==00)
  }    // end if (scanModeIN==96)

  if (cdiDebugExt >= 100)
  {
    printf("convertDataScanningMode(): data OUT (new scanning mode):\n");
    for (size_t j = 0; j < jDim; j++)
    {
      size_t jXiDim = j * iDim;
      for (size_t i = 0; i < iDim; i++)
      {
        idxIN = i + jXiDim;
        printf("%03.0f, ", data[idxIN]);
      }
      printf("\n");
    }
  }

  free(dataCopy);
}
#endif  // HIRLAM_EXTENSIONS

static void
gribapiSetExtMode(grib_handle *gh, int gridID, size_t datasize, const void *data)
{
#ifndef HIRLAM_EXTENSIONS
  (void) gh;
  (void) data;
  (void) datasize;
#endif
  int gridtype = gridInqType(gridID);
  if (gridtype == GRID_PROJECTION)
  {
    int projtype = gridInqProjType(gridID);
    // clang-format off
      if      (projtype == CDI_PROJ_RLL)   gridtype = GRID_LONLAT;
      else if (projtype == CDI_PROJ_LCC)   gridtype = CDI_PROJ_LCC;
      else if (projtype == CDI_PROJ_STERE) gridtype = CDI_PROJ_STERE;
    // clang-format on
  }

  if (gridtype == GRID_GENERIC || gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED
      || gridtype == CDI_PROJ_LCC)
  {
#ifdef HIRLAM_EXTENSIONS
    int scanModeIN = 0;
    cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_SCANNINGMODE, &scanModeIN);

    if (cdiDebugExt >= 100) Message("scanModeIN=%d; gridsize=%zu", scanModeIN, gridInqSize(gridID));

    if (cdiGribDataScanningMode.active)  // allowed modes: <0, 64, 96>; Default is 64
    {
      size_t iDim = gridInqXsize(gridID);
      size_t jDim = gridInqYsize(gridID);

      double yfirst = gridInqYval(gridID, 0);
      double ylast = gridInqYval(gridID, jDim - 1);
      double yinc = gridInqYinc(gridID);

      int scanModeOUT = cdiGribDataScanningMode.value;
      convertDataScanningMode(scanModeIN, scanModeOUT, (double *) data, datasize, iDim, jDim);
      // This will overrule the old scanning mode of the given grid
      if (cdiDebugExt >= 10) Message("Set GribDataScanningMode (%d) => (%d)", scanModeIN, cdiGribDataScanningMode.value);
      gribapiSetScanningMode(gh, cdiGribDataScanningMode.value);

      if (((scanModeIN == 00) && (cdiGribDataScanningMode.value == 64))
          || ((scanModeIN == 64) && (cdiGribDataScanningMode.value == 00)))
        verticallyFlipGridDefinitionWhenScanningModeChanged(gh, yfirst, ylast, yinc);
    }
    else
    {
      if (cdiDebugExt >= 100) Message("Set GribDataScanningMode => (%d) based on used grid", scanModeIN);
      gribapiSetScanningMode(gh, scanModeIN);
    }
#endif
  }
}

// #define GRIBAPIENCODETEST 1

size_t
gribapiEncode(int memType, int varID, int levelID, int vlistID, int gridID, int zaxisID, CdiDateTime vDateTime, int tsteptype,
              int numavg, SizeType datasize, const void *data, SizeType numMissVals, void **gribbuffer, size_t *gribbuffersize,
              int comptype, void *gribContainer)
{
  long editionNumber = 2;

  // extern unsigned char _grib_template_GRIB2[];
  cdi_check_gridsize_int_limit("GRIB", datasize);

  int param = vlistInqVarParam(vlistID, varID);
  int datatype = vlistInqVarDatatype(vlistID, varID);
  int typeOfGeneratingProcess = 0;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFGENERATINGPROCESS, &typeOfGeneratingProcess);
  int productDefinitionTemplate = -1;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_PRODUCTDEFINITIONTEMPLATE, &productDefinitionTemplate);

  int uvRelativeToGrid = -1;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_UVRELATIVETOGRID, &uvRelativeToGrid);

#ifdef GRIBAPIENCODETEST
  grib_handle *gh = (grib_handle *) gribHandleNew(editionNumber);
#else
  gribContainer_t *gc = (gribContainer_t *) gribContainer;
  assert(gc != NULL);
  grib_handle *gh = (struct grib_handle *) gc->gribHandle;
#endif

  GRIB_CHECK(grib_get_long(gh, "editionNumber", &editionNumber), 0);

  if (!gc->init) gribapiDefInstitut(gh, vlistID, varID);

  if (editionNumber == 2)
  {
    if (!gc->init)
    {
      int backgroundProcess = 0;
      cdiInqKeyInt(vlistID, varID, CDI_KEY_BACKGROUNDPROCESS, &backgroundProcess);
      GRIB_CHECK(my_grib_set_long(gh, "typeOfGeneratingProcess", typeOfGeneratingProcess), 0);
      GRIB_CHECK(my_grib_set_long(gh, "backgroundProcess", backgroundProcess), 0);
      int status, tablesVersion, localTablesVersion;
      status = cdiInqKeyInt(vlistID, varID, CDI_KEY_TABLESVERSION, &tablesVersion);
      if (status == 0) GRIB_CHECK(my_grib_set_long(gh, "tablesVersion", (long) tablesVersion), 0);
      status = cdiInqKeyInt(vlistID, varID, CDI_KEY_LOCALTABLESVERSION, &localTablesVersion);
      if (status == 0) GRIB_CHECK(my_grib_set_long(gh, "localTablesVersion", (long) localTablesVersion), 0);
      int typeOfProcessedData = 0;
      status = cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFPROCESSEDDATA, &typeOfProcessedData);
      if (status == 0) GRIB_CHECK(my_grib_set_long(gh, "typeOfProcessedData", (long) typeOfProcessedData), 0);
      /*
      int constituentType = 0;
      status = cdiInqKeyInt(vlistID, varID, CDI_KEY_CONSTITUENTTYPE, &constituentType);
      if ( status == 0 ) GRIB_CHECK(my_grib_set_long(gh, "constituentType", (long)constituentType), 0);
      */
    }
  }

  gribapiDefTime((int) editionNumber, productDefinitionTemplate, typeOfGeneratingProcess, gh, vDateTime, tsteptype, numavg,
                 vlistInqTaxis(vlistID), gc->init);

  {
    int typeOfTimeIncrement = 0;
    int status = cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFTIMEINCREMENT, &typeOfTimeIncrement);
    if (status == 0) grib_set_long(gh, "typeOfTimeIncrement", (long) typeOfTimeIncrement);
  }

  {
    int status, perturbationNumber, numberOfForecastsInEnsemble, typeOfEnsembleForecast;
    status = cdiInqKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, &numberOfForecastsInEnsemble);
    if (status == 0) grib_set_long(gh, "numberOfForecastsInEnsemble", numberOfForecastsInEnsemble);
    status = cdiInqKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, &perturbationNumber);
    if (status == 0) grib_set_long(gh, "perturbationNumber", perturbationNumber);
    status = cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, &typeOfEnsembleForecast);
    if (status == 0) grib_set_long(gh, "typeOfEnsembleForecast", typeOfEnsembleForecast);
  }

  if (!gc->init) gribapiDefModel(gh, vlistID, varID);

  if (!gc->init)
  {
    char name[256], stdname[256];
    vlistInqVarName(vlistID, varID, name);
    vlistInqVarStdname(vlistID, varID, stdname);
    gribapiDefParam((int) editionNumber, gh, param, name, stdname);
  }

  if (!gc->init && editionNumber == 2)
  {
    int shapeOfTheEarth = 0;
    cdiInqKeyInt(vlistID, varID, CDI_KEY_SHAPEOFTHEEARTH, &shapeOfTheEarth);
    GRIB_CHECK(my_grib_set_long(gh, "shapeOfTheEarth", (long) shapeOfTheEarth), 0);

    int grib2LocalSectionNumber, section2PaddingLength;
    int mpimType, mpimClass, mpimUser;
    if (cdiInqKeyInt(vlistID, varID, CDI_KEY_MPIMTYPE, &mpimType) == CDI_NOERR
        && cdiInqKeyInt(vlistID, varID, CDI_KEY_MPIMCLASS, &mpimClass) == CDI_NOERR
        && cdiInqKeyInt(vlistID, varID, CDI_KEY_MPIMUSER, &mpimUser) == CDI_NOERR)
    {
      grib_set_long(gh, "grib2LocalSectionPresent", 1);
      grib_set_long(gh, "grib2LocalSectionNumber", 1);
      grib_set_long(gh, "mpimType", mpimType);
      grib_set_long(gh, "mpimClass", mpimClass);
      grib_set_long(gh, "mpimUser", mpimUser);

      int revNumLen = 20;
      unsigned char revNumber[revNumLen];
      if (cdiInqKeyBytes(vlistID, varID, CDI_KEY_REVNUMBER, revNumber, &revNumLen) == CDI_NOERR)
      {
        size_t revNumLenS = (size_t) revNumLen;
        grib_set_bytes(gh, "revNumber", revNumber, &revNumLenS);
      }
      int revStatus;
      if (cdiInqKeyInt(vlistID, varID, CDI_KEY_REVSTATUS, &revStatus) == CDI_NOERR) grib_set_long(gh, "revStatus", revStatus);
    }
    else if (cdiInqKeyInt(vlistID, varID, CDI_KEY_GRIB2LOCALSECTIONNUMBER, &grib2LocalSectionNumber) == CDI_NOERR
             && cdiInqKeyInt(vlistID, varID, CDI_KEY_SECTION2PADDINGLENGTH, &section2PaddingLength) == CDI_NOERR)
    {
      grib_set_long(gh, "grib2LocalSectionPresent", 1);
      grib_set_long(gh, "grib2LocalSectionNumber", grib2LocalSectionNumber);
      unsigned char *section2Padding = (unsigned char *) Malloc((size_t) section2PaddingLength);
      cdiInqKeyBytes(vlistID, varID, CDI_KEY_SECTION2PADDING, section2Padding, &section2PaddingLength);
      size_t len = (size_t) section2PaddingLength;
      // Does not work anymore with ecCodes 2.22.0/2.25.0!!!
      // ECCODES ERROR   :  pack_bytes: Wrong size (10) for section2Padding. It is 0 bytes long
      grib_set_bytes(gh, "section2Padding", section2Padding, &len);
      Free(section2Padding);
    }
  }

  // bitsPerValue have to be defined first (complex packing)
  GRIB_CHECK(my_grib_set_long(gh, "bitsPerValue", (long) grbBitsPerValue(datatype)), 0);

  if (!gc->init) gribapiDefGrid((int) editionNumber, gh, gridID, comptype, datatype, uvRelativeToGrid);

  gribapiDefLevel((int) editionNumber, gh, zaxisID, levelID, gc->init, productDefinitionTemplate);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int zaxisSize = zaxisInqSize(zaxisID);
  // if (!gc->init)
  {
    // NOTE: Optional key/value pairs: Note that we do not distinguish between tiles here!

    for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; i++)
    {
      if (vlistptr->vars[varID].opt_grib_kvpair[i].update)
      {
        // DR: Fix for multi-level fields (otherwise only the 1st level is correct)
        if (zaxisSize == (levelID + 1)) vlistptr->vars[varID].opt_grib_kvpair[i].update = false;

        int ret = 0;

        if (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_double)
        {
          if (CDI_Debug)
            Message("key \"%s\"  :   double value = %g", vlistptr->vars[varID].opt_grib_kvpair[i].keyword,
                    vlistptr->vars[varID].opt_grib_kvpair[i].dbl_val);

          ret = my_grib_set_double(gh, vlistptr->vars[varID].opt_grib_kvpair[i].keyword,
                                   vlistptr->vars[varID].opt_grib_kvpair[i].dbl_val);
          GRIB_CHECK(ret, 0);
        }

        if (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_int)
        {
          if (CDI_Debug)
            Message("key \"%s\"  :   integer value = %d", vlistptr->vars[varID].opt_grib_kvpair[i].keyword,
                    vlistptr->vars[varID].opt_grib_kvpair[i].int_val);

          ret = my_grib_set_long(gh, vlistptr->vars[varID].opt_grib_kvpair[i].keyword,
                                 (long) vlistptr->vars[varID].opt_grib_kvpair[i].int_val);
          GRIB_CHECK(ret, 0);
        }

        /* ------------------------------------------------------------------
         * NEW: Support for integer arrays (t_intarr)
         * ------------------------------------------------------------------ */
        if (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_intarr)
        {
          size_t len = vlistptr->vars[varID].opt_grib_kvpair[i].arr_len;
          int *src = vlistptr->vars[varID].opt_grib_kvpair[i].int_arr;

          if (src && len > 0)
          {
            if (CDI_Debug)
              Message("key \"%s\"  :   integer array of length %zu", vlistptr->vars[varID].opt_grib_kvpair[i].keyword, len);

            long *long_arr = (long *) Malloc(len * sizeof(long));
            for (size_t j = 0; j < len; ++j) long_arr[j] = src[j];

            ret = grib_set_long_array(gh, vlistptr->vars[varID].opt_grib_kvpair[i].keyword, long_arr, len);
            Free(long_arr);
            GRIB_CHECK(ret, 0);
          }
        }

        /* ------------------------------------------------------------------
         * NEW: Support for double arrays (t_doublearr)
         * ------------------------------------------------------------------ */
        if (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_doublearr)
        {
          size_t len = vlistptr->vars[varID].opt_grib_kvpair[i].arr_len;
          double *src = vlistptr->vars[varID].opt_grib_kvpair[i].dbl_arr;

          if (src && len > 0)
          {
            if (CDI_Debug)
              Message("key \"%s\"  :   double array of length %zu", vlistptr->vars[varID].opt_grib_kvpair[i].keyword, len);

            ret = grib_set_double_array(gh, vlistptr->vars[varID].opt_grib_kvpair[i].keyword, src, len);
            GRIB_CHECK(ret, 0);
          }
        }
      }
    }
  }

  if (numMissVals > 0)
  {
    GRIB_CHECK(my_grib_set_long(gh, "bitmapPresent", 1), 0);
    GRIB_CHECK(my_grib_set_double(gh, "missingValue", vlistInqVarMissval(vlistID, varID)), 0);
  }

  gribapiSetExtMode(gh, gridID, (size_t) datasize, data);

  if (memType == MEMTYPE_FLOAT)
  {
#ifdef HAVE_GRIBAPI_FLOAT_INTERFACE
    GRIB_CHECK(grib_set_float_array(gh, "values", (const float *) data, (size_t) datasize), 0);
#else
    Error("grib_set_float_array() not found!");
#endif
  }
  else { GRIB_CHECK(grib_set_double_array(gh, "values", (const double *) data, (size_t) datasize), 0); }

  if (numMissVals)
  {
    long numberOfMissing = -1;
    GRIB_CHECK(grib_get_long(gh, "numberOfMissing", &numberOfMissing), 0);
    if (numberOfMissing == 0) GRIB_CHECK(my_grib_set_long(gh, "bitmapPresent", 0), 0);
  }

  // get the size of coded message
  const void *dummy = NULL;
  size_t recsize = 0;
  GRIB_CHECK(grib_get_message(gh, &dummy, &recsize), 0);
  recsize += 512;  // add some space for possible filling
  *gribbuffersize = recsize;
  *gribbuffer = Malloc(*gribbuffersize);

  if (CDI_Debug && !gc->init && editionNumber == 2)
  {
    long pdis;
    grib_get_long(gh, "discipline", &pdis);
    if (pdis != 255)
    {
      char cdi_name[CDI_MAX_NAME];
      cdi_name[0] = 0;
      vlistInqVarName(vlistID, varID, cdi_name);
      char grb_name[256];
      gribapi_get_string(gh, "shortName", grb_name, sizeof(grb_name));
      str_to_lower(cdi_name);
      str_to_lower(grb_name);
      bool checkName = (!grb_name[0] && strncmp(cdi_name, "param", 5) == 0) ? false : true;
      if (checkName && ((strlen(cdi_name) != strlen(grb_name)) || !strStartsWith(cdi_name, grb_name)))
        Warning("*** GRIB2 shortName does not correspond to chosen variable name: \"%s\" (\"%s\").",
                grb_name[0] ? grb_name : "unknown", cdi_name);
    }
  }

  // get a copy of the coded message
  GRIB_CHECK(grib_get_message_copy(gh, *gribbuffer, &recsize), 0);

#ifdef GRIBAPIENCODETEST
  gribHandleDelete(gh);
#endif

  gc->init = true;

  return recsize;
}

void
gribapiChangeParameterIdentification(grib_handle *gh, int code, int ltype, int level)
{
  //  timeRangeIndicator: could be included later
  if (gribEditionNumber(gh) <= 1)
  {
    if (code != -1) GRIB_CHECK(my_grib_set_long(gh, "indicatorOfParameter", code), 0);
    if (ltype != -1) GRIB_CHECK(my_grib_set_long(gh, "indicatorOfTypeOfLevel", ltype), 0);
    if (level != -1) GRIB_CHECK(my_grib_set_long(gh, "level", level), 0);
  }
}

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <float.h>



#ifdef HAVE_LIBIEG

static int
iegInqDatatype(int prec)
{
  return (prec == EXSE_PREC_FP64) ? CDI_DATATYPE_FLT64 : CDI_DATATYPE_FLT32;
}

static int
iegDefDatatype(int datatype)
{
  if (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64) Error("CDI/IEG library does not support complex numbers!");

  if (datatype != CDI_DATATYPE_FLT32 && datatype != CDI_DATATYPE_FLT64) datatype = CDI_DATATYPE_FLT32;

  return (datatype == CDI_DATATYPE_FLT64) ? EXSE_PREC_FP64 : EXSE_PREC_FP32;
}

static void
ieg_read_recordSP(stream_t *streamptr, float *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;

  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *iegp = streamptr->record->objectp;
  if (iegRead(fileID, iegp) < 0) Error("Could not read IEG record!");

  iegInqDataFP32(iegp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  size_t size = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));

  *numMissVals = get_num_missvalsSP(size, data, (float) missval);

  streamptr->numvals += (SizeType) size;
}

static void
ieg_read_recordDP(stream_t *streamptr, double *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;

  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *iegp = streamptr->record->objectp;
  if (iegRead(fileID, iegp) < 0) Error("Could not read IEG record!");

  iegInqDataFP64(iegp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  size_t size = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));

  *numMissVals = get_num_missvalsDP(size, data, missval);

  streamptr->numvals += (SizeType) size;
}

void
ieg_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals)
{
  if (memtype == MEMTYPE_DOUBLE)
    ieg_read_recordDP(streamptr, (double *) data, numMissVals);
  else
    ieg_read_recordSP(streamptr, (float *) data, numMissVals);
}

static int
iegGetZaxisType(int iegleveltype)
{
  // clang-format off
  switch (iegleveltype)
    {
    case IEG_LTYPE_SURFACE:         return ZAXIS_SURFACE;
    case IEG_LTYPE_99:
    case IEG_LTYPE_ISOBARIC:        return ZAXIS_PRESSURE;
    case IEG_LTYPE_HEIGHT:          return ZAXIS_HEIGHT;
    case IEG_LTYPE_ALTITUDE:        return ZAXIS_ALTITUDE;
    case IEG_LTYPE_HYBRID:
    case IEG_LTYPE_HYBRID_LAYER:    return ZAXIS_HYBRID;
    case IEG_LTYPE_LANDDEPTH:
    case IEG_LTYPE_LANDDEPTH_LAYER: return ZAXIS_DEPTH_BELOW_LAND;
    case IEG_LTYPE_SEADEPTH:        return ZAXIS_DEPTH_BELOW_SEA;
    }
  // clang-format on
  return ZAXIS_GENERIC;
}

static void
iegDefTime(int *pdb, CdiDateTime datetime, int taxisID)
{
  int timetype = (taxisID != -1) ? taxisInqType(taxisID) : -1;
  if (timetype == TAXIS_ABSOLUTE || timetype == TAXIS_RELATIVE)
  {
    IEG_P_Year(pdb) = datetime.date.year;
    IEG_P_Month(pdb) = datetime.date.month;
    IEG_P_Day(pdb) = datetime.date.day;
    IEG_P_Hour(pdb) = datetime.time.hour;
    IEG_P_Minute(pdb) = datetime.time.minute;

    pdb[15] = 1;
    pdb[16] = 0;
    pdb[17] = 0;
    pdb[18] = 10;
    pdb[36] = 1;
  }

  pdb[5] = 128;
}

/* find smallest power of 10 in [1000,10000000] that upon multiplication results in fractional part
   close to zero for all arguments */
static double
calc_resfac(double xfirst, double xlast, double xinc, double yfirst, double ylast, double yinc)
{
  double resfac = 1000.0;
  enum
  {
    nPwrOf10 = 5,
    nMultTests = 6,
  };
  static const double scaleFactors[nPwrOf10] = { 1000, 10000, 100000, 1000000, 10000000 };
  const double vals[nMultTests] = { xfirst, xlast, xinc, yfirst, ylast, yinc };

  for (size_t j = 0; j < nPwrOf10; ++j)
  {
    double scaleBy = scaleFactors[j];
    bool fractionalScale = false;
    for (size_t i = 0; i < nMultTests; ++i)
    {
      fractionalScale = fractionalScale || fabs(vals[i] * scaleBy - round(vals[i] * scaleBy)) > FLT_EPSILON;
    }
    if (!fractionalScale)
    {
      resfac = scaleBy;
      break;
    }
  }

  return resfac;
}

static void
iegDefGrid(int *gdb, int gridID)
{
  int projID = gridInqProj(gridID);
  if (projID != CDI_UNDEFID && gridInqProjType(projID) == CDI_PROJ_RLL) gridID = projID;

  int gridtype = gridInqType(gridID);
  int projtype = CDI_UNDEFID;
  if (gridtype == GRID_PROJECTION && gridInqProjType(gridID) == CDI_PROJ_RLL) projtype = CDI_PROJ_RLL;

  SizeType xsize = gridInqXsize(gridID);
  SizeType ysize = gridInqYsize(gridID);

  cdi_check_gridsize_int_limit("IEG", xsize * ysize);

  if (gridtype == GRID_GENERIC)
  {
    if ((ysize == 32 || ysize == 48 || ysize == 64 || ysize == 96 || ysize == 160) && (xsize == 2 * ysize || xsize == 1))
    {
      gridtype = GRID_GAUSSIAN;
      gridChangeType(gridID, gridtype);
    }
    else if ((xsize == 1 && ysize == 1) || (xsize == 0 && ysize == 0))
    {
      gridtype = GRID_LONLAT;
      gridChangeType(gridID, gridtype);
    }
    else if (gridInqXvals(gridID, NULL) && gridInqYvals(gridID, NULL))
    {
      gridtype = GRID_LONLAT;
      gridChangeType(gridID, gridtype);
    }
  }
  else if (gridtype == GRID_CURVILINEAR) { gridtype = GRID_LONLAT; }

  bool lrotated = (projtype == CDI_PROJ_RLL);

  if (gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || projtype == CDI_PROJ_RLL)
  {
    double xfirst = 0.0, xlast = 0.0, xinc = 0.0;
    double yfirst = 0.0, ylast = 0.0, yinc = 0.0;

    if (xsize == 0)
      xsize = 1;
    else
    {
      xfirst = gridInqXval(gridID, 0);
      xlast = gridInqXval(gridID, xsize - 1);
      xinc = gridInqXinc(gridID);
    }

    if (ysize == 0)
      ysize = 1;
    else
    {
      yfirst = gridInqYval(gridID, 0);
      ylast = gridInqYval(gridID, ysize - 1);
      yinc = gridInqYinc(gridID);
    }

    if (gridtype == GRID_GAUSSIAN)
      IEG_G_GridType(gdb) = 4;
    else if (lrotated)
      IEG_G_GridType(gdb) = 10;
    else
      IEG_G_GridType(gdb) = 0;

    double resfac = calc_resfac(xfirst, xlast, xinc, yfirst, ylast, yinc);
    int iresfac = (int) resfac;
    if (iresfac == 1000) iresfac = 0;

    IEG_G_ResFac(gdb) = iresfac;

    IEG_G_NumLon(gdb) = (int) xsize;
    IEG_G_NumLat(gdb) = (int) ysize;
    IEG_G_FirstLat(gdb) = (int) lround(yfirst * resfac);
    IEG_G_LastLat(gdb) = (int) lround(ylast * resfac);
    IEG_G_FirstLon(gdb) = (int) lround(xfirst * resfac);
    IEG_G_LastLon(gdb) = (int) lround(xlast * resfac);
    IEG_G_LonIncr(gdb) = (int) lround(xinc * resfac);
    if (fabs(xinc * resfac - IEG_G_LonIncr(gdb)) > FLT_EPSILON) IEG_G_LonIncr(gdb) = 0;

    if (gridtype == GRID_GAUSSIAN)
      IEG_G_LatIncr(gdb) = (int) ysize / 2;
    else
    {
      IEG_G_LatIncr(gdb) = (int) lround(yinc * resfac);
      if (fabs(yinc * resfac - IEG_G_LatIncr(gdb)) > FLT_EPSILON) IEG_G_LatIncr(gdb) = 0;

      if (IEG_G_LatIncr(gdb) < 0) IEG_G_LatIncr(gdb) = -IEG_G_LatIncr(gdb);
    }

    if (IEG_G_NumLon(gdb) > 1 && IEG_G_NumLat(gdb) == 1)
      if (IEG_G_LonIncr(gdb) != 0 && IEG_G_LatIncr(gdb) == 0) IEG_G_LatIncr(gdb) = IEG_G_LonIncr(gdb);

    if (IEG_G_NumLon(gdb) == 1 && IEG_G_NumLat(gdb) > 1)
      if (IEG_G_LonIncr(gdb) == 0 && IEG_G_LatIncr(gdb) != 0) IEG_G_LonIncr(gdb) = IEG_G_LatIncr(gdb);

    IEG_G_ResFlag(gdb) = (IEG_G_LatIncr(gdb) == 0 || IEG_G_LonIncr(gdb) == 0) ? 0 : 128;

    if (lrotated)
    {
      double xpole = 0.0, ypole = 0.0, angle = 0.0;
      gridInqParamRLL(gridID, &xpole, &ypole, &angle);

      IEG_G_LatSP(gdb) = -(int) lround(ypole * resfac);
      IEG_G_LonSP(gdb) = (int) lround((xpole + 180) * resfac);
      IEG_G_Size(gdb) = 42;
    }
    else { IEG_G_Size(gdb) = 32; }
  }
  else { Error("Unsupported grid type: %s", gridNamePtr(gridtype)); }

  IEG_G_ScanFlag(gdb) = 64;
}

static void
pdbDefLevel(int *pdb, int leveltype, int level1, int level2)
{
  IEG_P_LevelType(pdb) = leveltype;
  IEG_P_Level1(pdb) = level1;
  IEG_P_Level2(pdb) = level2;
}

static void
iegDefLevel(int *pdb, int *gdb, double *vct, int zaxisID, int levelID)
{
  double level;

  int leveltype = zaxisInqType(zaxisID);
  if (leveltype == ZAXIS_GENERIC)
  {
    Warning("Changed zaxis type from %s to %s", zaxisNamePtr(leveltype), zaxisNamePtr(ZAXIS_PRESSURE));
    leveltype = ZAXIS_PRESSURE;
    zaxisChangeType(zaxisID, leveltype);
    cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, "Pa");
  }

  // IEG_G_NumVCP(gdb) = 0;

  switch (leveltype)
  {
    case ZAXIS_SURFACE:
    {
      pdbDefLevel(pdb, IEG_LTYPE_SURFACE, 0, (int) (zaxisInqLevel(zaxisID, levelID)));
      break;
    }
    case ZAXIS_HYBRID:
    {
      if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
        pdbDefLevel(pdb, IEG_LTYPE_HYBRID_LAYER, (int) (zaxisInqLbound(zaxisID, levelID)),
                    (int) (zaxisInqUbound(zaxisID, levelID)));
      else
        pdbDefLevel(pdb, IEG_LTYPE_HYBRID, 0, (int) (zaxisInqLevel(zaxisID, levelID)));

      int vctsize = zaxisInqVctSize(zaxisID);
      if (vctsize > 100)
      {
        static bool vct_warning = true;
        // IEG_G_NumVCP(gdb) = 0;
        if (vct_warning)
        {
          Warning("VCT size of %d is too large (maximum is 100). Set to 0!", vctsize);
          vct_warning = false;
        }
      }
      else
      {
        IEG_G_Size(gdb) += (vctsize * 4);
        memcpy(vct, zaxisInqVctPtr(zaxisID), (size_t) vctsize / 2 * sizeof(double));
        memcpy(vct + 50, zaxisInqVctPtr(zaxisID) + vctsize / 2, (size_t) vctsize / 2 * sizeof(double));
      }
      break;
    }
    case ZAXIS_PRESSURE:
    {
      level = zaxisInqLevel(zaxisID, levelID);
      if (level < 0) Warning("pressure level of %f Pa is below 0.", level);

      char units[CDI_MAX_NAME];
      int length = CDI_MAX_NAME;
      cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
      if (memcmp(units, "hPa", 3) == 0 || memcmp(units, "mb", 2) == 0) level = level * 100;

      double dum;
      int ilevel = (int) level;
      if (level < 32768 && (level < 100 || modf(level / 100, &dum) > 0))
        pdbDefLevel(pdb, IEG_LTYPE_99, 0, ilevel);
      else
        pdbDefLevel(pdb, IEG_LTYPE_ISOBARIC, 0, ilevel / 100);

      break;
    }
    case ZAXIS_HEIGHT:
    {
      level = zaxisInqLevel(zaxisID, levelID);
      pdbDefLevel(pdb, IEG_LTYPE_HEIGHT, 0, (int) level);
      break;
    }
    case ZAXIS_ALTITUDE:
    {
      level = zaxisInqLevel(zaxisID, levelID);
      pdbDefLevel(pdb, IEG_LTYPE_ALTITUDE, 0, (int) level);
      break;
    }
    case ZAXIS_DEPTH_BELOW_LAND:
    {
      if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
        pdbDefLevel(pdb, IEG_LTYPE_LANDDEPTH_LAYER, (int) (zaxisInqLbound(zaxisID, levelID)),
                    (int) (zaxisInqUbound(zaxisID, levelID)));
      else
        pdbDefLevel(pdb, IEG_LTYPE_LANDDEPTH, 0, (int) (zaxisInqLevel(zaxisID, levelID)));

      break;
    }
    case ZAXIS_DEPTH_BELOW_SEA:
    {
      level = zaxisInqLevel(zaxisID, levelID);
      pdbDefLevel(pdb, IEG_LTYPE_SEADEPTH, 0, (int) level);
      break;
    }
    case ZAXIS_ISENTROPIC:
    {
      level = zaxisInqLevel(zaxisID, levelID);
      pdbDefLevel(pdb, 113, 0, (int) level);
      break;
    }
    default:
    {
      Error("Unsupported zaxis type: %s", zaxisNamePtr(leveltype));
      break;
    }
  }
}

void
iegCopyField(stream_t *streamptr2, stream_t *streamptr1)
{
  streamFCopyRecord(streamptr2, streamptr1, "IEG");
}

void
iegDefField(stream_t *streamptr)
{
  Record *record = streamptr->record;

  int vlistID = streamptr->vlistID;
  int byteorder = streamptr->byteorder;

  int varID = record->varID;
  int levelID = record->levelID;
  int tsID = streamptr->curTsID;
  CdiDateTime vDateTime = streamptr->tsteps[tsID].taxis.vDateTime;

  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);

  iegrec_t *iegp = (iegrec_t *) record->objectp;
  iegInitMem(iegp);
  for (int i = 0; i < 37; i++) iegp->ipdb[i] = -1;

  iegp->byteswap = getByteswap(byteorder);

  int param = vlistInqVarParam(vlistID, varID);
  int pdis, pcat, pnum;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);
  IEG_P_Parameter(iegp->ipdb) = pnum;
  if (pdis == 255) IEG_P_CodeTable(iegp->ipdb) = pcat;

  iegDefTime(iegp->ipdb, vDateTime, vlistInqTaxis(vlistID));
  iegDefGrid(iegp->igdb, gridID);
  iegDefLevel(iegp->ipdb, iegp->igdb, iegp->vct, zaxisID, levelID);

  iegp->dprec = iegDefDatatype(record->prec);
}

static double
calc_refvalSP(size_t size, const float *data)
{
  float refval = data[0];
  for (size_t i = 1; i < size; ++i)
    if (data[i] < refval) refval = data[i];
  return refval;
}

static double
calc_refvalDP(size_t size, const double *data)
{
  double refval = data[0];
  for (size_t i = 1; i < size; ++i)
    if (data[i] < refval) refval = data[i];
  return refval;
}

static void
ieg_write_recordSP(stream_t *streamptr, const float *data)
{
  Record *record = streamptr->record;
  iegrec_t *iegp = (iegrec_t *) record->objectp;

  int fileID = streamptr->fileID;
  size_t gridsize = (size_t) gridInqSize(record->gridID);

  iegp->refval = calc_refvalSP(gridsize, data);

  iegDefDataFP32(iegp, data);
  iegWrite(fileID, iegp);
}

static void
ieg_write_recordDP(stream_t *streamptr, const double *data)
{
  Record *record = streamptr->record;
  iegrec_t *iegp = (iegrec_t *) record->objectp;

  int fileID = streamptr->fileID;
  size_t gridsize = (size_t) gridInqSize(record->gridID);

  iegp->refval = calc_refvalDP(gridsize, data);

  iegDefDataFP64(iegp, data);
  iegWrite(fileID, iegp);
}

void
ieg_write_field(stream_t *streamptr, int memtype, const void *data)
{
  if (memtype == MEMTYPE_DOUBLE)
    ieg_write_recordDP(streamptr, (const double *) data);
  else
    ieg_write_recordSP(streamptr, (const float *) data);
}

static void
iegAddRecord(stream_t *streamptr, int param, int *pdb, int *gdb, double *vct, size_t recsize, off_t position, int prec)
{
  int vlistID = streamptr->vlistID;
  int tsID = streamptr->curTsID;
  int recID = recordNewEntry(streamptr, tsID);
  recinfo_t *recinfo = &(streamptr->tsteps[tsID].recinfo[recID]);
  record_t *record = &(streamptr->tsteps[tsID].records[recID]);

  int level1, level2;
  if (IEG_P_LevelType(pdb) == IEG_LTYPE_HYBRID_LAYER)
  {
    level1 = IEG_P_Level1(pdb);
    level2 = IEG_P_Level2(pdb);
  }
  else
  {
    level1 = IEG_P_Level2(pdb);
    level2 = 0;
    if (IEG_P_LevelType(pdb) == 100) level1 *= 100;
  }

  record->size = recsize;
  record->position = position;
  record->param = param;
  record->ilevel = level1;
  record->ilevel2 = level2;
  record->ltype = (short) IEG_P_LevelType(pdb);

  int gridtype = (IEG_G_GridType(gdb) == 0)    ? GRID_LONLAT
                 : (IEG_G_GridType(gdb) == 10) ? GRID_PROJECTION
                 : (IEG_G_GridType(gdb) == 4)  ? GRID_GAUSSIAN
                                               : GRID_GENERIC;

  grid_t *grid = (grid_t *) Malloc(sizeof(*grid));
  grid_init(grid);
  size_t xsize = (size_t) IEG_G_NumLon(gdb);
  size_t ysize = (size_t) IEG_G_NumLat(gdb);
  cdiGridTypeInit(grid, gridtype, xsize * ysize);
  grid->x.size = xsize;
  grid->y.size = ysize;
  grid->x.inc = 0;
  grid->y.inc = 0;
  grid->x.flag = 0;

  int iresfac = IEG_G_ResFac(gdb);
  if (iresfac == 0) iresfac = 1000;
  double resfac = 1. / (double) iresfac;

  // if ( IEG_G_FirstLon != 0 || IEG_G_LastLon != 0 )
  {
    if (xsize > 1)
    {
      if (IEG_G_ResFlag(gdb) && IEG_G_LonIncr(gdb) > 0)
        grid->x.inc = IEG_G_LonIncr(gdb) * resfac;
      else
        grid->x.inc = (IEG_G_LastLon(gdb) - IEG_G_FirstLon(gdb)) * resfac / ((double) xsize - 1);

      // correct xinc if necessary
      if (IEG_G_FirstLon(gdb) == 0 && IEG_G_LastLon(gdb) > 354000)
      {
        double xinc = 360. / (double) xsize;
        // FIXME: why not use grid->x.inc != xinc as condition?
        if (fabs(grid->x.inc - xinc) > 0.0)
        {
          grid->x.inc = xinc;
          if (CDI_Debug) Message("set xinc to %g", grid->x.inc);
        }
      }
    }
    grid->x.first = IEG_G_FirstLon(gdb) * resfac;
    grid->x.last = IEG_G_LastLon(gdb) * resfac;
    grid->x.flag = 2;
  }
  grid->y.flag = 0;
  // if ( IEG_G_FirstLat != 0 || IEG_G_LastLat != 0 )
  {
    if (ysize > 1)
    {
      if (IEG_G_ResFlag(gdb) && IEG_G_LatIncr(gdb) > 0)
        grid->y.inc = IEG_G_LatIncr(gdb) * resfac;
      else
        grid->y.inc = (IEG_G_LastLat(gdb) - IEG_G_FirstLat(gdb)) * resfac / (double) (ysize - 1);
    }
    grid->y.first = IEG_G_FirstLat(gdb) * resfac;
    grid->y.last = IEG_G_LastLat(gdb) * resfac;
    grid->y.flag = 2;
  }

  double xpole = 0, ypole = 0;
  if (IEG_G_GridType(gdb) == 10)
  {
    xpole = IEG_G_LonSP(gdb) * resfac - 180;
    ypole = -IEG_G_LatSP(gdb) * resfac;
    grid->projtype = CDI_PROJ_RLL;
  }

  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, grid, 0);
  int gridID = gridAdded.Id;
  if (!gridAdded.isNew)
  {
    grid_free(grid);
    Free(grid);
  }
  else if (gridtype == GRID_PROJECTION)
    gridDefParamRLL(gridID, xpole, ypole, 0);

  int leveltype = iegGetZaxisType(IEG_P_LevelType(pdb));
  if (leveltype == ZAXIS_HYBRID)
  {
    double tmpvct[100];
    size_t vctsize = (size_t) IEG_G_NumVCP(gdb);

    for (size_t i = 0; i < vctsize / 2; i++) tmpvct[i] = vct[i];
    for (size_t i = 0; i < vctsize / 2; i++) tmpvct[i + vctsize / 2] = vct[i + 50];

    varDefVCT(vctsize, tmpvct);
  }

  int lbounds = IEG_P_LevelType(pdb) == IEG_LTYPE_HYBRID_LAYER ? 1 : 0;

  int datatype = iegInqDatatype(prec);

  int varID, levelID = 0;
  varAddRecord(recID, param, gridID, leveltype, lbounds, level1, level2, 0, 0, datatype, &varID, &levelID, TSTEP_INSTANT, 0, -1,
               NULL, NULL, NULL, NULL);

  recinfo->varID = (short) varID;
  recinfo->levelID = levelID;

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;

  if (CDI_Debug) Message("varID = %d gridID = %d levelID = %d", varID, gridID, levelID);
}

#if 0
static
void iegCmpRecord(stream_t *streamptr, int tsID, int recID, off_t position, int param,
		  int level, size_t xsize, size_t ysize)
{
  int varID = 0;
  int levelID = 0;

  record_t *record  = &streamptr->tsteps[tsID].records[recID];

  if (param != (*record).param || level != (*record).ilevel) Error("inconsistent timestep");

  (*record).position = position;
  /*
  varID   = (*record).varID;
  levelID = (*record).levelID;

  streamptr->vars[varID].level[levelID] = recID;

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;
  */
  if (CDI_Debug) Message("varID = %d levelID = %d", varID, levelID);
}
#endif

static CdiDateTime
iegDateTime(const int *pdb)
{
  int ryear = IEG_P_Year(pdb);
  int rmonth = IEG_P_Month(pdb);
  int rday = IEG_P_Day(pdb);

  int rhour = IEG_P_Hour(pdb);
  int rminute = IEG_P_Minute(pdb);

  if (rminute == -1) rminute = 0;

  CdiDateTime cdiDateTime;
  cdiDateTime.date = cdiDate_encode(ryear, rmonth, rday);
  cdiDateTime.time = cdiTime_encode(rhour, rminute, 0, 0);
  return cdiDateTime;
}

static void
iegScanTimestep1(stream_t *streamptr)
{
  CdiDateTime datetime0;
  cdiDateTime_init(&datetime0);
  off_t recpos;
  iegrec_t *iegp = (iegrec_t *) streamptr->record->objectp;

  streamptr->curTsID = 0;

  int tsID = tstepsNewEntry(streamptr);
  if (tsID != 0) Error("Internal problem! tstepsNewEntry returns %d", tsID);
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  int fileID = streamptr->fileID;

  int nrecs = 0;
  while (true)
  {
    recpos = fileGetPos(fileID);
    if (iegRead(fileID, iegp) != 0)
    {
      streamptr->ntsteps = 1;
      break;
    }

    size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

    int prec = iegp->dprec;
    int rcode = IEG_P_Parameter(iegp->ipdb);
    int tabnum = IEG_P_CodeTable(iegp->ipdb);
    int param = cdiEncodeParam(rcode, tabnum, 255);
    int rlevel = (IEG_P_LevelType(iegp->ipdb) == IEG_LTYPE_HYBRID_LAYER) ? IEG_P_Level1(iegp->ipdb) : IEG_P_Level2(iegp->ipdb);

    if (IEG_P_LevelType(iegp->ipdb) == 100) rlevel *= 100;

    CdiDateTime datetime = iegDateTime(iegp->ipdb);

    if (nrecs == 0)
    {
      datetime0 = datetime;
      taxis->vDateTime = datetime;
    }
    else
    {
      record_t *records = streamptr->tsteps[tsID].records;
      for (int recID = 0; recID < nrecs; recID++)
        if (param == records[recID].param && rlevel == records[recID].ilevel) goto tstepScanLoopFinished;

      if (cdiDateTime_isNE(datetime, datetime0)) Warning("Inconsistent verification time for param %d level %d", param, rlevel);
    }

    nrecs++;

    if (CDI_Debug) Message("%4d%8d%4d%8d %s", nrecs, (int) recpos, param, rlevel, CdiDateTime_string(datetime));

    iegAddRecord(streamptr, param, iegp->ipdb, iegp->igdb, iegp->vct, recsize, recpos, prec);
  }

tstepScanLoopFinished:
  streamptr->rtsteps = 1;

  cdi_generate_vars(streamptr);

  int taxisID = taxisCreate(TAXIS_ABSOLUTE);
  taxis->type = TAXIS_ABSOLUTE;
  taxis->rDateTime = taxis->vDateTime;

  int vlistID = streamptr->vlistID;
  vlistDefTaxis(vlistID, taxisID);

  vlist_check_contents(vlistID);

  streamScanResizeRecords1(streamptr);

  streamScanTsFixNtsteps(streamptr, recpos);
  streamScanTimeConstAdjust(streamptr, taxis);
}

static int
iegScanTimestep2(stream_t *streamptr)
{
  off_t recpos = 0;
  iegrec_t *iegp = (iegrec_t *) streamptr->record->objectp;

  streamptr->curTsID = 1;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  int tsID = streamptr->rtsteps;
  if (tsID != 1) Error("Internal problem! unexpected timestep %d", tsID + 1);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

  cdi_create_records(streamptr, tsID, true);
  recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
  record_t *records = streamptr->tsteps[tsID].records;

  int nrecords = streamScanInitRecords2(streamptr);

  for (int rindex = 0; rindex <= nrecords; rindex++)
  {
    recpos = fileGetPos(fileID);
    if (iegRead(fileID, iegp) != 0)
    {
      streamptr->ntsteps = 2;
      break;
    }

    size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

    int rcode = IEG_P_Parameter(iegp->ipdb);
    int tabnum = IEG_P_CodeTable(iegp->ipdb);
    int param = cdiEncodeParam(rcode, tabnum, 255);
    int rlevel = (IEG_P_LevelType(iegp->ipdb) == IEG_LTYPE_HYBRID_LAYER) ? IEG_P_Level1(iegp->ipdb) : IEG_P_Level2(iegp->ipdb);

    if (IEG_P_LevelType(iegp->ipdb) == 100) rlevel *= 100;

    CdiDateTime datetime = iegDateTime(iegp->ipdb);

    if (rindex == 0)
    {
      taxis->type = TAXIS_ABSOLUTE;
      taxis->vDateTime = datetime;
    }

    bool nextstep = false;
    int recID = 0;
    for (recID = 0; recID < nrecords; recID++)
    {
      if (param == records[recID].param && rlevel == records[recID].ilevel)
      {
        if (recinfo[recID].used) { nextstep = true; }
        else
        {
          recinfo[recID].used = true;
          streamptr->tsteps[tsID].recIDs[rindex] = recID;
        }
        break;
      }
    }
    if (recID == nrecords)
    {
      char paramstr[32];
      cdiParamToString(param, paramstr, sizeof(paramstr));
      Warning("param %s level %d not defined at timestep 1", paramstr, rlevel);
      return CDI_EUFSTRUCT;
    }

    if (nextstep) break;

    if (CDI_Debug) Message("%4d%8d%4d%8d %s", rindex + 1, (int) recpos, param, rlevel, CdiDateTime_string(datetime));

    if (param != records[recID].param || rlevel != records[recID].ilevel)
    {
      Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
              records[recID].ilevel, rlevel);
      return CDI_EUFSTRUCT;
    }

    records[recID].position = recpos;
    records[recID].size = recsize;
  }

  int nrecs = 0;
  for (int recID = 0; recID < nrecords; recID++)
  {
    if (recinfo[recID].used)
      nrecs++;
    else
      vlistDefVarTimetype(vlistID, recinfo[recID].varID, TIME_CONSTANT);
  }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  return 0;
}

int
iegInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  iegScanTimestep1(streamptr);

  int status = (streamptr->ntsteps == -1) ? iegScanTimestep2(streamptr) : 0;

  fileSetPos(streamptr->fileID, 0, SEEK_SET);

  return status;
}

static long
iegScanTimestep(stream_t *streamptr)
{
  off_t recpos = 0;
  iegrec_t *iegp = (iegrec_t *) streamptr->record->objectp;

  if (streamptr->rtsteps == 0) Error("Internal problem! Missing contents.");

  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  int nrecs = 0;
  if (streamptr->tsteps[tsID].recordSize == 0)
  {
    cdi_create_records(streamptr, tsID, true);
    record_t *records = streamptr->tsteps[tsID].records;

    nrecs = streamScanInitRecords(streamptr, tsID);

    int fileID = streamptr->fileID;

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

    for (int rindex = 0; rindex <= nrecs; rindex++)
    {
      recpos = fileGetPos(fileID);
      if (iegRead(fileID, iegp) != 0)
      {
        streamptr->ntsteps = streamptr->rtsteps + 1;
        break;
      }

      size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

      int rcode = IEG_P_Parameter(iegp->ipdb);
      int tabnum = IEG_P_CodeTable(iegp->ipdb);
      int param = cdiEncodeParam(rcode, tabnum, 255);
      int rlevel = (IEG_P_LevelType(iegp->ipdb) == IEG_LTYPE_HYBRID_LAYER) ? IEG_P_Level1(iegp->ipdb) : IEG_P_Level2(iegp->ipdb);

      if (IEG_P_LevelType(iegp->ipdb) == 100) rlevel *= 100;

      CdiDateTime datetime = iegDateTime(iegp->ipdb);

      // if ( rindex == nrecs ) break; gcc-4.5 internal compiler error
      if (rindex == nrecs) continue;
      int recID = streamptr->tsteps[tsID].recIDs[rindex];

      if (rindex == 0)
      {
        taxis->type = TAXIS_ABSOLUTE;
        taxis->vDateTime = datetime;
      }

      if (param != records[recID].param || rlevel != records[recID].ilevel)
      {
        Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
                records[recID].ilevel, rlevel);
        Error("Invalid, unsupported or inconsistent record structure");
      }

      records[recID].position = recpos;
      records[recID].size = recsize;

      if (CDI_Debug) Message("%4d%8d%4d%8d %s", rindex, (int) recpos, param, rlevel, CdiDateTime_string(datetime));
    }

    streamptr->rtsteps++;

    if (streamptr->ntsteps != streamptr->rtsteps)
    {
      tsID = tstepsNewEntry(streamptr);
      if (tsID != streamptr->rtsteps) Error("Internal error. tsID = %d", tsID);

      streamptr->tsteps[tsID - 1].next = true;
      streamptr->tsteps[tsID].position = recpos;
    }

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);
    streamptr->tsteps[tsID].position = recpos;
  }

  if (nrecs > 0 && nrecs < streamptr->tsteps[tsID].nrecs)
  {
    Warning("Incomplete timestep. Stop scanning at timestep %d.", tsID);
    streamptr->ntsteps = tsID;
  }

  return streamptr->ntsteps;
}

int
iegInqTimestep(stream_t *streamptr, int tsID)
{
  if (tsID == 0 && streamptr->rtsteps == 0) Error("Call to cdiInqContents missing!");

  if (CDI_Debug) Message("tsID = %d rtsteps = %d", tsID, streamptr->rtsteps);

  long ntsteps = CDI_UNDEFID;
  while ((tsID + 1) > streamptr->rtsteps && ntsteps == CDI_UNDEFID) ntsteps = iegScanTimestep(streamptr);

  int nrecs = 0;
  if (!(tsID >= streamptr->ntsteps && streamptr->ntsteps != CDI_UNDEFID))
  {
    streamptr->curTsID = tsID;
    nrecs = streamptr->tsteps[tsID].nrecs;
  }

  return nrecs;
}

void
iegReadVarSliceDP(stream_t *streamptr, int varID, int levID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  void *iegp = streamptr->record->objectp;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  double missval = vlistInqVarMissval(vlistID, varID);
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  int tsid = streamptr->curTsID;

  off_t currentfilepos = fileGetPos(fileID);

  int recID = streamptr->vars[varID].recordTable[0].recordID[levID];
  off_t recpos = streamptr->tsteps[tsid].records[recID].position;
  fileSetPos(fileID, recpos, SEEK_SET);
  iegRead(fileID, iegp);
  iegInqDataFP64(iegp, data);

  fileSetPos(fileID, currentfilepos, SEEK_SET);

  *numMissVals = get_num_missvalsDP(gridsize, data, missval);
}

void
iegReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevs = (size_t) streamptr->vars[varID].recordTable[0].nlevs;

  for (size_t levID = 0; levID < nlevs; levID++)
    iegReadVarSliceDP(streamptr, varID, (int) levID, &data[levID * gridsize], numMissVals);
}

void
iegWriteVarSliceDP(stream_t *streamptr, int varID, int levID, const double *data)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  iegrec_t *iegp = (iegrec_t *) streamptr->record->objectp;
  iegInitMem(iegp);
  for (int i = 0; i < 37; i++) iegp->ipdb[i] = -1;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;
  CdiDateTime vDateTime = streamptr->tsteps[tsID].taxis.vDateTime;
  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);

  int param = vlistInqVarParam(vlistID, varID);
  int pdis, pcat, pnum;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);
  IEG_P_Parameter(iegp->ipdb) = pnum;
  if (pdis == 255) IEG_P_CodeTable(iegp->ipdb) = pcat;

  iegDefTime(iegp->ipdb, vDateTime, vlistInqTaxis(vlistID));
  iegDefGrid(iegp->igdb, gridID);
  iegDefLevel(iegp->ipdb, iegp->igdb, iegp->vct, zaxisID, levID);

  iegp->dprec = iegDefDatatype(vlistInqVarDatatype(vlistID, varID));

  size_t gridsize = (size_t) gridInqSize(gridID);
  iegp->refval = calc_refvalDP(gridsize, data);

  iegDefDataFP64(iegp, data);
  iegWrite(fileID, iegp);
}

void
iegWriteVarDP(stream_t *streamptr, int varID, const double *data)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevels = (size_t) zaxisInqSize(vlistInqVarZaxis(vlistID, varID));

  for (size_t levID = 0; levID < nlevels; levID++) iegWriteVarSliceDP(streamptr, varID, (int) levID, &data[levID * gridsize]);
}

#endif /* HAVE_LIBIEG */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <limits.h>
#include <stdio.h>
#include <string.h>



void
recinfoInitEntry(recinfo_t *recinfo)
{
  recinfo->varID = CDI_UNDEFID;
  recinfo->levelID = CDI_UNDEFID;
  recinfo->used = false;
}

static void
recordInitEntry(record_t *record)
{
  record->position = CDI_UNDEFID;
  record->size = 0;
  record->gridsize = 0;
  record->param = 0;
  record->ilevel = CDI_UNDEFID;
  record->tsteptype = CDI_UNDEFID;
#ifdef HAVE_LIBGRIB
  varScanKeysInit(&record->scanKeys);
  memset(&record->tiles, 0, sizeof(record->tiles));
#ifdef HAVE_LIBGRIB_API
  memset(record->varname, 0, sizeof(record->varname));
#endif
#endif
#ifdef HAVE_LIBFDB5
  record->fdbItemIndex = -1;
#endif
}

int
recordNewEntry(stream_t *streamptr, int tsID)
{
  int recordSize = streamptr->tsteps[tsID].recordSize;
  recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
  record_t *records = streamptr->tsteps[tsID].records;

  // Look for a free slot in record.
  int recordID = 0;
  if (recordSize)
  {
    while (recordID < recordSize && recinfo[recordID].used != CDI_UNDEFID) ++recordID;
  }
  else  // Create the table the first time through.
  {
    recordSize = 1;  //  <<<<----
    recinfo = (recinfo_t *) Malloc((size_t) recordSize * sizeof(recinfo_t));
    records = (record_t *) Malloc((size_t) recordSize * sizeof(record_t));
    for (int i = recordID; i < recordSize; i++) recinfo[i].used = CDI_UNDEFID;
  }

  // If the table overflows, double its size.
  if (recordID == recordSize)
  {
    // clang-format off
    if      (recordSize <= INT_MAX / 2) recordSize *= 2;
    else if (recordSize < INT_MAX)      recordSize = INT_MAX;
    else Error("Cannot handle this many records!\n");
    // clang-format on
    recinfo = (recinfo_t *) Realloc(recinfo, (size_t) recordSize * sizeof(recinfo_t));
    records = (record_t *) Realloc(records, (size_t) recordSize * sizeof(record_t));

    for (int i = recordID; i < recordSize; i++) recinfo[i].used = CDI_UNDEFID;
  }

  recinfoInitEntry(&recinfo[recordID]);
  recordInitEntry(&records[recordID]);

  recinfo[recordID].used = true;

  streamptr->tsteps[tsID].recordSize = recordSize;
  streamptr->tsteps[tsID].recinfo = recinfo;
  streamptr->tsteps[tsID].records = records;

  return recordID;
}

static void
cdiInitRecord(stream_t *streamptr)
{
  Record *record = (Record *) Malloc(sizeof(Record));
  streamptr->record = record;

  record->param = 0;
  record->ilevel = 0;
  record->vdate = 0;
  record->vtime = 0;
  record->gridID = 0;
  record->buffer = NULL;
  record->buffersize = 0;
  record->position = 0;
  record->varID = 0;
  record->levelID = CDI_UNDEFID;
}

void
stream_inq_field(stream_t *streamPtr, int *varID, int *levelID)
{
  check_parg(varID);
  check_parg(levelID);

  stream_def_accesstype(streamPtr, TYPE_REC);

  if (!streamPtr->record) cdiInitRecord(streamPtr);

  const int tsID = streamPtr->curTsID;
  tsteps_t *tstep = &(streamPtr->tsteps[tsID]);

  int rindex = tstep->curRecID + 1;
  if (rindex >= tstep->nrecs) Error("record %d not available at timestep %d", rindex + 1, tsID + 1);

  int recID = tstep->recIDs[rindex];
  if (recID == -1 || recID >= tstep->nallrecs) Error("Internal problem! tsID = %d recID = %d", tsID, recID);

  *varID = tstep->recinfo[recID].varID;
  if (*varID == -1) Error("Internal problem! varID = %d recID = %d", *varID, recID);

  int lindex = tstep->recinfo[recID].levelID;
  int isub = subtypeInqActiveIndex(streamPtr->vars[*varID].subtypeID);
  *levelID = streamPtr->vars[*varID].recordTable[isub].lindex[lindex];

  if (CDI_Debug)
    Message("streamID = %d tsID = %d, recID = %d, varID = %d, levelID = %d", streamPtr->self, tsID, recID, *varID, *levelID);

  streamPtr->curTsID = tsID;
  tstep->curRecID = rindex;
}

void
pstreamInqField(void *streamPtr, int *varID, int *levelID)
{
  stream_inq_field((stream_t *) streamPtr, varID, levelID);
}

void
streamInqField(int streamID, int *varID, int *levelID)
{
  stream_inq_field(stream_to_pointer(streamID), varID, levelID);
}

/*
@Function  streamDefRecord
@Title     Define the next record

@Prototype void streamDefRecord(int streamID, int varID, int levelID)
@Parameter
    @Item  streamID  Stream ID, from a previous call to @fref{streamOpenWrite}.
    @Item  varID     Variable identifier.
    @Item  levelID   Level identifier.

@Description
The function streamDefRecord defines the meta-data of the next record.
@EndFunction
*/
void
streamDefField(int streamID, int varID, int levelID)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  int tsID = streamptr->curTsID;
  if (tsID == CDI_UNDEFID)
  {
    tsID++;
    streamDefTimestep(streamID, tsID);
  }

  if (!streamptr->record) cdiInitRecord(streamptr);

  int vlistID = streamptr->vlistID;
  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);
  int param = vlistInqVarParam(vlistID, varID);
  int ilevel = (int) lround(zaxisInqLevel(zaxisID, levelID));

  Record *record = streamptr->record;
  record->varID = varID;
  record->levelID = levelID;
  record->param = param;
  record->ilevel = ilevel;
  record->vdate = (int) cdiDate_get(streamptr->tsteps[tsID].taxis.vDateTime.date);
  record->vtime = cdiTime_get(streamptr->tsteps[tsID].taxis.vDateTime.time);
  record->gridID = gridID;
  record->prec = vlistInqVarDatatype(vlistID, varID);

  switch (cdiBaseFiletype(streamptr->filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grbDefField(streamptr); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srvDefField(streamptr); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: extDefField(streamptr); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: iegDefField(streamptr); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      if (streamptr->accessmode == 0) cdfEndDef(streamptr);
      cdfDefField(streamptr);
      break;
#endif
    default: Error("%s support not compiled in!", strfiletype(streamptr->filetype)); break;
  }
}

void
streamCopyField(int streamID2, int streamID1)
{
  stream_t *streamptr1 = stream_to_pointer(streamID1);
  stream_t *streamptr2 = stream_to_pointer(streamID2);
  int filetype1 = streamptr1->filetype;
  int filetype2 = streamptr2->filetype;
  int filetype = CDI_FILETYPE_UNDEF;

  if (cdiBaseFiletype(filetype1) == cdiBaseFiletype(filetype2)) filetype = filetype2;

  if (filetype == CDI_FILETYPE_UNDEF)
    Error("Streams have different file types (%s -> %s)!", strfiletype(filetype1), strfiletype(filetype2));

  switch (cdiBaseFiletype(filetype))
  {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRIB: grbCopyField(streamptr2, streamptr1); break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV: srvCopyField(streamptr2, streamptr1); break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT: extCopyField(streamptr2, streamptr1); break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG: iegCopyField(streamptr2, streamptr1); break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF: cdfCopyField(streamptr2, streamptr1); break;
#endif
    default: Error("%s support not compiled in!", strfiletype(filetype));
  }
}

void
cdi_create_records(stream_t *streamptr, int tsID, bool allocRecords)
{
  unsigned nrecords, maxrecords;

  tsteps_t *sourceTstep = streamptr->tsteps;
  tsteps_t *destTstep = sourceTstep + tsID;

  if (destTstep->recinfo) return;

  int vlistID = streamptr->vlistID;

  if (tsID == 0)
  {
    maxrecords = 0;
    int nvars = streamptr->nvars;
    for (int varID = 0; varID < nvars; varID++)
      for (int isub = 0; isub < streamptr->vars[varID].subtypeSize; isub++)
        maxrecords += (unsigned) streamptr->vars[varID].recordTable[isub].nlevs;
  }
  else { maxrecords = (unsigned) sourceTstep->recordSize; }

  if (tsID == 0) { nrecords = maxrecords; }
  else if (tsID == 1)
  {
    nrecords = 0;
    maxrecords = (unsigned) sourceTstep->recordSize;
    if (sourceTstep->records)
    {
      for (size_t recID = 0; recID < maxrecords; recID++)
      {
        int varID = sourceTstep->recinfo[recID].varID;
        nrecords += (varID == CDI_UNDEFID /* varID = CDI_UNDEFID for write mode !!! */
                     || vlistInqVarTimetype(vlistID, varID) != TIME_CONSTANT);
        //    printf("varID nrecords %d %d %d \n", varID, nrecords, vlistInqVarTsteptype(vlistID, varID));
      }
    }
    else { nrecords = maxrecords; }
  }
  else { nrecords = (unsigned) streamptr->tsteps[1].nallrecs; }
  //  printf("tsID, nrecords %d %d\n", tsID, nrecords);

  recinfo_t *recinfo = (maxrecords > 0) ? (recinfo_t *) Malloc(maxrecords * sizeof(recinfo_t)) : (recinfo_t *) NULL;
  record_t *records = (allocRecords && maxrecords > 0) ? (record_t *) Malloc(maxrecords * sizeof(record_t)) : (record_t *) NULL;

  destTstep->recinfo = recinfo;
  destTstep->records = records;
  destTstep->recordSize = (int) maxrecords;
  destTstep->nallrecs = (int) nrecords;
#ifdef HAVE_LIBFDB5
  if (destTstep->records) destTstep->records->fdbItemIndex = -1;
#endif

  if (tsID == 0)
  {
    for (unsigned recID = 0; recID < maxrecords; recID++) recinfoInitEntry(&destTstep->recinfo[recID]);
    if (allocRecords)
      for (unsigned recID = 0; recID < maxrecords; recID++) recordInitEntry(&destTstep->records[recID]);
  }
  else if (sourceTstep->recinfo)
  {
    memcpy(destTstep->recinfo, sourceTstep->recinfo, (size_t) maxrecords * sizeof(recinfo_t));
    if (allocRecords) memcpy(destTstep->records, sourceTstep->records, (size_t) maxrecords * sizeof(record_t));

    for (size_t recID = 0; recID < maxrecords; recID++)
    {
      recinfo_t *curRecord = &sourceTstep->recinfo[recID];
      destTstep->recinfo[recID].used = curRecord->used;
      if (curRecord->used != CDI_UNDEFID && curRecord->varID != -1)  // curRecord->varID = -1 for write mode !!!
      {
        if (vlistInqVarTimetype(vlistID, curRecord->varID) != TIME_CONSTANT)
        {
          if (allocRecords) destTstep->records[recID].position = CDI_UNDEFID;
          if (allocRecords) destTstep->records[recID].size = 0;
          destTstep->recinfo[recID].used = false;
        }
      }
    }
  }
}


void
streamFCopyRecord(stream_t *streamptr2, stream_t *streamptr1, const char *container_name)
{
  int fileID1 = streamptr1->fileID;
  int fileID2 = streamptr2->fileID;

  int tsID = streamptr1->curTsID;
  int vrecID = streamptr1->tsteps[tsID].curRecID;
  int recID = streamptr1->tsteps[tsID].recIDs[vrecID];
  off_t recpos = streamptr1->tsteps[tsID].records[recID].position;
  size_t recsize = streamptr1->tsteps[tsID].records[recID].size;

  if (fileSetPos(fileID1, recpos, SEEK_SET) != 0) Error("Cannot seek input file for %s record copy!", container_name);

  char *buffer = (char *) Malloc(recsize);

  if (fileRead(fileID1, buffer, recsize) != recsize) Error("Failed to read record from %s file for copying!", container_name);

  if (fileWrite(fileID2, buffer, recsize) != recsize) Error("Failed to write record to %s file when copying!", container_name);

  Free(buffer);
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif



#ifdef HAVE_LIBSERVICE

static int
srvInqDatatype(int prec)
{
  return (prec == EXSE_PREC_FP64) ? CDI_DATATYPE_FLT64 : CDI_DATATYPE_FLT32;
}

static int
srvDefDatatype(int datatype)
{
  if (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64)
    Error("CDI/SERVICE library does not support complex numbers!");

  if (datatype != CDI_DATATYPE_FLT32 && datatype != CDI_DATATYPE_FLT64) datatype = CDI_DATATYPE_FLT32;

  return (datatype == CDI_DATATYPE_FLT64) ? EXSE_PREC_FP64 : EXSE_PREC_FP32;
}

static void
srv_read_recordSP(stream_t *streamptr, float *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;

  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *srvp = streamptr->record->objectp;
  if (srvRead(fileID, srvp) < 0) Error("Failed to read record from SRV file");

  int header[8];
  srvInqHeader(srvp, header);
  srvInqDataFP32(srvp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  SizeType size = gridInqSize(vlistInqVarGrid(vlistID, varID));

  *numMissVals = get_num_missvalsSP((size_t) size, data, (float) missval);

  streamptr->numvals += size;
}

static void
srv_read_recordDP(stream_t *streamptr, double *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;

  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *srvp = streamptr->record->objectp;
  if (srvRead(fileID, srvp) < 0) Error("Failed to read record from SRV file");

  int header[8];
  srvInqHeader(srvp, header);
  srvInqDataFP64(srvp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  SizeType size = gridInqSize(vlistInqVarGrid(vlistID, varID));

  *numMissVals = get_num_missvalsDP((size_t) size, data, missval);

  streamptr->numvals += size;
}

void
srv_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals)
{
  if (memtype == MEMTYPE_DOUBLE)
    srv_read_recordDP(streamptr, (double *) data, numMissVals);
  else
    srv_read_recordSP(streamptr, (float *) data, numMissVals);
}

void
srvCopyField(stream_t *streamptr2, stream_t *streamptr1)
{
  streamFCopyRecord(streamptr2, streamptr1, "SRV");
}

void
srvDefField(stream_t *streamptr)
{
  Record *record = streamptr->record;

  int pdis, pcat, pnum;
  cdiDecodeParam(record->param, &pnum, &pcat, &pdis);

  int header[8];
  header[0] = pnum;
  header[1] = record->ilevel;
  header[2] = record->vdate;
  header[3] = record->vtime;

  int gridID = record->gridID;
  SizeType xsize = gridInqXsize(gridID), ysize = gridInqYsize(gridID);
  if (xsize == 0 || ysize == 0)
  {
    xsize = gridInqSize(gridID);
    ysize = 1;
  }
  if (gridInqType(gridID) == GRID_UNSTRUCTURED) ysize = 1;
  if (gridInqSize(gridID) != xsize * ysize) Error("Internal problem with gridsize!");

  cdi_check_gridsize_int_limit("SERVICE", gridInqSize(gridID));

  header[4] = (int) xsize;
  header[5] = (int) ysize;
  header[6] = 0;
  header[7] = 0;

  srvrec_t *srvp = (srvrec_t *) record->objectp;
  srvp->dprec = srvDefDatatype(record->prec);

  srvDefHeader(srvp, header);
}

static void
srv_write_recordSP(stream_t *streamptr, const float *data)
{
  void *srvp = streamptr->record->objectp;
  srvDefDataFP32(srvp, data);
  srvWrite(streamptr->fileID, srvp);
}

static void
srv_write_recordDP(stream_t *streamptr, const double *data)
{
  void *srvp = streamptr->record->objectp;
  srvDefDataFP64(srvp, data);
  srvWrite(streamptr->fileID, srvp);
}

void
srv_write_field(stream_t *streamptr, int memtype, const void *data)
{
  if (memtype == MEMTYPE_DOUBLE)
    srv_write_recordDP(streamptr, (const double *) data);
  else
    srv_write_recordSP(streamptr, (const float *) data);
}

static void
srv_add_record(stream_t *streamptr, int param, int level, size_t xsize, size_t ysize, size_t recsize, off_t position, int prec)
{
  int vlistID = streamptr->vlistID;
  int tsID = streamptr->curTsID;
  int recID = recordNewEntry(streamptr, tsID);
  recinfo_t *recinfo = &(streamptr->tsteps[tsID].recinfo[recID]);
  record_t *record = &(streamptr->tsteps[tsID].records[recID]);

  record->size = recsize;
  record->position = position;
  record->param = param;
  record->ilevel = level;

  grid_t *grid = (grid_t *) Malloc(sizeof(*grid));
  grid_init(grid);
  cdiGridTypeInit(grid, GRID_GENERIC, xsize * ysize);
  grid->x.size = xsize;
  grid->y.size = ysize;
  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, grid, 0);
  int gridID = gridAdded.Id;
  if (!gridAdded.isNew)
  {
    grid_free(grid);
    Free(grid);
  }

  int leveltype = ZAXIS_GENERIC;
  int datatype = srvInqDatatype(prec);

  int varID, levelID = 0;
  varAddRecord(recID, param, gridID, leveltype, 0, level, 0, 0, 0, datatype, &varID, &levelID, TSTEP_INSTANT, 0, -1, NULL, NULL,
               NULL, NULL);

  xassert(varID <= SHRT_MAX && levelID <= SHRT_MAX);
  recinfo->varID = (short) varID;
  recinfo->levelID = levelID;

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;

  if (CDI_Debug) Message("varID = %d gridID = %d levelID = %d", varID, gridID, levelID);
}

static void
srvScanTimestep1(stream_t *streamptr)
{
  CdiDateTime datetime0;
  cdiDateTime_init(&datetime0);
  off_t recpos;
  srvrec_t *srvp = (srvrec_t *) streamptr->record->objectp;

  streamptr->curTsID = 0;

  int tsID = tstepsNewEntry(streamptr);
  if (tsID != 0) Error("Internal problem! tstepsNewEntry returns %d", tsID);
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  int fileID = streamptr->fileID;

  int nrecs = 0;
  while (true)
  {
    recpos = fileGetPos(fileID);
    if (srvRead(fileID, srvp) != 0)
    {
      streamptr->ntsteps = 1;
      break;
    }

    size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

    int header[8];
    srvInqHeader(srvp, header);

    int prec = srvp->dprec;
    int rcode = header[0];
    int rlevel = header[1];
    int vdate = header[2];
    int vtime = header[3];
    int rxsize = header[4];
    int rysize = header[5];
    int param = cdiEncodeParam(rcode, 255, 255);
    CdiDateTime datetime = cdiDateTime_set(vdate, vtime);

    if (nrecs == 0)
    {
      datetime0 = datetime;
      taxis->vDateTime = datetime;
    }
    else
    {
      record_t *records = streamptr->tsteps[tsID].records;
      for (int recID = 0; recID < nrecs; recID++)
        if (param == records[recID].param && rlevel == records[recID].ilevel) goto tstepScanLoopFinished;

      if (cdiDateTime_isNE(datetime, datetime0)) Warning("Inconsistent verification time for code %d level %d", rcode, rlevel);
    }

    nrecs++;

    if (CDI_Debug) Message("%4d%8d%4d%8d%8d%6d", nrecs, (int) recpos, rcode, rlevel, vdate, vtime);

    srv_add_record(streamptr, param, rlevel, (size_t) rxsize, (size_t) rysize, recsize, recpos, prec);
  }

tstepScanLoopFinished:
  streamptr->rtsteps = 1;

  cdi_generate_vars(streamptr);

  int taxisID = taxisCreate(TAXIS_ABSOLUTE);
  taxis->type = TAXIS_ABSOLUTE;
  taxis->rDateTime = taxis->vDateTime;

  int vlistID = streamptr->vlistID;
  vlistDefTaxis(vlistID, taxisID);

  vlist_check_contents(vlistID);

  streamScanResizeRecords1(streamptr);

  streamScanTsFixNtsteps(streamptr, recpos);
  streamScanTimeConstAdjust(streamptr, taxis);
}

static int
srvScanTimestep2(stream_t *streamptr)
{
  int header[8];
  off_t recpos = 0;
  void *srvp = streamptr->record->objectp;

  streamptr->curTsID = 1;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  int tsID = streamptr->rtsteps;
  if (tsID != 1) Error("Internal problem! unexpected timestep %d", tsID + 1);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

  cdi_create_records(streamptr, tsID, true);
  recinfo_t *recinfo = streamptr->tsteps[tsID].recinfo;
  record_t *records = streamptr->tsteps[tsID].records;

  int nrecords = streamScanInitRecords2(streamptr);

  for (int rindex = 0; rindex <= nrecords; rindex++)
  {
    recpos = fileGetPos(fileID);
    if (srvRead(fileID, srvp) != 0)
    {
      streamptr->ntsteps = 2;
      break;
    }

    size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

    srvInqHeader(srvp, header);

    int rcode = header[0];
    int rlevel = header[1];
    int vdate = header[2];
    int vtime = header[3];
    int param = cdiEncodeParam(rcode, 255, 255);

    if (rindex == 0)
    {
      taxis->type = TAXIS_ABSOLUTE;
      taxis->vDateTime = cdiDateTime_set(vdate, vtime);
    }

    bool nextstep = false;
    int recID;
    for (recID = 0; recID < nrecords; recID++)
    {
      if (param == records[recID].param && rlevel == records[recID].ilevel)
      {
        if (recinfo[recID].used) { nextstep = true; }
        else
        {
          recinfo[recID].used = true;
          streamptr->tsteps[tsID].recIDs[rindex] = recID;
        }
        break;
      }
    }
    if (recID == nrecords)
    {
      Warning("Code %d level %d not found at timestep %d", rcode, rlevel, tsID + 1);
      return CDI_EUFSTRUCT;
    }

    if (nextstep) break;

    if (CDI_Debug) Message("%4d%8d%4d%8d%8d%6d", rindex + 1, (int) recpos, rcode, rlevel, vdate, vtime);

    if (param != records[recID].param || rlevel != records[recID].ilevel)
    {
      Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
              records[recID].ilevel, rlevel);
      return CDI_EUFSTRUCT;
    }

    records[recID].position = recpos;
    records[recID].size = recsize;
  }

  int nrecs = 0;
  for (int recID = 0; recID < nrecords; recID++)
  {
    if (recinfo[recID].used)
      nrecs++;
    else
      vlistDefVarTimetype(vlistID, recinfo[recID].varID, TIME_CONSTANT);
  }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  return 0;
}

int
srvInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  srvScanTimestep1(streamptr);

  int status = (streamptr->ntsteps == -1) ? srvScanTimestep2(streamptr) : 0;

  fileSetPos(streamptr->fileID, 0, SEEK_SET);

  return status;
}

static long
srvScanTimestep(stream_t *streamptr)
{
  int header[8];
  off_t recpos = 0;
  int nrecs = 0;
  void *srvp = streamptr->record->objectp;

  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if (streamptr->tsteps[tsID].recordSize == 0)
  {
    cdi_create_records(streamptr, tsID, true);
    record_t *records = streamptr->tsteps[tsID].records;

    nrecs = streamScanInitRecords(streamptr, tsID);

    int fileID = streamptr->fileID;

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

    for (int rindex = 0; rindex <= nrecs; rindex++)
    {
      recpos = fileGetPos(fileID);
      if (srvRead(fileID, srvp) != 0)
      {
        streamptr->ntsteps = streamptr->rtsteps + 1;
        break;
      }

      size_t recsize = (size_t) (fileGetPos(fileID) - recpos);

      srvInqHeader(srvp, header);

      int rcode = header[0];
      int rlevel = header[1];
      int vdate = header[2];
      int vtime = header[3];
      int param = cdiEncodeParam(rcode, 255, 255);

      // if ( rindex == nrecs ) break; gcc-4.5 internal compiler error
      if (rindex == nrecs) continue;
      int recID = streamptr->tsteps[tsID].recIDs[rindex];

      if (rindex == 0)
      {
        taxis->type = TAXIS_ABSOLUTE;
        taxis->vDateTime = cdiDateTime_set(vdate, vtime);
      }

      if (param != records[recID].param || rlevel != records[recID].ilevel)
      {
        Message("tsID = %d recID = %d param = %3d new %3d  level = %3d new %3d", tsID, recID, records[recID].param, param,
                records[recID].ilevel, rlevel);
        Error("Invalid, unsupported or inconsistent record structure!");
      }

      records[recID].position = recpos;
      records[recID].size = recsize;

      if (CDI_Debug) Message("%4d%8d%4d%8d%8d%6d", rindex, (int) recpos, rcode, rlevel, vdate, vtime);
    }

    streamptr->rtsteps++;

    if (streamptr->ntsteps != streamptr->rtsteps)
    {
      tsID = tstepsNewEntry(streamptr);
      if (tsID != streamptr->rtsteps) Error("Internal error. tsID = %d", tsID);

      streamptr->tsteps[tsID - 1].next = true;
      streamptr->tsteps[tsID].position = recpos;
    }

    fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);
    streamptr->tsteps[tsID].position = recpos;
  }

  if (nrecs > 0 && nrecs < streamptr->tsteps[tsID].nrecs)
  {
    Warning("Incomplete timestep. Stop scanning at timestep %d.", tsID);
    streamptr->ntsteps = tsID;
  }

  return streamptr->ntsteps;
}

int
srvInqTimestep(stream_t *streamptr, int tsID)
{
  if (tsID == 0 && streamptr->rtsteps == 0) Error("Call to cdiInqContents missing!");

  if (CDI_Debug) Message("tsID = %d rtsteps = %d", tsID, streamptr->rtsteps);

  long ntsteps = CDI_UNDEFID;
  while ((tsID + 1) > streamptr->rtsteps && ntsteps == CDI_UNDEFID) ntsteps = srvScanTimestep(streamptr);

  int nrecs = 0;
  if (!(tsID >= streamptr->ntsteps && streamptr->ntsteps != CDI_UNDEFID))
  {
    streamptr->curTsID = tsID;
    nrecs = streamptr->tsteps[tsID].nrecs;
  }

  return nrecs;
}

void
srvReadVarSliceDP(stream_t *streamptr, int varID, int levID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsid = streamptr->curTsID;

  off_t currentfilepos = fileGetPos(fileID);

  int recID = streamptr->vars[varID].recordTable[0].recordID[levID];
  off_t recpos = streamptr->tsteps[tsid].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *srvp = streamptr->record->objectp;
  if (srvRead(fileID, srvp) < 0) abort();

  int header[8];
  srvInqHeader(srvp, header);
  srvInqDataFP64(srvp, data);

  fileSetPos(fileID, currentfilepos, SEEK_SET);

  double missval = vlistInqVarMissval(vlistID, varID);
  SizeType size = gridInqSize(vlistInqVarGrid(vlistID, varID));

  *numMissVals = get_num_missvalsDP((size_t) size, data, missval);
}

void
srvReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevs = (size_t) streamptr->vars[varID].recordTable[0].nlevs;

  for (size_t levID = 0; levID < nlevs; levID++)
    srvReadVarSliceDP(streamptr, varID, (int) levID, &data[levID * gridsize], numMissVals);
}

void
srvWriteVarSliceDP(stream_t *streamptr, int varID, int levID, const double *data)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int tsID = streamptr->curTsID;
  CdiDateTime vDateTime = streamptr->tsteps[tsID].taxis.vDateTime;
  int gridID = vlistInqVarGrid(vlistID, varID);

  int pdis, pcat, pnum;
  cdiDecodeParam(vlistInqVarParam(vlistID, varID), &pnum, &pcat, &pdis);

  int header[8];
  header[0] = pnum;
  header[1] = (int) lround(zaxisInqLevel(vlistInqVarZaxis(vlistID, varID), levID));
  header[2] = (int) cdiDate_get(vDateTime.date);
  header[3] = cdiTime_get(vDateTime.time);

  SizeType xsize = gridInqXsize(gridID), ysize = gridInqYsize(gridID);
  if (xsize == 0 || ysize == 0)
  {
    xsize = gridInqSize(gridID);
    ysize = 1;
  }
  if (gridInqType(gridID) == GRID_UNSTRUCTURED) ysize = 1;
  if (gridInqSize(gridID) != xsize * ysize) Error("Internal problem with gridsize!");

  cdi_check_gridsize_int_limit("SERVICE", gridInqSize(gridID));

  header[4] = (int) xsize;
  header[5] = (int) ysize;
  header[6] = 0;
  header[7] = 0;

  int datatype = vlistInqVarDatatype(vlistID, varID);

  srvrec_t *srvp = (srvrec_t *) streamptr->record->objectp;
  srvp->dprec = srvDefDatatype(datatype);

  srvDefHeader(srvp, header);
  srvDefDataFP64(srvp, data);
  srvWrite(fileID, srvp);
}

void
srvWriteVarDP(stream_t *streamptr, int varID, const double *data)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  SizeType gridsize = gridInqSize(vlistInqVarGrid(vlistID, varID));
  int nlevs = zaxisInqSize(vlistInqVarZaxis(vlistID, varID));

  for (int levID = 0; levID < nlevs; levID++)
    srvWriteVarSliceDP(streamptr, varID, levID, data + (size_t) levID * (size_t) gridsize);
}

#endif /* HAVE_LIBSERVICE */

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */



static void
record_init(sleveltable_t *record)
{
  record->nlevs = 0;
  record->recordID = NULL;
  record->lindex = NULL;
}

static void
var_init(svarinfo_t *var)
{
#ifdef HAVE_LIBNETCDF
  var->cdfCache = NULL;
#endif
  var->recordTable = NULL;

  var->ncvarid = CDI_UNDEFID;
  var->defmiss = false;

  var->subtypeSize = 0;

  var->gridID = CDI_UNDEFID;
  var->zaxisID = CDI_UNDEFID;
  var->tsteptype = CDI_UNDEFID;
  var->subtypeID = CDI_UNDEFID;
}

static int
streamvar_new_entry(stream_t *streamptr)
{
  int varID = 0;
  int streamvarSize = streamptr->varsAllocated;
  svarinfo_t *streamvar = streamptr->vars;
  /*
    Look for a free slot in streamvar.
    (Create the table the first time through).
  */
  if (!streamvarSize)
  {
    streamvarSize = 2;
    streamvar = (svarinfo_t *) Malloc((size_t) streamvarSize * sizeof(svarinfo_t));
    if (streamvar == NULL)
    {
      Message("streamvarSize = %d", streamvarSize);
      SysError("Allocation of svarinfo_t failed");
    }

    for (int i = 0; i < streamvarSize; i++) streamvar[i].isUsed = false;
  }
  else
  {
    while (varID < streamvarSize)
    {
      if (!streamvar[varID].isUsed) break;
      varID++;
    }
  }
  /*
    If the table overflows, double its size.
  */
  if (varID == streamvarSize)
  {
    streamvarSize = 2 * streamvarSize;
    streamvar = (svarinfo_t *) Realloc(streamvar, (size_t) streamvarSize * sizeof(svarinfo_t));
    if (streamvar == NULL)
    {
      Message("streamvarSize = %d", streamvarSize);
      SysError("Reallocation of svarinfo_t failed");
    }
    varID = streamvarSize / 2;

    for (int i = varID; i < streamvarSize; i++) streamvar[i].isUsed = false;
  }

  streamptr->varsAllocated = streamvarSize;
  streamptr->vars = streamvar;

  var_init(&streamptr->vars[varID]);

  streamptr->vars[varID].isUsed = true;

  return varID;
}

static void
allocate_record_table_entry(stream_t *streamptr, int varID, int subID, int nlevs)
{
  int *level = (int *) Malloc((size_t) nlevs * sizeof(int));
  int *lindex = (int *) Malloc((size_t) nlevs * sizeof(int));

  for (int levID = 0; levID < nlevs; levID++)
  {
    level[levID] = CDI_UNDEFID;
    lindex[levID] = levID;
  }

  sleveltable_t *record = &streamptr->vars[varID].recordTable[subID];
  record->nlevs = nlevs;
  record->recordID = level;
  record->lindex = lindex;
}

int
stream_new_var(stream_t *streamptr, int gridID, int zaxisID, int tilesetID)
{
  if (CDI_Debug) Message("gridID = %d  zaxisID = %d", gridID, zaxisID);

  int varID = streamvar_new_entry(streamptr);
  int nlevs = zaxisInqSize(zaxisID);

  streamptr->nvars++;

  svarinfo_t *var = &streamptr->vars[varID];
  var->gridID = gridID;
  var->zaxisID = zaxisID;

  int nsub = 1;
  if (tilesetID != CDI_UNDEFID) nsub = subtypeInqSize(tilesetID); /* e.g. no of tiles */
  if (CDI_Debug) Message("varID %d: create %d tiles with %d level(s), zaxisID=%d", varID, nsub, nlevs, zaxisID);
  var->recordTable = (sleveltable_t *) Malloc((size_t) nsub * sizeof(sleveltable_t));
  if (var->recordTable == NULL) SysError("Allocation of leveltable failed!");
  var->subtypeSize = nsub;

  for (int isub = 0; isub < nsub; isub++)
  {
    record_init(&streamptr->vars[varID].recordTable[isub]);
    allocate_record_table_entry(streamptr, varID, isub, nlevs);
    if (CDI_Debug) Message("streamptr->vars[varID].recordTable[isub].recordID[0]=%d", var->recordTable[isub].recordID[0]);
  }

  var->subtypeID = tilesetID;

  return varID;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBGRIB

#ifdef HAVE_LIBFDB5
#endif

#ifdef HAVE_ACROSS
#endif


static size_t
grb_encode(int filetype, int memType, int varID, int levelID, int vlistID, int gridID, int zaxisID, CdiDateTime vDateTime,
           int tsteptype, int numavg, SizeType datasize, const void *data, size_t numMissVals, void **gribbuffer, int comptype,
           void *gribContainers)
{
  size_t nbytes = 0;

#ifdef HAVE_LIBCGRIBEX
  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1)
  {
    size_t gribbuffersize = (size_t) datasize * 4 + 3000;
    *gribbuffer = Malloc(gribbuffersize);

    nbytes = cgribexEncode(memType, varID, levelID, vlistID, gridID, zaxisID, vDateTime, tsteptype, numavg, datasize, data,
                           (SizeType) numMissVals, *gribbuffer, gribbuffersize);
  }
  else
#endif
#ifdef HAVE_LIBGRIB_API
  {
#ifdef GRIBCONTAINER2D
    void *gribContainer = (void *) &((gribContainer_t **) gribContainers)[varID][levelID];
#else
    void *gribContainer = (void *) &((gribContainer_t *) gribContainers)[varID];
#endif
    bool useFloatInterface = false;
    int memTypeX = useFloatInterface ? memType : MEMTYPE_DOUBLE;
    const void *datap = data;

    // if (useFloatInterface) printf("gribapi write: useFloatInterface\n");

    if (!useFloatInterface && memType == MEMTYPE_FLOAT)
    {
      // printf("gribapi write: convert float to double\n");
      const float *dataf = (const float *) data;
      double *datad = (double *) Malloc((size_t) datasize * sizeof(double));
      for (size_t i = 0; i < (size_t) datasize; ++i) datad[i] = (double) dataf[i];
      datap = (const void *) datad;
    }

    size_t gribbuffersize;
    nbytes = gribapiEncode(memTypeX, varID, levelID, vlistID, gridID, zaxisID, vDateTime, tsteptype, numavg, (size_t) datasize,
                           datap, (SizeType) numMissVals, gribbuffer, &gribbuffersize, comptype, gribContainer);

    if (!useFloatInterface && memType == MEMTYPE_FLOAT) Free((void *) datap);
  }
#else
  {
    Error("ecCodes support not compiled in!");
    (void) gribContainers;
    (void) comptype;
  }
#endif

  return nbytes;
}

static size_t
grbSzip(int filetype, void *gribbuffer, size_t gribbuffersize)
{
  size_t buffersize = gribbuffersize + 1000;  // compressed record can be greater than source record
  void *buffer = Malloc(buffersize);

  // memcpy(buffer, gribbuffer, gribbuffersize);

  size_t nbytes = 0;
  if (filetype == CDI_FILETYPE_GRB)
  {
    nbytes = (size_t) gribZip((unsigned char *) gribbuffer, (long) gribbuffersize, (unsigned char *) buffer, (long) buffersize);
  }
  else
  {
    static int lszip_warn = 1;
    if (lszip_warn) Warning("Szip compression of GRIB2 records not implemented!");
    lszip_warn = 0;
    nbytes = gribbuffersize;
  }

  Free(buffer);

  return nbytes;
}

typedef struct
{
  char date[16];
  char time[8];
  char param[8];
  char levtype[8];
  char levelist[8];
} FDB_Keys;

#ifdef HAVE_LIBFDB5
static void
cdi_fdb_store(void *fdbHandle, const char *filename, void *gribbuffer, size_t nbytes, const FDB_Keys *fdbKeys)
{
  size_t len = strlen(filename);
  if (len == 6) Error("FDB keys missing!");

  KeyValueItem keyValue;
  keyValue.item = NULL;
  decode_fdbitem(filename + 6, &keyValue);

  if (keyValue.numKeys == 0) Error("FDB keys missing!");

  bool expverDefined = false;
  bool classDefined = false;
  for (int k = 0; k < keyValue.numKeys; k++)
  {
    // clang-format off
    if      (!expverDefined && str_is_equal(keyValue.keys[k], "expver")) expverDefined = true;
    else if (!classDefined  && str_is_equal(keyValue.keys[k], "class")) classDefined = true;
    // clang-format on
  }

  if (!expverDefined) Error("FDB parameter <expver> undefined!");
  if (!classDefined) Error("FDB parameter <class> undefined!");

  if (CDI_Debug)
  {
    printf("{");
    for (int k = 0; k < keyValue.numKeys; k++) { printf("%s%s=%s", (k > 0) ? "," : "", keyValue.keys[k], keyValue.values[k]); }
    printf(",date=%s,time=%s,domain=g}", fdbKeys->date, fdbKeys->time);
    printf("{type=an,levtype=%s}{step=0,", fdbKeys->levtype);
    if (fdbKeys->levelist[0]) printf("levelist=%s,", fdbKeys->levelist);
    printf("param=%s},length=%zu\n", fdbKeys->param, nbytes);
  }

  fdb_key_t *key;
  check_fdb_error(fdb_new_key(&key));
  for (int k = 0; k < keyValue.numKeys; k++) { check_fdb_error(fdb_key_add(key, keyValue.keys[k], keyValue.values[k])); }
  check_fdb_error(fdb_key_add(key, "domain", "g"));
  check_fdb_error(fdb_key_add(key, "date", fdbKeys->date));
  check_fdb_error(fdb_key_add(key, "time", fdbKeys->time));
  check_fdb_error(fdb_key_add(key, "type", "an"));
  check_fdb_error(fdb_key_add(key, "levtype", fdbKeys->levtype));
  check_fdb_error(fdb_key_add(key, "step", "0"));
  check_fdb_error(fdb_key_add(key, "param", fdbKeys->param));
  if (fdbKeys->levelist[0]) check_fdb_error(fdb_key_add(key, "levelist", fdbKeys->levelist));

  check_fdb_error(fdb_archive(fdbHandle, key, gribbuffer, nbytes));
  // alternative: write to tmpfile and use C++ code from fdb_write

  check_fdb_error(fdb_delete_key(key));
}
#endif

static int
map_grib2_param(int pnum, int pcat, int pdis)
{
  // clang-format off
  if      (pnum ==  8 && pcat == 2 && pdis == 0) return 135;
  else if (pnum ==  0 && pcat == 0 && pdis == 0) return 130;
  else if (pnum ==  0 && pcat == 1 && pdis == 0) return 133;
  else if (pnum == 83 && pcat == 1 && pdis == 0) return 246;
  else if (pnum == 84 && pcat == 1 && pdis == 0) return 247;
  else if (pnum == 85 && pcat == 1 && pdis == 0) return  75;
  else if (pnum == 86 && pcat == 1 && pdis == 0) return  76;
  else if (pnum ==  2 && pcat == 2 && pdis == 0) return 131;
  else if (pnum ==  3 && pcat == 2 && pdis == 0) return 132;
  else if (pnum == 25 && pcat == 3 && pdis == 0) return 152;
  else if (pnum ==  4 && pcat == 3 && pdis == 0) return 129;
  // clang-format on

  return -1;
}

static int
get_fdb_param(int cdiParam)
{
  int pnum, pcat, pdis;
  cdiDecodeParam(cdiParam, &pnum, &pcat, &pdis);
  if (pnum < 0) pnum = -pnum;
  if (pnum > 255) pnum = pnum % 256;

  int fdbParam = (pdis == 255) ? pnum : map_grib2_param(pnum, pcat, pdis);

  return fdbParam;
}

static void
fillup_gribbuffer(size_t nbytes, unsigned char *gribbuffer)
{
  while (nbytes & 7) gribbuffer[nbytes++] = 0;
}

void
grbCopyField(stream_t *streamptr2, stream_t *streamptr1)
{
  int filetype = streamptr1->filetype;
  int fileID1 = streamptr1->fileID;
  int fileID2 = streamptr2->fileID;
  int tsID = streamptr1->curTsID;
  int vrecID = streamptr1->tsteps[tsID].curRecID;
  int recID = streamptr1->tsteps[tsID].recIDs[vrecID];
  const recinfo_t *recinfo = &(streamptr1->tsteps[tsID].recinfo[recID]);
  const record_t *record = &(streamptr1->tsteps[tsID].records[recID]);
  off_t recpos = record->position;
  size_t recsize = record->size;

  void *gribbuffer = NULL;

  if (streamptr1->protocol == CDI_PROTOCOL_FDB)
  {
#ifdef HAVE_LIBFDB5
    int fdbItemIndex = streamptr1->tsteps[tsID].records[recID].fdbItemIndex;
    if (fdbItemIndex == -1) Error("fdbItem not available!");

    size_t buffersize = 0;
    recsize = cdi_fdb_read_record(streamptr1->protocolData, &(streamptr1->fdbKeyValueList[fdbItemIndex]), &buffersize, &gribbuffer);

    // round up recsize to next multiple of 8
    size_t gribbuffersize = ((recsize + 7U) & ~7U);

    gribbuffer = (unsigned char *) Realloc(gribbuffer, gribbuffersize);
#endif
  }
  else
  {
    fileSetPos(fileID1, recpos, SEEK_SET);

    // round up recsize to next multiple of 8
    size_t gribbuffersize = ((recsize + 7U) & ~7U);

    gribbuffer = (unsigned char *) Malloc(gribbuffersize);

    if (fileRead(fileID1, gribbuffer, recsize) != recsize) Error("Could not read GRIB record for copying!");
  }

  size_t nbytes = recsize;

#ifdef HAVE_LIBCGRIBEX
  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1)
  {
    if (cdiGribChangeParameterID.active)
    {
      // Even if you are stream-copy records you might need to change a bit of grib-header !
      void *gh = cgribex_handle_new_from_meassage((void *) gribbuffer, recsize);
      cgribexChangeParameterIdentification(gh, cdiGribChangeParameterID.code, cdiGribChangeParameterID.ltype,
                                           cdiGribChangeParameterID.lev);
      cgribex_handle_delete(gh);
      cdiGribChangeParameterID.active = false;  // after grib attributes have been changed turn it off again
    }
  }
  else
#endif
#ifdef HAVE_LIBGRIB_API
  {
    if (cdiGribChangeParameterID.active)
    {
      // Even if you are stream-copy records you might need to change a bit of grib-header !
      grib_handle *gh = grib_handle_new_from_message(NULL, (void *) gribbuffer, recsize);
      gribapiChangeParameterIdentification(gh, cdiGribChangeParameterID.code, cdiGribChangeParameterID.ltype,
                                           cdiGribChangeParameterID.lev);
      grib_handle_delete(gh);
      cdiGribChangeParameterID.active = false;  // after grib attributes have been changed turn it off again
    }
  }
#else
  {
    Error("ecCodes support not compiled in!");
  }
#endif

#ifdef HAVE_LIBGRIB_API
#ifdef HIRLAM_EXTENSIONS
  // Even if you are stream-copy records you might need to change a bit of grib-header !

  if (cdiGribDataScanningMode.active)
  // allowed modes: <0, 64, 96>; Default is 64
  // This will overrule the old scanning mode of the given grid
  {
    grib_handle *gh = grib_handle_new_from_message(NULL, (void *) gribbuffer, recsize);

    int scanModeIN = gribapiGetScanningMode(gh);

    grib_handle_delete(gh);

    if (cdiDebugExt >= 20)
      Message("Change GribDataScanningMode => %d (scanModeIN = %d)", cdiGribDataScanningMode.value, scanModeIN);

    if (scanModeIN != cdiGribDataScanningMode.value)
    {
      size_t numMissVals = 0;

      int vlistID = streamptr1->vlistID;
      int varID = recinfo->varID;
      int levelID = recinfo->levelID;
      int gridID = vlistInqVarGrid(vlistID, varID);

      size_t gridsize = gridInqSize(gridID);
      if (vlistNumber(vlistID) != CDI_REAL) gridsize *= 2;
      double *data = (double *) Malloc(gridsize * sizeof(double));

      if (cdiDebugExt >= 20) Message(" processing varID %d; levelID %d", varID, levelID);

      grb_write_var_slice(streamptr2, varID, levelID, MEMTYPE_DOUBLE, (const void *) data, numMissVals);

      free(data);
    }
  }
#endif  // HIRLAM_EXTENSIONS
#endif

  if (filetype == CDI_FILETYPE_GRB)
  {
    size_t unzipsize;
    int izip = gribGetZip(recsize, (unsigned char *) gribbuffer, &unzipsize);

    if (izip == 0 && (streamptr2->comptype == CDI_COMPRESS_SZIP || streamptr2->comptype == CDI_COMPRESS_AEC))
      nbytes = grbSzip(filetype, gribbuffer, nbytes);
  }

  fillup_gribbuffer(nbytes, (unsigned char *) gribbuffer);

  if (streamptr2->protocol == CDI_PROTOCOL_FDB)
  {
    int vlistID = streamptr1->vlistID;
    int zaxisID = vlistInqVarZaxis(vlistID, recinfo->varID);
    int zaxisType = zaxisInqType(zaxisID);
    CdiDateTime vDateTime = streamptr1->tsteps[tsID].taxis.vDateTime;

    FDB_Keys fdbKeys;
    snprintf(fdbKeys.date, sizeof(fdbKeys.date), "%d", (int) cdiDate_get(vDateTime.date));
    snprintf(fdbKeys.time, sizeof(fdbKeys.time), "%04d", (short) (cdiTime_get(vDateTime.time) / 100));
    snprintf(fdbKeys.param, sizeof(fdbKeys.param), "%d", get_fdb_param(record->param));
    bool isML = (zaxisType == ZAXIS_HYBRID || zaxisType == ZAXIS_HYBRID_HALF);
    snprintf(fdbKeys.levtype, sizeof(fdbKeys.levtype), "%s", isML ? "ml" : "sfc");
    fdbKeys.levelist[0] = 0;
    if (isML) snprintf(fdbKeys.levelist, sizeof(fdbKeys.levelist), "%d", isML ? record->ilevel : 0);

#ifdef HAVE_LIBFDB5
    cdi_fdb_store(streamptr2->protocolData, streamptr2->filename, gribbuffer, nbytes, &fdbKeys);
#endif
  }
  else
  {
    size_t nwrite = fileWrite(fileID2, gribbuffer, nbytes);
    if (nwrite != nbytes) SysError("Could not write record for copying!");
  }

  Free(gribbuffer);
}

void
grb_write_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, const void *data, size_t numMissVals)
{
  int filetype = streamptr->filetype;
  int fileID = streamptr->fileID;
  int vlistID = streamptr->vlistID;
  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);
  int tsteptype = vlistInqVarTsteptype(vlistID, varID);
  int tsID = streamptr->curTsID;
  CdiDateTime vDateTime = streamptr->tsteps[tsID].taxis.vDateTime;
  int numavg = (tsteptype == TSTEP_AVG) ? streamptr->tsteps[tsID].taxis.numavg : 0;
  int comptype = streamptr->comptype;

  if (CDI_Debug) Message("gridID = %d zaxisID = %d", gridID, zaxisID);

  SizeType datasize = gridInqSize(gridID);

  if (comptype != CDI_COMPRESS_JPEG && comptype != CDI_COMPRESS_SZIP && comptype != CDI_COMPRESS_AEC) comptype = CDI_COMPRESS_NONE;

  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1 && comptype == CDI_COMPRESS_JPEG)
  {
    static bool ljpeg_warn = true;
    if (ljpeg_warn) Warning("JPEG compression of GRIB1 records not available!");
    ljpeg_warn = false;
  }

  void *gribbuffer = NULL;
  size_t nbytes = grb_encode(filetype, memtype, varID, levelID, vlistID, gridID, zaxisID, vDateTime, tsteptype, numavg, datasize,
                             data, numMissVals, &gribbuffer, comptype, streamptr->gribContainers);

  if (filetype == CDI_FILETYPE_GRB && (comptype == CDI_COMPRESS_SZIP || comptype == CDI_COMPRESS_AEC))
    nbytes = grbSzip(filetype, gribbuffer, nbytes);

  switch (streamptr->protocol)
  {
    case CDI_PROTOCOL_ACROSS:
    {
#ifdef HAVE_ACROSS
      if (across_write_grib_message(streamptr, gribbuffer, nbytes)) SysError("Failed to write GRIB slice!");
#endif
    }
    break;

    case CDI_PROTOCOL_FDB:
    {
#ifdef HAVE_LIBFDB5
      int zaxisType = zaxisInqType(zaxisID);
      double level = zaxisInqLevel(zaxisID, levelID);

      FDB_Keys fdbKeys;
      snprintf(fdbKeys.date, sizeof(fdbKeys.date), "%d", (int) cdiDate_get(vDateTime.date));
      snprintf(fdbKeys.time, sizeof(fdbKeys.time), "%04d", (short) (cdiTime_get(vDateTime.time) / 100));
      snprintf(fdbKeys.param, sizeof(fdbKeys.param), "%d", get_fdb_param(vlistInqVarParam(vlistID, varID)));
      bool isML = (zaxisType == ZAXIS_HYBRID || zaxisType == ZAXIS_HYBRID_HALF);
      snprintf(fdbKeys.levtype, sizeof(fdbKeys.levtype), "%s", isML ? "ml" : "sfc");
      fdbKeys.levelist[0] = 0;
      int ilevel = (isML) ? (int) level : 0;
      if (isML) snprintf(fdbKeys.levelist, sizeof(fdbKeys.levelist), "%d", isML ? ilevel : 0);

      cdi_fdb_store(streamptr->protocolData, streamptr->filename, gribbuffer, nbytes, &fdbKeys);
#endif
    }
    break;

    case CDI_PROTOCOL_OTHER:
    case CDI_PROTOCOL_FILE:
    {
      size_t (*myFileWrite)(int fileID, const void *restrict buffer, size_t len)
          = (size_t(*)(int, const void *restrict, size_t)) namespaceSwitchGet(NSSWITCH_FILE_WRITE).func;

      size_t nwrite = myFileWrite(fileID, gribbuffer, nbytes);
      if (nwrite != nbytes) SysError("Failed to write GRIB slice!");
    }
    break;
  }

  if (gribbuffer) Free(gribbuffer);
}

void
grb_write_var(stream_t *streamptr, int varID, int memtype, const void *data, size_t numMissVals)
{
  int vlistID = streamptr->vlistID, gridID = vlistInqVarGrid(vlistID, varID), zaxisID = vlistInqVarZaxis(vlistID, varID),
      nlevs = zaxisInqSize(zaxisID);
  size_t gridsize = (size_t) gridInqSize(gridID);
  double missval = vlistInqVarMissval(vlistID, varID);

  size_t chunkLen = gridsize;
  if (memtype == MEMTYPE_FLOAT)
    for (int levelID = 0; levelID < nlevs; levelID++)
    {
      const float *restrict fdata = ((const float *) data) + (size_t) levelID * gridsize;

      size_t numMissVals_slice = 0;
      if (numMissVals)
        for (size_t i = 0; i < chunkLen; ++i) numMissVals_slice += DBL_IS_EQUAL(fdata[i], missval);

      grb_write_var_slice(streamptr, varID, levelID, memtype, fdata, numMissVals_slice);
    }
  else
    for (int levelID = 0; levelID < nlevs; levelID++)
    {
      const double *restrict ddata = ((const double *) data) + (size_t) levelID * gridsize;

      size_t numMissVals_slice = 0;
      if (numMissVals)
        for (size_t i = 0; i < chunkLen; ++i) numMissVals_slice += DBL_IS_EQUAL(ddata[i], missval);

      grb_write_var_slice(streamptr, varID, levelID, memtype, ddata, numMissVals_slice);
    }
}

void
grb_write_field(stream_t *streamptr, int memtype, const void *data, size_t numMissVals)
{
  int varID = streamptr->record->varID;
  int levelID = streamptr->record->levelID;
  grb_write_var_slice(streamptr, varID, levelID, memtype, data, numMissVals);
}

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBGRIB

#ifdef HAVE_LIBFDB5
#endif


static int
grb_decode(int filetype, int memType, int datatype, void *cgribexp, void *gribbuffer, size_t gribsize, void *data, size_t datasize,
           int unreduced, size_t *numMissVals, double missval)
{
  int status = 0;

#ifdef HAVE_LIBCGRIBEX
  if (filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1)
  {
#ifdef HAVE_LIBGRIB_API
    extern int cdiNAdditionalGRIBKeys;
    if (cdiNAdditionalGRIBKeys > 0) Error("CGRIBEX decode does not support reading of additional GRIB keys!");
#endif
    status = cgribexDecode(memType, cgribexp, gribbuffer, gribsize, data, datasize, unreduced, numMissVals, missval);
  }
  else
#endif
#ifdef HAVE_LIBGRIB_API
  {
    bool useFloatInterface = (have_gribapi_float_interface() && datatype != CDI_DATATYPE_FLT32 && datatype != CDI_DATATYPE_FLT64);
    int memTypeX = useFloatInterface ? memType : MEMTYPE_DOUBLE;
    void *datap = (!useFloatInterface && memType == MEMTYPE_FLOAT) ? Malloc(datasize * sizeof(double)) : data;

    // if (useFloatInterface) printf("gribapi read: useFloatInterface\n");

    status = gribapiDecode(memTypeX, gribbuffer, gribsize, datap, datasize, unreduced, numMissVals, missval);

    if (!useFloatInterface && memType == MEMTYPE_FLOAT)
    {
      // printf("gribapi read: convert double to float\n");
      float *dataf = (float *) data;
      double *datad = (double *) datap;
      for (size_t i = 0; i < datasize; ++i) dataf[i] = (float) datad[i];
      Free(datap);
    }
  }
#else
  {
    (void) datatype;
    Error("ecCodes support not compiled in!");
  }
#endif

  return status;
}

// Decompresses the grib data in gribbuffer.
static int
grib1_unzip_record(void *gribbuffer, size_t *gribsize)
{
  int zip = 0;

  size_t igribsize = *gribsize;
  size_t ogribsize = *gribsize;

  int izip;
  size_t unzipsize;
  if ((izip = gribGetZip(igribsize, (unsigned char *) gribbuffer, &unzipsize)) > 0)
  {
    zip = izip;
    if (izip == 128)  // szip
    {
      if (unzipsize < igribsize)
      {
        fprintf(stderr, "Decompressed size smaller than compressed size (in %zu; out %zu)!\n", igribsize, unzipsize);
        return 0;
      }

      unzipsize += 100;  // need 0 to 1 bytes for rounding of bds

      void *buffer = Malloc(igribsize);
      memcpy(buffer, gribbuffer, igribsize);

      ogribsize = (size_t) gribUnzip((unsigned char *) gribbuffer, (long) unzipsize, (unsigned char *) buffer, (long) igribsize);

      Free(buffer);

      if (ogribsize == 0) Error("Decompression problem!");
    }
    else
    {
      Error("Decompression for %d not implemented!", izip);
    }
  }

  *gribsize = ogribsize;

  return zip;
}

typedef struct JobArgsGRB
{
  int *outZip;
  int recID, tsID, filetype, memType, datatype, unreduced;
  void *cgribexp, *gribbuffer, *data;
  size_t recsize, gridsize, numMissVals;
  double missval;
} JobArgsGRB;

static int
grb_decode_record(void *untypedArgs)
{
  JobArgsGRB *args = (JobArgsGRB *) untypedArgs;
  *args->outZip = grib1_unzip_record(args->gribbuffer, &args->recsize);
  grb_decode(args->filetype, args->memType, args->datatype, args->cgribexp, args->gribbuffer, args->recsize, args->data,
             args->gridsize, args->unreduced, &args->numMissVals, args->missval);
  return 0;
}

static JobArgsGRB
grb_read_raw_data(stream_t *streamptr, int tsID, int recID, int memType, void *gribbuffer, void *data, bool resetFilePos)
{
  int vlistID = streamptr->vlistID;
  int varID = streamptr->tsteps[tsID].recinfo[recID].varID;
  size_t recsize = streamptr->tsteps[tsID].records[recID].size;

  int gridID = vlistInqVarGrid(vlistID, varID);
  size_t gridsize = (size_t) gridInqSize(gridID);
  if (CDI_Debug) Message("gridID = %d gridsize = %zu", gridID, gridsize);

  void *cgribexp = (gribbuffer && streamptr->record->objectp) ? streamptr->record->objectp : NULL;
  if (!gribbuffer) gribbuffer = Malloc(streamptr->record->buffersize);
  if (!data) data = Malloc(gridsize * ((memType == MEMTYPE_FLOAT) ? sizeof(float) : sizeof(double)));

  if (streamptr->protocol == CDI_PROTOCOL_FDB)
  {
#ifdef HAVE_LIBFDB5
    int fdbItemIndex = streamptr->tsteps[tsID].records[recID].fdbItemIndex;
    if (fdbItemIndex == -1) Error("fdbItem not available!");

    recsize = cdi_fdb_read_record(streamptr->protocolData, &(streamptr->fdbKeyValueList[fdbItemIndex]),
                                  &(streamptr->record->buffersize), &gribbuffer);
#endif
  }
  else
  {
    if (recsize == 0) Error("Internal problem! Recordsize is zero for record %d at timestep %d", recID + 1, tsID + 1);

    int fileID = streamptr->fileID;
    off_t recpos = streamptr->tsteps[tsID].records[recID].position;
    off_t currentfilepos = (resetFilePos ? fileGetPos(fileID) : 0);

    fileSetPos(fileID, recpos, SEEK_SET);
    if (fileRead(fileID, gribbuffer, recsize) != recsize) Error("Failed to read GRIB record!");

    if (resetFilePos) fileSetPos(fileID, currentfilepos, SEEK_SET);
    if (!resetFilePos) streamptr->numvals += (SizeType) gridsize;
  }

  return (JobArgsGRB) {
    .outZip = &streamptr->tsteps[tsID].records[recID].zip,
    .recID = recID,
    .tsID = tsID,
    .filetype = streamptr->filetype,
    .memType = memType,
    .datatype = vlistInqVarDatatype(vlistID, varID),
    .unreduced = streamptr->unreduced,
    .cgribexp = cgribexp,
    .gribbuffer = gribbuffer,
    .data = data,
    .recsize = recsize,
    .gridsize = gridsize,
    .numMissVals = 0,
    .missval = vlistInqVarMissval(vlistID, varID),
  };
}

static size_t
grb_read_and_decode_record(stream_t *streamptr, int recID, int memType, void *data, bool resetFilePos)
{
  JobArgsGRB args = grb_read_raw_data(streamptr, streamptr->curTsID, recID, memType, streamptr->record->buffer, data, resetFilePos);
  grb_decode_record(&args);
  return args.numMissVals;
}

typedef struct JobDescriptorGRB
{
  JobArgsGRB args;
  AsyncJob *job;
} JobDescriptorGRB;

static void
JobDescriptor_startJob(AsyncManager *jobManager, JobDescriptorGRB *me, stream_t *streamptr, int tsID, int recID, int memType)
{
  me->args = grb_read_raw_data(streamptr, tsID, recID, memType, NULL, NULL, false);
  me->job = AsyncWorker_requestWork(jobManager, grb_decode_record, &me->args);
  if (!me->job) xabort("error while trying to send job to worker thread");
}

static void
grb_JobDescriptor_finishJob(AsyncManager *jobManager, JobDescriptorGRB *me, void *data, size_t *numMissVals)
{
  if (AsyncWorker_wait(jobManager, me->job)) xabort("error executing job in worker thread");
  memcpy(data, me->args.data, me->args.gridsize * ((me->args.memType == MEMTYPE_FLOAT) ? sizeof(float) : sizeof(double)));
  *numMissVals = me->args.numMissVals;

  Free(me->args.gribbuffer);
  Free(me->args.data);
  me->args.recID = -1;  // mark as inactive
  me->args.tsID = -1;   // mark as inactive
}
/*
static long
get_global_recId(stream_t *streamptr, int tsID, int recID)
{
  const tsteps_t *tsteps = streamptr->tsteps;
  long globalRecId = recID;
  if (tsID > 0) globalRecId += tsteps[0].nrecs;
  if (tsID > 1) globalRecId += tsteps[1].nrecs * (tsID - 1);
  return globalRecId;
}
*/

static void
grb_get_local_step_and_recId(stream_t *streamptr, long globalRecId, int *tsID, long *recID)
{
  int localTsId = 0;
  long numSteps = streamptr->ntsteps;
  const tsteps_t *tsteps = streamptr->tsteps;
  if (numSteps > 0 && globalRecId >= tsteps[0].nrecs)
  {
    localTsId++;
    globalRecId -= tsteps[0].nrecs;
  }
  if (globalRecId >= tsteps[1].nrecs)
  {
    ldiv_t nadjust = ldiv(globalRecId, tsteps[1].nrecs);
    localTsId += (int) nadjust.quot;
    globalRecId = nadjust.rem;
  }

  *tsID = localTsId;
  *recID = globalRecId;
}

static void
grb_read_next_record_async(AsyncManager *jobManager, JobDescriptorGRB *jd, stream_t *streamptr, int memType)
{
  int tsId = -1;
  long recId = -1;
  grb_get_local_step_and_recId(streamptr, streamptr->nextGlobalRecId, &tsId, &recId);
  int xRecId = streamptr->tsteps[tsId].recIDs[recId];
  JobDescriptor_startJob(jobManager, jd, streamptr, tsId, xRecId, memType);
  streamptr->nextGlobalRecId++;
}

static void
grb_read_next_record(stream_t *streamptr, int recID, int memType, void *data, size_t *numMissVals)
{
  bool jobFound = false;

  int workerCount = streamptr->numWorker;
  if (workerCount > 0)
  {
    int tsID = streamptr->curTsID;

    AsyncManager *jobManager = (AsyncManager *) streamptr->jobManager;
    JobDescriptorGRB *jobs = (JobDescriptorGRB *) streamptr->jobs;

    // if this is the first call, init and start worker threads
    if (!jobs)
    {
      jobs = (JobDescriptorGRB *) Malloc((size_t) workerCount * sizeof(*jobs));
      streamptr->jobs = jobs;
      for (int i = 0; i < workerCount; i++) jobs[i].args.recID = -1;
      for (int i = 0; i < workerCount; i++) jobs[i].args.tsID = -1;
      if (AsyncWorker_init(&jobManager, workerCount)) xabort("error while trying to start worker threads");
      streamptr->jobManager = jobManager;

      // Start as many new jobs as possible.
      for (int i = 0; streamptr->nextGlobalRecId < streamptr->maxGlobalRecs && i < workerCount; i++)
        if (jobs[i].args.recID < 0 && jobs[i].args.tsID < 0) grb_read_next_record_async(jobManager, jobs + i, streamptr, memType);
    }

    // search for a job descriptor with the given tsID and recID, and use its results if it exists
    for (int i = 0; !jobFound && i < workerCount; i++)
      if (jobs[i].args.recID == recID && jobs[i].args.tsID == tsID)
      {
        jobFound = true;
        grb_JobDescriptor_finishJob(jobManager, jobs + i, data, numMissVals);
        if (streamptr->nextGlobalRecId < streamptr->maxGlobalRecs)
          grb_read_next_record_async(jobManager, jobs + i, streamptr, memType);
      }
  }

  // perform the work synchronously if we didn't start a job for it yet
  if (!jobFound) *numMissVals = grb_read_and_decode_record(streamptr, recID, memType, data, false);
}

void
grb_read_field(stream_t *streamptr, int memType, void *data, size_t *numMissVals)
{
  int tsID = streamptr->curTsID;
  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];

  grb_read_next_record(streamptr, recID, memType, data, numMissVals);
}

void
grb_read_var_slice(stream_t *streamptr, int varID, int levelID, int memType, void *data, size_t *numMissVals)
{
  int isub = subtypeInqActiveIndex(streamptr->vars[varID].subtypeID);
  int recID = streamptr->vars[varID].recordTable[isub].recordID[levelID];

  *numMissVals = grb_read_and_decode_record(streamptr, recID, memType, data, true);
}

void
grb_read_var(stream_t *streamptr, int varID, int memType, void *data, size_t *numMissVals)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  int gridID = vlistInqVarGrid(vlistID, varID);
  size_t gridsize = (size_t) gridInqSize(gridID);

  off_t currentfilepos = fileGetPos(fileID);

  int isub = subtypeInqActiveIndex(streamptr->vars[varID].subtypeID);
  int nlevs = streamptr->vars[varID].recordTable[0].nlevs;

  if (CDI_Debug) Message("nlevs = %d gridID = %d gridsize = %zu", nlevs, gridID, gridsize);

  *numMissVals = 0;
  for (int levelID = 0; levelID < nlevs; levelID++)
  {
    int recID = streamptr->vars[varID].recordTable[isub].recordID[levelID];
    size_t offset = (size_t) levelID * gridsize;
    void *datap = (memType == MEMTYPE_FLOAT) ? (void *) ((float *) data + offset) : (void *) ((double *) data + offset);

    *numMissVals += grb_read_and_decode_record(streamptr, recID, memType, datap, false);
  }

  fileSetPos(fileID, currentfilepos, SEEK_SET);
}

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF
#include <stdbool.h>


void
cdfDefVarDeflate(int ncid, int ncvarID, int shuffle, int compLevel)
{
  int deflate = 1;

  if (CDI_Shuffle > 0 && shuffle == 0) shuffle = 1;

  if (compLevel < 1 || compLevel > 9) compLevel = 1;

  int status;
  if ((status = nc_def_var_deflate(ncid, ncvarID, shuffle, deflate, compLevel)))
  {
    Error("nc_def_var_deflate failed: %s", nc_strerror(status));
  }
}

void
cdfDefVarSzip(int ncid, int ncvarID, int pixels_per_block)
{
#ifdef HAVE_NC_DEF_VAR_SZIP
  // Set options_mask.
  /*
    H5_SZIP_ALLOW_K13_OPTION_MASK   1
    H5_SZIP_CHIP_OPTION_MASK        2
    H5_SZIP_EC_OPTION_MASK          4
    H5_SZIP_NN_OPTION_MASK          32
    H5_SZIP_ALL_MASKS (H5_SZIP_CHIP_OPTION_MASK|H5_SZIP_EC_OPTION_MASK|H5_SZIP_NN_OPTION_MASK)
  */
  int options_mask = 38;
  int status;
  if ((status = nc_def_var_szip(ncid, ncvarID, options_mask, pixels_per_block)))
    Error("nc_def_var_szip failed: %s", nc_strerror(status));
#else
  (void) ncid;
  (void) ncvarID;
  (void) pixels_per_block;
  static bool lwarn = true;
  if (lwarn)
  {
    lwarn = false;
    Warning("Szip compression failed, NetCDF4/szlib not available!");
  }
#endif
}

static nc_type
cdfTypeComplexFloat(stream_t *streamptr)
{
  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  if (cdfInfo->complexFloatId == CDI_UNDEFID)
  {
    typedef struct
    {
      float r, i;
    } complex_float;
    int fileID = streamptr->fileID;
    int nc_complex_id;
    int status;
    status = nc_def_compound(fileID, sizeof(complex_float), "complex_float", &nc_complex_id);
    if (status != NC_NOERR) Error("%s", nc_strerror(status));
    status = nc_insert_compound(fileID, nc_complex_id, "r", NC_COMPOUND_OFFSET(complex_float, r), NC_FLOAT);
    if (status != NC_NOERR) Error("%s", nc_strerror(status));
    status = nc_insert_compound(fileID, nc_complex_id, "i", NC_COMPOUND_OFFSET(complex_float, i), NC_FLOAT);
    if (status != NC_NOERR) Error("%s", nc_strerror(status));
    cdfInfo->complexFloatId = nc_complex_id;
  }

  return (nc_type) cdfInfo->complexFloatId;
}

static nc_type
cdfTypeComplexDouble(stream_t *streamptr)
{
  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  if (cdfInfo->complexDoubleId == CDI_UNDEFID)
  {
    typedef struct
    {
      double r, i;
    } complex_double;
    int fileID = streamptr->fileID;
    int nc_complex_id;
    int status;
    status = nc_def_compound(fileID, sizeof(complex_double), "complex_double", &nc_complex_id);
    if (status != NC_NOERR) Error("%s", nc_strerror(status));
    status = nc_insert_compound(fileID, nc_complex_id, "r", NC_COMPOUND_OFFSET(complex_double, r), NC_DOUBLE);
    if (status != NC_NOERR) Error("%s", nc_strerror(status));
    status = nc_insert_compound(fileID, nc_complex_id, "i", NC_COMPOUND_OFFSET(complex_double, i), NC_DOUBLE);
    if (status != NC_NOERR) Error("%s", nc_strerror(status));
    cdfInfo->complexDoubleId = nc_complex_id;
  }

  return (nc_type) cdfInfo->complexDoubleId;
}

nc_type
cdfDefDatatype(int datatype, stream_t *streamptr)
{
  nc_type xtype = NC_FLOAT;

  // clang-format off
  if (streamptr->filetype == CDI_FILETYPE_NC4)
  {
    if      (datatype == CDI_DATATYPE_INT8  ) xtype = NC_BYTE;
    else if (datatype == CDI_DATATYPE_INT16 ) xtype = NC_SHORT;
    else if (datatype == CDI_DATATYPE_INT32 ) xtype = NC_INT;
    else if (datatype == CDI_DATATYPE_UINT8 ) xtype = NC_UBYTE;
    else if (datatype == CDI_DATATYPE_UINT16) xtype = NC_USHORT;
    else if (datatype == CDI_DATATYPE_UINT32) xtype = NC_UINT;
    else if (datatype == CDI_DATATYPE_CPX32 ) xtype = cdfTypeComplexFloat(streamptr);
    else if (datatype == CDI_DATATYPE_CPX64 ) xtype = cdfTypeComplexDouble(streamptr);
    else if (datatype == CDI_DATATYPE_FLT64 ) xtype = NC_DOUBLE;
    else if (datatype == CDI_DATATYPE_FLT32 ) xtype = NC_FLOAT;
  }
  else
  {
    if      (datatype == CDI_DATATYPE_INT8  ) xtype = NC_BYTE;
    else if (datatype == CDI_DATATYPE_INT16 ) xtype = NC_SHORT;
    else if (datatype == CDI_DATATYPE_INT32 ) xtype = NC_INT;
    else if (datatype == CDI_DATATYPE_UINT8 ) xtype = NC_SHORT;
    else if (datatype == CDI_DATATYPE_UINT16) xtype = NC_INT;
    else if (datatype == CDI_DATATYPE_UINT32) xtype = NC_INT;
    else if (datatype == CDI_DATATYPE_FLT64 ) xtype = NC_DOUBLE;
    else if (datatype == CDI_DATATYPE_FLT32 ) xtype = NC_FLOAT;
    else if (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64)
      Error("CDI library does not support complex numbers with NetCDF classic!");
  }
  // clang-format on

  return xtype;
}

static void
cdfDefVarMissval(stream_t *streamptr, int varID, int dtype, int lcheck)
{
  if (streamptr->vars[varID].defmiss == false)
  {
    int vlistID = streamptr->vlistID;
    int fileID = streamptr->fileID;
    int ncvarID = streamptr->vars[varID].ncvarid;
    double missval = vlistInqVarMissval(vlistID, varID);

    if (lcheck && streamptr->ncmode == 2) cdf_redef(fileID);

    nc_type xtype = cdfDefDatatype(dtype, streamptr);
    if (xtype == NC_BYTE && missval > 127 && missval < 256) xtype = NC_INT;

    if (lcheck == 0 || streamptr->ncmode != 2 || streamptr->filetype == CDI_FILETYPE_NC || streamptr->filetype == CDI_FILETYPE_NC2
        || streamptr->filetype == CDI_FILETYPE_NC5)
      cdf_put_att_double(fileID, ncvarID, "_FillValue", xtype, 1, &missval);

    cdf_put_att_double(fileID, ncvarID, "missing_value", xtype, 1, &missval);

    if (lcheck && streamptr->ncmode == 2) cdf_enddef(fileID, streamptr->self);

    streamptr->vars[varID].defmiss = true;
  }
}

static void
cdfDefInstituteGlobal(const stream_t *streamptr)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int instID = vlistInqInstitut(vlistID);

  if (instID != CDI_UNDEFID)
  {
    const char *longname = institutInqLongnamePtr(instID);
    if (longname)
    {
      size_t len = strlen(longname);
      if (len > 0)
      {
        if (streamptr->ncmode == 2) cdf_redef(fileID);
        cdf_put_att_text(fileID, NC_GLOBAL, "institution", len, longname);
        if (streamptr->ncmode == 2) cdf_enddef(fileID, streamptr->self);
      }
    }
  }
}

static void
cdfDefSourceGlobal(const stream_t *streamptr)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int modelID = vlistInqModel(vlistID);

  if (modelID != CDI_UNDEFID)
  {
    const char *longname = modelInqNamePtr(modelID);
    if (longname)
    {
      size_t len = strlen(longname);
      if (len > 0)
      {
        if (streamptr->ncmode == 2) cdf_redef(fileID);
        cdf_put_att_text(fileID, NC_GLOBAL, "source", len, longname);
        if (streamptr->ncmode == 2) cdf_enddef(fileID, streamptr->self);
      }
    }
  }
}

static inline void *
resizeBuf(void **buf, size_t *bufSize, size_t reqSize)
{
  if (reqSize > *bufSize)
  {
    *buf = Realloc(*buf, reqSize);
    *bufSize = reqSize;
  }
  return *buf;
}

static void
cdfDefineCellMethods(stream_t *streamptr, int cdiID, int varID, int fileID, int ncvarID)
{
  taxis_t *taxis = &streamptr->tsteps[0].taxis;
  if (!taxis->hasBounds) return;

  int timeVarId = streamptr->basetime.ncvarid;
  char timeVarName[CDI_MAX_NAME];
  cdf_inq_varname(fileID, timeVarId, timeVarName);

  int stepType = vlistInqVarTsteptype(cdiID, varID);

  const char *cellMethod = NULL;
  // clang-format off
  if      (stepType == TSTEP_AVG)   cellMethod = "mean";
  else if (stepType == TSTEP_SUM)   cellMethod = "sum";
  else if (stepType == TSTEP_RANGE) cellMethod = "range";
  else if (stepType == TSTEP_MIN)   cellMethod = "minimum";
  else if (stepType == TSTEP_MAX)   cellMethod = "maximum";
  // clang-format on

  if (cellMethod)
  {
    const char *attname = "cell_methods";
    char atttxt[CDI_MAX_NAME + 10];
    snprintf(atttxt, sizeof(atttxt), "%s: %s", timeVarName, cellMethod);
    cdf_put_att_text(fileID, ncvarID, attname, strlen(atttxt), atttxt);
  }
}

static nc_type
int_datatype_to_xtype(int filetype, int datatype)
{
  // clang-format off
  if (filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C || filetype == CDI_FILETYPE_NCZARR)
  {
    return (datatype == CDI_DATATYPE_INT8)   ? NC_BYTE :
           (datatype == CDI_DATATYPE_INT16)  ? NC_SHORT :
           (datatype == CDI_DATATYPE_UINT8)  ? NC_UBYTE :
           (datatype == CDI_DATATYPE_UINT16) ? NC_USHORT :
           (datatype == CDI_DATATYPE_UINT32) ? NC_UINT :
                                               NC_INT;
  }

  return (datatype == CDI_DATATYPE_INT8)   ? NC_BYTE :
         (datatype == CDI_DATATYPE_INT16)  ? NC_SHORT :
                                             NC_INT;
  // clang-format on
}

void
cdfDefineAttributes(int filetype, int cdiID, int varID, int fileID, int ncvarID)
{
  int atttype, attlen;
  char attname[CDI_MAX_NAME + 1];
  void *attBuf = NULL;
  size_t attBufSize = 0;

  int natts;
  cdiInqNatts(cdiID, varID, &natts);

  for (int iatt = 0; iatt < natts; ++iatt)
  {
    cdiInqAtt(cdiID, varID, iatt, attname, &atttype, &attlen);

    // if (attlen == 0) continue;

    if (atttype == CDI_DATATYPE_TXT)
    {
      size_t attSize = (size_t) attlen * sizeof(char);
      char *atttxt = (char *) resizeBuf(&attBuf, &attBufSize, attSize);
      cdiInqAttTxt(cdiID, varID, attname, attlen, atttxt);
      size_t len = (size_t) attlen;
      cdf_put_att_text(fileID, ncvarID, attname, len, atttxt);
    }
    else if (atttype == CDI_DATATYPE_INT8 || atttype == CDI_DATATYPE_UINT8 || atttype == CDI_DATATYPE_INT16
             || atttype == CDI_DATATYPE_UINT16 || atttype == CDI_DATATYPE_INT32 || atttype == CDI_DATATYPE_UINT32)
    {
      if (attlen == 0) continue;
      size_t attSize = (size_t) attlen * sizeof(int);
      int *attint = (int *) resizeBuf(&attBuf, &attBufSize, attSize);
      cdiInqAttInt(cdiID, varID, attname, attlen, &attint[0]);
      size_t len = (size_t) attlen;
      cdf_put_att_int(fileID, ncvarID, attname, int_datatype_to_xtype(filetype, atttype), len, attint);
    }
    else if (atttype == CDI_DATATYPE_FLT32 || atttype == CDI_DATATYPE_FLT64)
    {
      if (attlen == 0) continue;
      size_t attSize = (size_t) attlen * sizeof(double);
      double *attflt = (double *) resizeBuf(&attBuf, &attBufSize, attSize);
      cdiInqAttFlt(cdiID, varID, attname, attlen, attflt);
      size_t len = (size_t) attlen;
      if (atttype == CDI_DATATYPE_FLT32)
      {
        float attflt_sp[8];
        float *pattflt_sp = (len > 8) ? (float *) malloc(len * sizeof(float)) : attflt_sp;
        for (size_t i = 0; i < len; ++i) pattflt_sp[i] = (float) attflt[i];
        cdf_put_att_float(fileID, ncvarID, attname, NC_FLOAT, len, pattflt_sp);
        if (len > 8) free(pattflt_sp);
      }
      else
        cdf_put_att_double(fileID, ncvarID, attname, NC_DOUBLE, len, attflt);
    }
  }

  if (attBuf) Free(attBuf);
}

static void
cdfDefineInstituteName(int vlistID, int varID, int fileID, int ncvarID)
{
  int instID = vlistInqVarInstitut(vlistID, varID);
  if (instID != CDI_UNDEFID)
  {
    const char *name = institutInqNamePtr(instID);
    if (name) cdf_put_att_text(fileID, ncvarID, "institution", strlen(name), name);
  }
}

static void
cdfDefGlobalAtts(stream_t *streamptr)
{
  if (streamptr->globalatts) return;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  cdfDefSourceGlobal(streamptr);
  cdfDefInstituteGlobal(streamptr);

  int natts;
  cdiInqNatts(vlistID, CDI_GLOBAL, &natts);

  if (natts > 0 && streamptr->ncmode == 2) cdf_redef(fileID);

  cdfDefineAttributes(streamptr->filetype, vlistID, CDI_GLOBAL, fileID, NC_GLOBAL);

  if (natts > 0 && streamptr->ncmode == 2) cdf_enddef(fileID, streamptr->self);

  streamptr->globalatts = 1;
}

static void
cdf_get_gmapvarname(int gridID, char *gmapvarname)
{
  gmapvarname[0] = 0;

  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname, &length);

  if (!gmapvarname[0])
  {
    length = CDI_MAX_NAME;
    gridID = gridInqProj(gridID);
    if (gridID != CDI_UNDEFID) cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname, &length);
  }
}

static int
nc_grid_index(stream_t *streamptr, int gridID)
{
  int index = 0;
  int vlistID = streamptr->vlistID;
  int ngrids = vlistNumGrids(vlistID);
  for (index = 0; index < ngrids; ++index)
    if (streamptr->cdfInfo.cdfGridList[index].gridID == gridID) break;

  assert(index < ngrids);

  return index;
}

// convert NetCDF xtype to pixels_per_block
static int
xtype2ppb(nc_type xtype)
{
  int ppb = 32;

  // clang-format off
  if      (xtype == NC_BYTE)   ppb = 8;
  else if (xtype == NC_SHORT)  ppb = 16;
  else if (xtype == NC_UBYTE)  ppb = 8;
  else if (xtype == NC_USHORT) ppb = 16;
  // clang-format on

  return ppb;
}

static void
cdfDefVarFilter(int fileID, int fileType, int ncvarID, const char *filterSpec)
{
  if (filterSpec)
  {
    if (fileType == CDI_FILETYPE_NC4 || fileType == CDI_FILETYPE_NC4C || fileType == CDI_FILETYPE_NCZARR)
    {
      cdf_def_var_filter(fileID, ncvarID, filterSpec);
    }
    else
    {
      static bool lwarn = true;
      if (lwarn)
      {
        lwarn = false;
        Warning("Filter is only available with NetCDF4!");
      }
    }
  }
}

static void
cdfDefVarCompression(const stream_t *streamptr, int ncvarID, nc_type xtype)
{
  if (streamptr->comptype == CDI_COMPRESS_ZIP)
  {
    if (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C
        || streamptr->filetype == CDI_FILETYPE_NCZARR)
    {
      cdfDefVarDeflate(streamptr->fileID, ncvarID, streamptr->shuffle, streamptr->complevel);
    }
    else
    {
      static bool lwarn = true;
      if (lwarn)
      {
        lwarn = false;
        Warning("Deflate compression is only available for NetCDF4!");
      }
    }
  }
  /*
  else if (streamptr->comptype == CDI_COMPRESS_ZSTD)
    {
      if (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C
          || streamptr->filetype == CDI_FILETYPE_NCZARR)
        {
          cdfDefVarZstd(streamptr->fileID, ncvarID, streamptr->complevel);
        }
      else
        {
          static bool lwarn = true;
          if (lwarn)
            {
              lwarn = false;
              Warning("SZIP compression is only available for NetCDF4!");
            }
        }
    }
  */
  else if (streamptr->comptype == CDI_COMPRESS_SZIP)
  {
    if (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C
        || streamptr->filetype == CDI_FILETYPE_NCZARR)
    {
      cdfDefVarSzip(streamptr->fileID, ncvarID, xtype2ppb(xtype));
    }
    else
    {
      static bool lwarn = true;
      if (lwarn)
      {
        lwarn = false;
        Warning("SZIP compression is only available for NetCDF4!");
      }
    }
  }
}

static void
cdfDefVarPacking(const stream_t *streamptr, int ncvarID, nc_type xtype, int vlistID, int varID)
{
  //  if ( xtype == NC_BYTE || xtype == NC_SHORT || xtype == NC_INT )
  {
    double addoffset = 0.0, scalefactor = 1.0;
    bool haveAddoffset = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_ADDOFFSET, &addoffset) == CDI_NOERR);
    bool haveScalefactor = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_SCALEFACTOR, &scalefactor) == CDI_NOERR);

    if (haveAddoffset || haveScalefactor)
    {
      nc_type astype = (xtype == NC_FLOAT) ? NC_FLOAT : NC_DOUBLE;
      if ((astype == NC_DOUBLE) && IS_EQUAL(addoffset, (double) ((float) addoffset))
          && IS_EQUAL(scalefactor, (double) ((float) scalefactor)))
      {
        astype = NC_FLOAT;
      }

      cdf_put_att_double(streamptr->fileID, ncvarID, "add_offset", astype, 1, &addoffset);
      cdf_put_att_double(streamptr->fileID, ncvarID, "scale_factor", astype, 1, &scalefactor);
    }
  }
}

static void
cdfAppendCoordinates(int fileID, int ncvarID, char coordinates[CDI_MAX_NAME])
{
  if (ncvarID != CDI_UNDEFID)
  {
    size_t len = strlen(coordinates);
    if (len) coordinates[len++] = ' ';
    cdf_inq_varname(fileID, ncvarID, coordinates + len);
  }
}

static void
cdfDefineCoordinates(const stream_t *streamptr, int ncvarID, int nczvarID, int gridtype, int gridID, int gridindex, int xid,
                     int yid, size_t gridsize, char axis[5], size_t iax)
{
  int fileID = streamptr->fileID;

  if (gridtype != GRID_GENERIC && gridtype != GRID_LONLAT && gridtype != GRID_PROJECTION && gridtype != GRID_CURVILINEAR
      && gridtype != GRID_HEALPIX && gridtype != GRID_CHARXY)
  {
    size_t len = strlen(gridNamePtr(gridtype));
    if (len > 0) cdf_put_att_text(fileID, ncvarID, "CDI_grid_type", len, gridNamePtr(gridtype));
  }

  char gmapvarname[CDI_MAX_NAME];
  cdf_get_gmapvarname(gridID, gmapvarname);
  if (gmapvarname[0]) cdf_put_att_text(fileID, ncvarID, "grid_mapping", strlen(gmapvarname), gmapvarname);

  if (gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED)
  {
    int numLPE = gridInqNP(gridID);
    if (numLPE > 0) cdf_put_att_int(fileID, ncvarID, "CDI_grid_num_LPE", NC_INT, 1, &numLPE);
  }

  const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridindex]);
  if (gridtype == GRID_GAUSSIAN_REDUCED)
  {
    int ncyvarID = cdfGrid->ncIdList[CDF_VARID_Y];
    if (ncyvarID != CDI_UNDEFID)
    {
      char name[CDI_MAX_NAME];
      name[0] = 0;
      cdf_inq_varname(fileID, ncyvarID, name);
      size_t len = strlen(name);
      cdf_put_att_text(fileID, ncvarID, "CDI_grid_latitudes", len, name);
    }

    int ncrpvarID = cdfGrid->ncIdList[CDF_VARID_RP];
    if (ncrpvarID != CDI_UNDEFID)
    {
      char name[CDI_MAX_NAME];
      name[0] = 0;
      cdf_inq_varname(fileID, ncrpvarID, name);
      size_t len = strlen(name);
      cdf_put_att_text(fileID, ncvarID, "CDI_grid_reduced_points", len, name);
    }
  }

  // define coordinates attribute

  char coordinates[CDI_MAX_NAME];
  coordinates[0] = 0;

  if (nczvarID != CDI_UNDEFID) cdfAppendCoordinates(fileID, nczvarID, coordinates);

  if (gridtype == GRID_TRAJECTORY) { cdf_put_att_text(fileID, ncvarID, "coordinates", 9, "tlon tlat"); }
  else if (gridtype == GRID_LONLAT && xid == CDI_UNDEFID && yid == CDI_UNDEFID && gridsize == 1)
  {
    int ncxvarID = cdfGrid->ncIdList[CDF_VARID_X];
    int ncyvarID = cdfGrid->ncIdList[CDF_VARID_Y];
    cdfAppendCoordinates(fileID, ncyvarID, coordinates);
    cdfAppendCoordinates(fileID, ncxvarID, coordinates);
  }
  else if (gridtype == GRID_GAUSSIAN_REDUCED)
  {
    /*
   int ncxvarID = ncgrid->ncIDs[CDF_VARID_X];
   int ncyvarID = ncgrid->ncIDs[CDF_VARID_Y];
   cdfAppendCoordinates(fileID, ncyvarID, coordinates);
   cdfAppendCoordinates(fileID, ncxvarID, coordinates);
   */
  }
  else if (gridtype == GRID_UNSTRUCTURED || gridtype == GRID_CURVILINEAR)
  {
    int ncxvarID = cdfGrid->ncIdList[CDF_VARID_X];
    int ncyvarID = cdfGrid->ncIdList[CDF_VARID_Y];
    int ncavarID = cdfGrid->ncIdList[CDF_VARID_A];
    // CMOR order: coordinates = "lat lon"
    if (CDI_Coordinates_Lon_Lat)
    {
      cdfAppendCoordinates(fileID, ncxvarID, coordinates);
      cdfAppendCoordinates(fileID, ncyvarID, coordinates);
    }
    else
    {
      cdfAppendCoordinates(fileID, ncyvarID, coordinates);
      cdfAppendCoordinates(fileID, ncxvarID, coordinates);
    }

    if (ncavarID != CDI_UNDEFID)
    {
      char cellarea[CDI_MAX_NAME] = "area: ";
      size_t len = strlen(cellarea);
      cdf_inq_varname(fileID, ncavarID, cellarea + len);
      len = strlen(cellarea);
      cdf_put_att_text(fileID, ncvarID, "cell_measures", len, cellarea);
    }

    if (gridtype == GRID_UNSTRUCTURED)
    {
      int position = gridInqPosition(gridID);
      if (position > 0) cdf_put_att_int(fileID, ncvarID, "number_of_grid_in_reference", NC_INT, 1, &position);
    }
  }
  else if (gridtype == GRID_SPECTRAL || gridtype == GRID_FOURIER)
  {
    axis[iax++] = '-';
    axis[iax++] = '-';
    cdf_put_att_text(fileID, ncvarID, "axis", iax, axis);
    int gridTruncation = gridInqTrunc(gridID);
    cdf_put_att_int(fileID, ncvarID, "truncation", NC_INT, 1, &gridTruncation);
  }
  else if (gridtype == GRID_CHARXY)
  {
    if (gridInqXIsc(gridID))
    {
      int ncxvarID = cdfGrid->ncIdList[CDF_VARID_X];
      cdfAppendCoordinates(fileID, ncxvarID, coordinates);
    }
    else if (gridInqYIsc(gridID))
    {
      int ncyvarID = cdfGrid->ncIdList[CDF_VARID_Y];
      cdfAppendCoordinates(fileID, ncyvarID, coordinates);
    }
  }

  size_t len = strlen(coordinates);
  if (len) cdf_put_att_text(fileID, ncvarID, "coordinates", len, coordinates);
}

static size_t
calc_chunksize(size_t chunkSizeLim, size_t size)
{
  static const size_t pageSize = 4096;

  if (size <= chunkSizeLim) return size;

  size_t numChunks = (size / chunkSizeLim) + 1;
  size_t chunkSize = size / numChunks;
  if (chunkSize % pageSize) chunkSize = (chunkSize / pageSize + 1) * pageSize;

  return chunkSize;
}

static const size_t chunkSizeMin = 262144;  // 256kb
// static const size_t chunkSizeLim = 16777216;  // 16mb
static const size_t chunkSizeLim = 4194304;  // 4mb

size_t
auto_chunksize_y(size_t gridsize, size_t xsize, size_t ysize)
{
  size_t gridChunkSize = calc_chunksize(chunkSizeLim, gridsize);
  size_t yChunkSize = gridChunkSize / xsize;
  size_t numChunks = (ysize % yChunkSize == 0) ? (ysize / yChunkSize) : (ysize / yChunkSize) + 1;
  size_t modChunk = ysize % numChunks;
  size_t maxChunks = numChunks * 2;
  for (size_t i = numChunks; i < maxChunks; ++i)
  {
    size_t imod = ysize % i;
    if (imod == 0 || imod > modChunk)
    {
      numChunks = i;
      modChunk = imod;
      if (modChunk == 0) break;
    }
  }
  yChunkSize = ysize / numChunks;
  return yChunkSize;
}

size_t
calc_chunksize_y(int chunkType, size_t gridsize, size_t xsize, size_t ysize)
{
  if (chunkType == CDI_CHUNK_AUTO)
    return auto_chunksize_y(gridsize, xsize, ysize);
  else
    return (chunkType == CDI_CHUNK_LINES) ? 1 : ysize;
}

size_t
calc_chunksize_x(int chunkType, long chunkSize, size_t xsize, bool isReg2dGrid)
{
  if (chunkType == CDI_CHUNK_AUTO && !isReg2dGrid)
    return (chunkSize > 0 && (size_t) chunkSize < xsize) ? (size_t) chunkSize : ((xsize <= chunkSizeMin) ? xsize : chunkSizeMin);
  else
    return calc_chunksize(chunkSizeLim, xsize);
}

static int
cdfDefineDimsAndChunks(const stream_t *streamptr, int varID, int xid, int yid, int zid, size_t gridsize, const int dimorder[3],
                       int dims[4], bool useChunks, size_t chunks[4], char axis[5], size_t *piax, size_t *pchunkCacheSize)
{
  size_t chunkCacheSize = 1;
  int fileID = streamptr->fileID;
  int vlistID = streamptr->vlistID;

  size_t iax = *piax;
  int ndims = 0;

  for (int i = 0; i < 4; ++i) chunks[i] = 0;

  size_t xsize = 0, ysize = 0, zsize = 0;
  if (xid != CDI_UNDEFID) cdf_inq_dimlen(fileID, xid, &xsize);
  if (yid != CDI_UNDEFID) cdf_inq_dimlen(fileID, yid, &ysize);
  if (zid != CDI_UNDEFID) cdf_inq_dimlen(fileID, zid, &zsize);

  int timetype = vlistInqVarTimetype(vlistID, varID);
  if (vlistHasTime(vlistID) && timetype != TIME_CONSTANT)
  {
    int chunkSizeDimT = 0;
    cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMT, &chunkSizeDimT);
    int tid = streamptr->basetime.ncdimid;
    if (tid == CDI_UNDEFID) Error("Internal problem, time undefined!");
    axis[iax++] = 'T';
    chunks[ndims] = (chunkSizeDimT > 0) ? chunkSizeDimT : 1;
    dims[ndims] = tid;
    chunkCacheSize *= chunks[ndims];
    ndims++;
  }

  int chunkSize = 0;
  int chunkType = CDI_CHUNK_GRID;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKTYPE, &chunkType);
  cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE, &chunkSize);
  if (chunkSize > 0 && yid == CDI_UNDEFID) chunkType = CDI_CHUNK_AUTO;

  bool isReg2dGrid = (yid != CDI_UNDEFID && xid != CDI_UNDEFID);
  if (chunkType == CDI_CHUNK_GRID && gridsize > ChunkSizeLim && isReg2dGrid)
  {
    if (CDI_Debug) fprintf(stderr, "gridsize > %d, changed chunkType to CDI_CHUNK_AUTO!\n", ChunkSizeLim);
    chunkType = CDI_CHUNK_AUTO;
  }

  for (int id = 0; id < 3; ++id)
  {
    if (dimorder[id] == 3 && zid != CDI_UNDEFID)
    {
      int chunkSizeDimZ = 0;
      cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMZ, &chunkSizeDimZ);
      if (chunkSizeDimZ == -1) chunkSizeDimZ = zsize;
      axis[iax++] = 'Z';
      chunks[ndims] = (chunkSizeDimZ > 0) ? chunkSizeDimZ : 1;
      dims[ndims] = zid;
      chunkCacheSize *= (chunkCacheSize > 1) ? zsize : chunks[ndims];
      ndims++;
    }
    else if (dimorder[id] == 2 && yid != CDI_UNDEFID)
    {
      int chunkSizeDimY = 0;
      cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMY, &chunkSizeDimY);
      if (chunkSizeDimY == -1) chunkSizeDimY = ysize;
      if (chunkSizeDimY == 0) chunkSizeDimY = calc_chunksize_y(chunkType, gridsize, xsize, ysize);
      chunks[ndims] = chunkSizeDimY;
      dims[ndims] = yid;
      chunkCacheSize *= ysize;
      ndims++;
    }
    else if (dimorder[id] == 1 && xid != CDI_UNDEFID)
    {
      int chunkSizeDimX = 0;
      cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE_DIMX, &chunkSizeDimX);
      if (chunkSizeDimX == -1 || chunkSizeDimX > (int) xsize) chunkSizeDimX = xsize;
      if (chunkSizeDimX == 0) chunkSizeDimX = calc_chunksize_x(chunkType, chunkSize, xsize, isReg2dGrid);
      chunks[ndims] = chunkSizeDimX;
      dims[ndims] = xid;
      chunkCacheSize *= xsize;
      ndims++;
    }
  }

  if (CDI_Debug)
    fprintf(stderr, "useChunks %d chunkType %d chunkSize %d  chunks %zu %zu %zu %zu\n", useChunks, chunkType, chunkSize, chunks[0],
            chunks[1], chunks[2], chunks[3]);

  *pchunkCacheSize = chunkCacheSize;
  *piax = iax;
  return ndims;
}

static void
cdfDefineAttrLeveltype(int fileID, int ncvarID, int zaxisID, int zaxistype)
{
  // clang-format off
  if ( zaxistype == ZAXIS_CLOUD_BASE          ||
       zaxistype == ZAXIS_CLOUD_TOP           ||
       zaxistype == ZAXIS_ISOTHERM_ZERO       ||
       zaxistype == ZAXIS_TROPOPAUSE          ||
       zaxistype == ZAXIS_TOA                 ||
       zaxistype == ZAXIS_SEA_BOTTOM          ||
       zaxistype == ZAXIS_LAKE_BOTTOM         ||
       zaxistype == ZAXIS_SEDIMENT_BOTTOM     ||
       zaxistype == ZAXIS_SEDIMENT_BOTTOM_TA  ||
       zaxistype == ZAXIS_SEDIMENT_BOTTOM_TW  ||
       zaxistype == ZAXIS_MIX_LAYER           ||
       zaxistype == ZAXIS_ATMOSPHERE )
    {
      char varname[CDI_MAX_NAME];
      int length = CDI_MAX_NAME;
      cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, varname, &length);
      cdf_put_att_text(fileID, ncvarID, "level_type", strlen(varname), varname);
    }
  // clang-format on
}

static void
cdfDefineAttrEnsemble(int fileID, int ncvarID, int vlistID, int varID)
{
  int perturbationNumber, numberOfForecastsInEnsemble, typeOfEnsembleForecast;
  if (cdiInqKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, &perturbationNumber) == 0)
    cdf_put_att_int(fileID, ncvarID, "realization", NC_INT, 1, &perturbationNumber);
  if (cdiInqKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, &numberOfForecastsInEnsemble) == 0)
    cdf_put_att_int(fileID, ncvarID, "ensemble_members", NC_INT, 1, &numberOfForecastsInEnsemble);
  if (cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, &typeOfEnsembleForecast) == 0)
    cdf_put_att_int(fileID, ncvarID, "forecast_init_type", NC_INT, 1, &typeOfEnsembleForecast);
}

static void
cdfCheckVarname(int fileID, char name[CDI_MAX_NAME])
{
  if (name[0])
  {
    int ncvarID;
    char varname[CDI_MAX_NAME];
    snprintf(varname, sizeof(varname), "%s", name);
    size_t len = strlen(varname);
    char *varname2 = varname + len;
    unsigned iz = 0;

    do {
      if (iz) snprintf(varname2, sizeof(varname) - len, "_%u", iz + 1);

      if (nc_inq_varid(fileID, varname, &ncvarID) != NC_NOERR) break;

      ++iz;
    } while (iz <= 99);

    if (iz > 99) Error("Variable name %s already exsist!", name);

    if (!str_is_equal(name, varname))
      Warning("Changed %s entry of variable name '%s' to '%s'!", (iz == 1) ? "double" : "multiple", name, varname);

    strcpy(name, varname);
  }
}

static void
cdfGenVarname(int fileID, char name[CDI_MAX_NAME], int pnum, int pcat, int *pdis, int *pcode)
{
  char varname[CDI_MAX_NAME];

  int code = *pcode;
  if (code < 0) code = -code;
  if (pnum < 0) pnum = -pnum;

  if (*pdis == 255)
    snprintf(varname, sizeof(varname), "var%d", code);
  else
    snprintf(varname, sizeof(varname), "param%d.%d.%d", pnum, pcat, *pdis);

  size_t len = strlen(varname);
  char *varname2 = varname + len;
  int ncvarID;
  unsigned iz = 0;

  do {
    if (iz) snprintf(varname2, sizeof(varname) - len, "_%u", iz + 1);

    if (nc_inq_varid(fileID, varname, &ncvarID) != NC_NOERR) break;

    ++iz;
  } while (iz <= 99);

  if (iz > 99) Error("Variable name %s already exsist!", name);

  strcpy(name, varname);
  *pcode = 0;
  *pdis = 255;
}

static void
cdfDefVarChunkCache(int fileID, int ncvarID, size_t chunkCacheSize)
{
  size_t size = 0;
  size_t nelems = 0;
  float preemption = 0;
  if (nc_get_var_chunk_cache(fileID, ncvarID, &size, &nelems, &preemption) == NC_NOERR)
  {
    if (chunkCacheSize > size) size = chunkCacheSize;
    if (CDI_CacheSize_Out > 0) size = (size_t) CDI_CacheSize_Out;
  }

  nc_set_var_chunk_cache(fileID, ncvarID, size, nelems, preemption);
}

int
cdfDefVar(stream_t *streamptr, int varID)
{
  if (streamptr->vars[varID].ncvarid != CDI_UNDEFID) return streamptr->vars[varID].ncvarid;

  int fileID = streamptr->fileID;
  int fileType = streamptr->filetype;
  if (CDI_Debug) Message("streamID = %d, fileID = %d, varID = %d", streamptr->self, fileID, varID);

  int vlistID = streamptr->vlistID;
  int param = vlistInqVarParam(vlistID, varID);
  int code = vlistInqVarCode(vlistID, varID);
  int pnum, pcat, pdis;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);

  int gridID = vlistInqVarGrid(vlistID, varID);
  SizeType gridsize = gridInqSize(gridID);
  int gridtype = gridInqType(gridID);
  int gridindex = nc_grid_index(streamptr, gridID);
  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridindex]);
  int xid = (gridtype != GRID_TRAJECTORY) ? cdfGrid->ncIdList[CDF_DIMID_X] : CDI_UNDEFID;
  int yid = (gridtype != GRID_TRAJECTORY && gridtype != GRID_GAUSSIAN_REDUCED) ? cdfGrid->ncIdList[CDF_DIMID_Y] : CDI_UNDEFID;

  int zaxisID = vlistInqVarZaxis(vlistID, varID);
  int zaxistype = zaxisInqType(zaxisID);
  int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  int zid = cdfInfo->zaxisIdList[zaxisindex];

  int dimorder[3];  // ZYX/321 and ZXY/312
  vlistInqVarDimorder(vlistID, varID, dimorder);
  bool useChunks
      = (gridsize >= 32) && ((dimorder[0] == 3) || (dimorder[1] == 3 && dimorder[2] == 1 && gridsize == gridInqXsize(gridID)));

  if (((dimorder[0] > 0) + (dimorder[1] > 0) + (dimorder[2] > 0))
      < ((xid != CDI_UNDEFID) + (yid != CDI_UNDEFID) + (zid != CDI_UNDEFID)))
  {
    printf("zid=%d  yid=%d  xid=%d\n", zid, yid, xid);
    Error("Internal problem, dimension order missing!");
  }

  size_t iax = 0;
  char axis[5];
  int dims[4];
  size_t chunks[4];
  size_t chunkCacheSize = 1;
  int ndims = cdfDefineDimsAndChunks(streamptr, varID, xid, yid, zid, (size_t) gridsize, dimorder, dims, useChunks, chunks, axis,
                                     &iax, &chunkCacheSize);

  char name[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length);

  char longname[CDI_MAX_NAME];
  vlistInqVarLongname(vlistID, varID, longname);

  char units[CDI_MAX_NAME];
  vlistInqVarUnits(vlistID, varID, units);

  char stdname[CDI_MAX_NAME];
  length = CDI_MAX_NAME;
  cdiInqKeyString(vlistID, varID, CDI_KEY_STDNAME, stdname, &length);

  int tableID = vlistInqVarTable(vlistID, varID);
  if (!name[0]) tableInqEntry(tableID, code, -1, name, longname, units);
  if (name[0])
    cdfCheckVarname(fileID, name);
  else
    cdfGenVarname(fileID, name, pnum, pcat, &pdis, &code);

  if (streamptr->ncmode == 2)
  {
    cdf_redef(fileID);
    streamptr->ncmode = 1;
  }

  int dtype = vlistInqVarDatatype(vlistID, varID);
  nc_type xtype = cdfDefDatatype(dtype, streamptr);

  int ncvarID = -1;
  cdf_def_var(fileID, name, xtype, ndims, dims, &ncvarID);

  chunkCacheSize *= cdf_xtype_to_numbytes(xtype);
  cdfDefVarChunkCache(fileID, ncvarID, chunkCacheSize);

#ifdef NC_QUANTIZE_BITROUND
  if (xtype == NC_FLOAT || xtype == NC_DOUBLE)
  {
    int nsb = vlistInqVarNSB(vlistID, varID);
    if (nsb > 0) nc_def_var_quantize(fileID, ncvarID, NC_QUANTIZE_BITROUND, nsb);
    // if (nsb > 0) nc_def_var_quantize(fileID, ncvarID, NC_QUANTIZE_GRANULARBR, nsb);
  }
#endif

  if (useChunks && (fileType == CDI_FILETYPE_NC4 || fileType == CDI_FILETYPE_NC4C || fileType == CDI_FILETYPE_NCZARR))
    cdf_def_var_chunking(fileID, ncvarID, NC_CHUNKED, chunks);

  if (useChunks) cdfDefVarCompression(streamptr, ncvarID, xtype);
  if (useChunks)
  {
    char filterSpec[CDI_MAX_NAME];
    length = CDI_MAX_NAME;
    if (cdiInqKeyString(vlistID, varID, CDI_KEY_FILTERSPEC, filterSpec, &length) == CDI_NOERR)
    {
      cdfDefVarFilter(fileID, fileType, ncvarID, filterSpec);
    }
    else if (streamptr->filterSpec) { cdfDefVarFilter(fileID, fileType, ncvarID, streamptr->filterSpec); }
  }

  if (*stdname) cdf_put_att_text(fileID, ncvarID, "standard_name", strlen(stdname), stdname);
  if (*longname) cdf_put_att_text(fileID, ncvarID, "long_name", strlen(longname), longname);
  if (*units) cdf_put_att_text(fileID, ncvarID, "units", strlen(units), units);

  if (code > 0 && pdis == 255) cdf_put_att_int(fileID, ncvarID, "code", NC_INT, 1, &code);

  if (pdis != 255)
  {
    char paramstr[32];
    cdiParamToString(param, paramstr, sizeof(paramstr));
    cdf_put_att_text(fileID, ncvarID, "param", strlen(paramstr), paramstr);
  }

  if (tableID != CDI_UNDEFID)
  {
    int tablenum = tableInqNum(tableID);
    if (tablenum > 0) cdf_put_att_int(fileID, ncvarID, "table", NC_INT, 1, &tablenum);
  }

  bool zaxisIsScalar = (zid == CDI_UNDEFID) ? (zaxisInqScalar(zaxisID) > 0) : false;
  int nczvarID = (zaxisIsScalar || zaxistype == ZAXIS_CHAR) ? streamptr->cdfInfo.ncZvarIdList[zaxisindex] : CDI_UNDEFID;

  cdfDefineCoordinates(streamptr, ncvarID, nczvarID, gridtype, gridID, gridindex, xid, yid, (size_t) gridsize, axis, iax);

  cdfDefVarPacking(streamptr, ncvarID, xtype, vlistID, varID);

  if (dtype == CDI_DATATYPE_UINT8 && xtype == NC_BYTE)
  {
    int validrange[2] = { 0, 255 };
    cdf_put_att_int(fileID, ncvarID, "valid_range", NC_SHORT, 2, validrange);
    cdf_put_att_text(fileID, ncvarID, "_Unsigned", 4, "true");
  }

  streamptr->vars[varID].ncvarid = ncvarID;

  if (vlistInqVarMissvalUsed(vlistID, varID)) cdfDefVarMissval(streamptr, varID, vlistInqVarDatatype(vlistID, varID), 0);

  if (zid == CDI_UNDEFID) cdfDefineAttrLeveltype(fileID, ncvarID, zaxisID, zaxistype);

  cdfDefineAttrEnsemble(fileID, ncvarID, vlistID, varID);

  // Attribute: cell_methods
  cdfDefineCellMethods(streamptr, vlistID, varID, fileID, ncvarID);

  // Attributes
  cdfDefineAttributes(fileType, vlistID, varID, fileID, ncvarID);

  // Institute
  if (vlistInqInstitut(vlistID) == CDI_UNDEFID) cdfDefineInstituteName(vlistID, varID, fileID, ncvarID);

  return ncvarID;
}

void
cdfEndDef(stream_t *streamptr)
{
  cdfDefGlobalAtts(streamptr);

  if (streamptr->accessmode == 0)
  {
    int fileID = streamptr->fileID;
    if (streamptr->ncmode == 2)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    int nvars = streamptr->nvars;
    for (int varID = 0; varID < nvars; ++varID) cdfDefVar(streamptr, varID);

    if (streamptr->ncmode != 2)
    {
      if (CDI_Netcdf_Hdr_Pad == 0UL)
        cdf_enddef(fileID, streamptr->self);
      else
        cdf__enddef(fileID, streamptr->self, CDI_Netcdf_Hdr_Pad);
      streamptr->ncmode = 2;
    }

    streamptr->accessmode = 1;
  }
}

static void
cdfWriteGridTraj(stream_t *streamptr, int gridID)
{
  int gridindex = nc_grid_index(streamptr, gridID);
  const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridindex]);
  int lonID = cdfGrid->ncIdList[CDF_DIMID_X];
  int latID = cdfGrid->ncIdList[CDF_DIMID_Y];
  size_t index = (size_t) streamptr->curTsID;

  double xlon = gridInqXval(gridID, 0);
  double xlat = gridInqYval(gridID, 0);

  cdf_put_var1_double(streamptr->fileID, lonID, &index, &xlon);
  cdf_put_var1_double(streamptr->fileID, latID, &index, &xlat);
}

static void
cdf_write_var_data(int fileID, int vlistID, int varID, int ncvarID, int dtype, size_t nvals, size_t xsize, size_t ysize,
                   bool swapxy, size_t *start, size_t *count, int memtype, const void *data, size_t numMissVals)
{
  const double *pdata_dp = (const double *) data;
  double *mdata_dp = NULL;
  double *sdata_dp = NULL;
  const float *pdata_sp = (const float *) data;
  float *mdata_sp = NULL;
  float *sdata_sp = NULL;

  /*  if ( dtype == CDI_DATATYPE_INT8 || dtype == CDI_DATATYPE_INT16 || dtype == CDI_DATATYPE_INT32 ) */
  {
    double missval = vlistInqVarMissval(vlistID, varID);
    double addoffset = 0.0, scalefactor = 1.0;
    bool haveAddoffset = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_ADDOFFSET, &addoffset) == CDI_NOERR);
    bool haveScalefactor = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_SCALEFACTOR, &scalefactor) == CDI_NOERR);

    if (haveAddoffset || haveScalefactor)
    {
      if (memtype == MEMTYPE_FLOAT)
      {
        mdata_sp = (float *) Malloc(nvals * sizeof(float));
        memcpy(mdata_sp, pdata_sp, nvals * sizeof(float));
        pdata_sp = mdata_sp;

        if (numMissVals > 0)
        {
          for (size_t i = 0; i < nvals; ++i)
          {
            double temp = mdata_sp[i];
            if (!DBL_IS_EQUAL(temp, (float) missval))
            {
              if (haveAddoffset) temp -= addoffset;
              if (haveScalefactor) temp /= scalefactor;
              mdata_sp[i] = (float) temp;
            }
          }
        }
        else
        {
          for (size_t i = 0; i < nvals; ++i)
          {
            double temp = mdata_sp[i];
            if (haveAddoffset) temp -= addoffset;
            if (haveScalefactor) temp /= scalefactor;
            mdata_sp[i] = (float) temp;
          }
        }
      }
      else
      {
        mdata_dp = (double *) Malloc(nvals * sizeof(double));
        memcpy(mdata_dp, pdata_dp, nvals * sizeof(double));
        pdata_dp = mdata_dp;

        if (numMissVals > 0)
        {
          for (size_t i = 0; i < nvals; ++i)
          {
            if (!DBL_IS_EQUAL(mdata_dp[i], missval))
            {
              if (haveAddoffset) mdata_dp[i] -= addoffset;
              if (haveScalefactor) mdata_dp[i] /= scalefactor;
            }
          }
        }
        else
        {
          for (size_t i = 0; i < nvals; ++i)
          {
            if (haveAddoffset) mdata_dp[i] -= addoffset;
            if (haveScalefactor) mdata_dp[i] /= scalefactor;
          }
        }
      }
    }

    if (dtype == CDI_DATATYPE_UINT8 || dtype == CDI_DATATYPE_INT8 || dtype == CDI_DATATYPE_UINT16 || dtype == CDI_DATATYPE_INT16
        || dtype == CDI_DATATYPE_UINT32 || dtype == CDI_DATATYPE_INT32)
    {
      if (memtype == MEMTYPE_FLOAT)
      {
        if (mdata_sp == NULL)
        {
          mdata_sp = (float *) Malloc(nvals * sizeof(float));
          memcpy(mdata_sp, pdata_sp, nvals * sizeof(float));
          pdata_sp = mdata_sp;
        }

        for (size_t i = 0; i < nvals; ++i) mdata_sp[i] = roundf(mdata_sp[i]);

        if (dtype == CDI_DATATYPE_UINT8)
        {
          nc_type xtype;
          cdf_inq_vartype(fileID, ncvarID, &xtype);
          if (xtype == NC_BYTE)
          {
            for (size_t i = 0; i < nvals; ++i)
              if (mdata_sp[i] > 127) mdata_sp[i] -= 256;
          }
        }
      }
      else
      {
        if (mdata_dp == NULL)
        {
          mdata_dp = (double *) Malloc(nvals * sizeof(double));
          memcpy(mdata_dp, pdata_dp, nvals * sizeof(double));
          pdata_dp = mdata_dp;
        }

        for (size_t i = 0; i < nvals; ++i) mdata_dp[i] = round(mdata_dp[i]);

        if (dtype == CDI_DATATYPE_UINT8)
        {
          nc_type xtype;
          cdf_inq_vartype(fileID, ncvarID, &xtype);
          if (xtype == NC_BYTE)
          {
            for (size_t i = 0; i < nvals; ++i)
              if (mdata_dp[i] > 127) mdata_dp[i] -= 256;
          }
        }
      }
    }

    if (CDF_Debug)
    {
      double fmin = 1.0e200;
      double fmax = -1.0e200;
      if (memtype == MEMTYPE_FLOAT)
      {
        for (size_t i = 0; i < nvals; ++i)
        {
          if (!DBL_IS_EQUAL(pdata_sp[i], (float) missval))
          {
            if (pdata_sp[i] < fmin) fmin = pdata_sp[i];
            if (pdata_sp[i] > fmax) fmax = pdata_sp[i];
          }
        }
      }
      else
      {
        for (size_t i = 0; i < nvals; ++i)
        {
          if (!DBL_IS_EQUAL(pdata_dp[i], missval))
          {
            if (pdata_dp[i] < fmin) fmin = pdata_dp[i];
            if (pdata_dp[i] > fmax) fmax = pdata_dp[i];
          }
        }
      }

      Message("nvals = %zu, numMissVals = %d, missval = %g, minval = %g, maxval = %g", nvals, numMissVals, missval, fmin, fmax);
    }
  }

  if (swapxy)  // implemented only for cdf_write_var_slice()
  {
    size_t gridsize = xsize * ysize;
    if (memtype == MEMTYPE_FLOAT)
    {
      sdata_sp = (float *) Malloc(gridsize * sizeof(float));
      for (size_t j = 0; j < ysize; ++j)
        for (size_t i = 0; i < xsize; ++i) sdata_sp[i * ysize + j] = pdata_sp[j * xsize + i];
      pdata_sp = sdata_sp;
    }
    else
    {
      sdata_dp = (double *) Malloc(gridsize * sizeof(double));
      for (size_t j = 0; j < ysize; ++j)
        for (size_t i = 0; i < xsize; ++i) sdata_dp[i * ysize + j] = pdata_dp[j * xsize + i];
      pdata_dp = sdata_dp;
    }
  }

  if (dtype == CDI_DATATYPE_CPX32 || dtype == CDI_DATATYPE_CPX64)
  {
    void *pdata = (memtype == MEMTYPE_FLOAT) ? (void *) pdata_sp : (void *) pdata_dp;
    float *cdata_sp = NULL;
    double *cdata_dp = NULL;
    if (memtype == MEMTYPE_FLOAT && dtype == CDI_DATATYPE_CPX64)
    {
      cdata_dp = (double *) Malloc(2 * nvals * sizeof(double));
      for (size_t i = 0; i < nvals; ++i)
      {
        cdata_dp[2 * i] = (double) (pdata_sp[2 * i]);
        cdata_dp[2 * i + 1] = (double) (pdata_sp[2 * i + 1]);
      }
      pdata = cdata_dp;
    }
    else if (memtype == MEMTYPE_DOUBLE && dtype == CDI_DATATYPE_CPX32)
    {
      cdata_sp = (float *) Malloc(2 * nvals * sizeof(float));
      for (size_t i = 0; i < nvals; ++i)
      {
        cdata_sp[2 * i] = (float) (pdata_dp[2 * i]);
        cdata_sp[2 * i + 1] = (float) (pdata_dp[2 * i + 1]);
      }
      pdata = cdata_sp;
    }

    cdf_put_vara(fileID, ncvarID, start, count, pdata);
    if (cdata_sp) Free(cdata_sp);
    if (cdata_dp) Free(cdata_dp);
  }
  else
  {
    if (memtype == MEMTYPE_FLOAT)
      cdf_put_vara_float(fileID, ncvarID, start, count, pdata_sp);
    else
      cdf_put_vara_double(fileID, ncvarID, start, count, pdata_dp);
  }

  if (mdata_dp) Free(mdata_dp);
  if (sdata_dp) Free(sdata_dp);
  if (mdata_sp) Free(mdata_sp);
  if (sdata_sp) Free(sdata_sp);
}

static void
cdfGetXYZid(stream_t *streamptr, int gridID, int zaxisID, int *xid, int *yid, int *zid)
{
  *xid = CDI_UNDEFID;
  *yid = CDI_UNDEFID;

  int gridtype = gridInqType(gridID);
  if (gridtype == GRID_TRAJECTORY) { cdfWriteGridTraj(streamptr, gridID); }
  else
  {
    int gridindex = nc_grid_index(streamptr, gridID);
    const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridindex]);
    *xid = cdfGrid->ncIdList[CDF_DIMID_X];
    if (gridtype != GRID_GAUSSIAN_REDUCED) *yid = cdfGrid->ncIdList[CDF_DIMID_Y];
  }

  int vlistID = streamptr->vlistID;
  int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  *zid = streamptr->cdfInfo.zaxisIdList[zaxisindex];
}

static void
cdfDefineStartAndCount(stream_t *streamptr, int varID, int xid, int yid, int zid, size_t start[5], size_t count[5], size_t *xsize,
                       size_t *ysize)
{
  size_t ndims = 0;
  *xsize = 0;
  *ysize = 0;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  long ntsteps = streamptr->ntsteps;
  if (CDI_Debug) Message("ntsteps = %ld", ntsteps);

  int timetype = vlistInqVarTimetype(vlistID, varID);

  if (vlistHasTime(vlistID) && timetype != TIME_CONSTANT)
  {
    start[ndims] = (size_t) ntsteps - 1;
    count[ndims] = 1;
    ndims++;
  }

  if (zid != CDI_UNDEFID)
  {
    int zaxisID = vlistInqVarZaxis(vlistID, varID);
    start[ndims] = 0;
    count[ndims] = (size_t) zaxisInqSize(zaxisID);
    ndims++;
  }

  if (yid != CDI_UNDEFID)
  {
    start[ndims] = 0;
    size_t size;
    cdf_inq_dimlen(fileID, yid, &size);
    /*      count[ndims] = gridInqYsize(gridID); */
    count[ndims] = size;
    ndims++;
  }

  if (xid != CDI_UNDEFID)
  {
    start[ndims] = 0;
    size_t size;
    cdf_inq_dimlen(fileID, xid, &size);
    /*      count[ndims] = gridInqXsize(gridID); */
    count[ndims] = size;
    ndims++;
  }

  if (CDI_Debug)
    for (size_t idim = 0; idim < ndims; ++idim) Message("dim = %d  start = %d  count = %d", idim, start[idim], count[idim]);
}

void
cdf_write_var(stream_t *streamptr, int varID, int memtype, const void *data, size_t numMissVals)
{
  if (streamptr->accessmode == 0) cdfEndDef(streamptr);

  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  int ncvarID = cdfDefVar(streamptr, varID);

  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);

  int xid, yid, zid;
  cdfGetXYZid(streamptr, gridID, zaxisID, &xid, &yid, &zid);

  size_t xsize, ysize;
  size_t start[5], count[5];
  cdfDefineStartAndCount(streamptr, varID, xid, yid, zid, start, count, &xsize, &ysize);

  if (streamptr->ncmode == 1)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  int dtype = vlistInqVarDatatype(vlistID, varID);

  if (numMissVals > 0) cdfDefVarMissval(streamptr, varID, dtype, 1);

  size_t nvals = (size_t) gridInqSize(gridID) * (size_t) (zaxisInqSize(zaxisID));

  bool swapxy = false;
  cdf_write_var_data(fileID, vlistID, varID, ncvarID, dtype, nvals, xsize, ysize, swapxy, start, count, memtype, data, numMissVals);
}

static void
cdfDefineStartAndCountChunk(stream_t *streamptr, const int rect[][2], int varID, int xid, int yid, int zid, size_t start[5],
                            size_t count[5], size_t *xsize, size_t *ysize)
{
  size_t ndims = 0;
  *xsize = 0;
  *ysize = 0;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  long ntsteps = streamptr->ntsteps;
  if (CDI_Debug) Message("ntsteps = %ld", ntsteps);

  int timetype = vlistInqVarTimetype(vlistID, varID);

  if (vlistHasTime(vlistID) && timetype != TIME_CONSTANT)
  {
    start[ndims] = (size_t) ntsteps - 1;
    count[ndims] = 1;
    ndims++;
  }

  if (zid != CDI_UNDEFID)
  {
    int zaxisID = vlistInqVarZaxis(vlistID, varID);
    int size = zaxisInqSize(zaxisID);
    xassert(rect[2][0] >= 0 && rect[2][0] <= rect[2][1] + 1 && rect[2][1] <= size);
    start[ndims] = (size_t) rect[2][0];
    count[ndims] = rect[2][1] < 0 ? (size_t) 0 : (size_t) rect[2][1] - (size_t) rect[2][0] + 1;
    ndims++;
  }

  if (yid != CDI_UNDEFID)
  {
    size_t size;
    cdf_inq_dimlen(fileID, yid, &size);
    xassert(rect[1][0] >= 0 && rect[1][0] <= rect[1][1] + 1 && (rect[1][1] < 0 || (size_t) rect[1][1] <= size));
    start[ndims] = (size_t) rect[1][0];
    count[ndims] = rect[1][1] < 0 ? (size_t) 0 : ((size_t) rect[1][1] - (size_t) rect[1][0] + 1);
    ndims++;
  }

  if (xid != CDI_UNDEFID)
  {
    size_t size;
    cdf_inq_dimlen(fileID, xid, &size);
    xassert(rect[0][0] >= 0 && rect[0][0] <= rect[0][1] + 1 && (rect[0][1] < 0 || (size_t) rect[0][1] <= size));
    start[ndims] = (size_t) rect[0][0];
    count[ndims] = rect[0][1] < 0 ? (size_t) 0 : (size_t) rect[0][1] - (size_t) rect[0][0] + 1;
    ndims++;
  }

  if (CDI_Debug)
    for (size_t idim = 0; idim < ndims; ++idim) Message("dim = %d  start = %d  count = %d", idim, start[idim], count[idim]);
}

void
cdf_write_var_chunk(stream_t *streamptr, int varID, int memtype, const int rect[][2], const void *data, size_t numMissVals)
{
  if (streamptr->accessmode == 0) cdfEndDef(streamptr);

  int streamID = streamptr->self;

  if (CDI_Debug) Message("streamID = %d  varID = %d", streamID, varID);

  int vlistID = streamInqVlist(streamID);
  int fileID = streamInqFileID(streamID);

  int ncvarID = cdfDefVar(streamptr, varID);

  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);

  int xid, yid, zid;
  cdfGetXYZid(streamptr, gridID, zaxisID, &xid, &yid, &zid);

  size_t xsize, ysize;
  size_t start[5], count[5];
  cdfDefineStartAndCountChunk(streamptr, rect, varID, xid, yid, zid, start, count, &xsize, &ysize);

  if (streamptr->ncmode == 1)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  int dtype = vlistInqVarDatatype(vlistID, varID);

  if (numMissVals > 0) cdfDefVarMissval(streamptr, varID, dtype, 1);

  size_t nvals = (size_t) gridInqSize(gridID) * (size_t) zaxisInqSize(zaxisID);

  bool swapxy = false;
  cdf_write_var_data(fileID, vlistID, varID, ncvarID, dtype, nvals, xsize, ysize, swapxy, start, count, memtype, data, numMissVals);
}

static void
cdfDefineStartAndCountSlice(stream_t *streamptr, int varID, int levelID, int dimorder[3], int xid, int yid, int zid,
                            size_t start[5], size_t count[5], size_t *xsize, size_t *ysize)
{
  size_t ndims = 0;
  *xsize = 0;
  *ysize = 0;

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  long ntsteps = streamptr->ntsteps;
  if (CDI_Debug) Message("ntsteps = %ld", ntsteps);

  int timetype = vlistInqVarTimetype(vlistID, varID);

  if (vlistHasTime(vlistID) && timetype != TIME_CONSTANT)
  {
    start[ndims] = (size_t) ntsteps - 1;
    count[ndims] = 1;
    ndims++;
  }

  for (int id = 0; id < 3; ++id)
  {
    if (dimorder[id] == 3 && zid != CDI_UNDEFID)
    {
      start[ndims] = (size_t) levelID;
      count[ndims] = 1;
      ndims++;
    }
    else if (dimorder[id] == 2 && yid != CDI_UNDEFID)
    {
      start[ndims] = 0;
      cdf_inq_dimlen(fileID, yid, ysize);
      count[ndims] = *ysize;
      ndims++;
    }
    else if (dimorder[id] == 1 && xid != CDI_UNDEFID)
    {
      start[ndims] = 0;
      cdf_inq_dimlen(fileID, xid, xsize);
      count[ndims] = *xsize;
      ndims++;
    }
  }

  if (CDI_Debug)
    for (size_t idim = 0; idim < ndims; ++idim) Message("dim = %d  start = %d  count = %d", idim, start[idim], count[idim]);
}

void
cdf_write_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, const void *data, size_t numMissVals)
{
  if (streamptr->accessmode == 0) cdfEndDef(streamptr);

  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  int ncvarID = cdfDefVar(streamptr, varID);

  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);

  int xid, yid, zid;
  cdfGetXYZid(streamptr, gridID, zaxisID, &xid, &yid, &zid);

  int dimorder[3];
  vlistInqVarDimorder(vlistID, varID, dimorder);
  bool swapxy = (dimorder[2] == 2 || dimorder[0] == 1) && xid != CDI_UNDEFID && yid != CDI_UNDEFID;

  size_t xsize, ysize;
  size_t start[5], count[5];
  cdfDefineStartAndCountSlice(streamptr, varID, levelID, dimorder, xid, yid, zid, start, count, &xsize, &ysize);

  int dtype = vlistInqVarDatatype(vlistID, varID);

  if (numMissVals > 0) cdfDefVarMissval(streamptr, varID, dtype, 1);

  size_t nvals = (size_t) gridInqSize(gridID);

  cdf_write_var_data(fileID, vlistID, varID, ncvarID, dtype, nvals, xsize, ysize, swapxy, start, count, memtype, data, numMissVals);
}

void
cdf_write_field(stream_t *streamptr, int memtype, const void *data, size_t numMissVals)
{
  int varID = streamptr->record->varID;
  int levelID = streamptr->record->levelID;
  cdf_write_var_slice(streamptr, varID, levelID, memtype, data, numMissVals);
}

#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF

#include <assert.h>
#include <limits.h>
#include <float.h>


static void
cdfReadGridTraj(stream_t *streamptr, int gridID)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;

  int gridindex = vlistGridIndex(vlistID, gridID);
  const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridindex]);
  int ncLonId = cdfGrid->ncIdList[CDF_VARID_X];
  int ncLatId = cdfGrid->ncIdList[CDF_VARID_Y];

  int tsID = streamptr->curTsID;
  size_t ncStepIndex = (size_t) streamptr->tsteps[tsID].ncStepIndex;

  double xlon, xlat;
  cdf_get_var1_double(fileID, ncLonId, &ncStepIndex, &xlon);
  cdf_get_var1_double(fileID, ncLatId, &ncStepIndex, &xlat);

  gridDefXvals(gridID, &xlon);
  gridDefYvals(gridID, &xlat);
}

static void
cdfGetSlapDescription(stream_t *streamptr, int varID, size_t (*start)[MAX_DIMENSIONS], size_t (*count)[MAX_DIMENSIONS])
{
  int vlistID = streamptr->vlistID;
  int tsID = streamptr->curTsID;
  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);
  int timetype = vlistInqVarTimetype(vlistID, varID);
  int gridindex = vlistGridIndex(vlistID, gridID);
  const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridindex]);
  size_t ncStepIndex = (size_t) streamptr->tsteps[tsID].ncStepIndex;

  int xid = CDI_UNDEFID, yid = CDI_UNDEFID;
  if (gridInqType(gridID) == GRID_TRAJECTORY) { cdfReadGridTraj(streamptr, gridID); }
  else
  {
    xid = cdfGrid->ncIdList[CDF_DIMID_X];
    yid = cdfGrid->ncIdList[CDF_DIMID_Y];
  }
  int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  int zid = streamptr->cdfInfo.zaxisIdList[zaxisindex];

  int ndims = 0;
#define addDimension(startCoord, length) \
  do {                                   \
    (*start)[ndims] = startCoord;        \
    (*count)[ndims] = length;            \
    ndims++;                             \
  } while (0)
  if (timetype != TIME_CONSTANT) addDimension(ncStepIndex, 1);
  if (zid != CDI_UNDEFID) addDimension(0, (size_t) zaxisInqSize(zaxisID));
  if (yid != CDI_UNDEFID) addDimension(0, (size_t) gridInqYsize(gridID));
  if (xid != CDI_UNDEFID) addDimension(0, (size_t) gridInqXsize(gridID));
#undef addDimension

  assert(ndims <= (int) (sizeof(*start) / sizeof(**start)));
  assert(ndims <= (int) (sizeof(*count) / sizeof(**count)));

  if (CDI_Debug)
    for (int idim = 0; idim < ndims; ++idim) Message("dim = %d  start = %d  count = %d", idim, start[idim], count[idim]);
}

// Scans the data array for missVals, optionally applying first a scale factor and then an offset.
// Returns the number of missing + out-of-range values encountered.
static size_t
cdfDoInputDataTransformationDP(int vlistID, int varID, size_t valueCount, double *data)
{
  double missVal = vlistInqVarMissval(vlistID, varID);
  int haveMissVal = vlistInqVarMissvalUsed(vlistID, varID);
  double validRange[2];
  if (!(haveMissVal && vlistInqVarValidrange(vlistID, varID, validRange))) validRange[0] = DBL_MIN, validRange[1] = DBL_MAX;
  double addoffset = 0.0, scalefactor = 1.0;
  int haveAddoffset = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_ADDOFFSET, &addoffset) == CDI_NOERR);
  int haveScalefactor = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_SCALEFACTOR, &scalefactor) == CDI_NOERR);

  bool missValIsNaN = DBL_IS_NAN(missVal);
  size_t missValCount = 0;

  double validMin = validRange[0];
  double validMax = validRange[1];
  if (IS_EQUAL(validMin, VALIDMISS)) validMin = DBL_MIN;
  if (IS_EQUAL(validMax, VALIDMISS)) validMax = DBL_MAX;

  int haveRangeCheck = (IS_NOT_EQUAL(validMax, DBL_MAX)) | (IS_NOT_EQUAL(validMin, DBL_MIN));
  assert(!haveRangeCheck || haveMissVal);

  switch (haveMissVal | (haveScalefactor << 1) | (haveAddoffset << 2) | (haveRangeCheck << 3))
  {
    case 15:  // haveRangeCheck & haveMissVal & haveScalefactor & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? missVal : isMissVal ? data[i] : data[i] * scalefactor + addoffset;
      }
      break;
    case 13:  // haveRangeCheck & haveMissVal & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? missVal : isMissVal ? data[i] : data[i] + addoffset;
      }
      break;
    case 11:  // haveRangeCheck & haveMissVal & haveScalefactor
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? missVal : isMissVal ? data[i] : data[i] * scalefactor;
      }
      break;
    case 9:  // haveRangeCheck & haveMissVal
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? missVal : data[i];
      }
      break;
    case 7:  // haveMissVal & haveScalefactor & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
        if (DBL_IS_EQUAL(data[i], missVal))
          missValCount++;
        else
          data[i] = data[i] * scalefactor + addoffset;
      break;
    case 6:  // haveAddoffset & haveScalefactor
      for (size_t i = 0; i < valueCount; ++i) data[i] = data[i] * scalefactor + addoffset;
      break;
    case 5:  // haveMissVal & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
        if (DBL_IS_EQUAL(data[i], missVal))
          missValCount++;
        else
          data[i] += addoffset;
      break;
    case 4:  // haveAddoffset
      for (size_t i = 0; i < valueCount; ++i) data[i] += addoffset;
      break;
    case 3:  // haveMissVal & haveScalefactor
      for (size_t i = 0; i < valueCount; ++i)
        if (DBL_IS_EQUAL(data[i], missVal))
          missValCount++;
        else
          data[i] *= scalefactor;
      break;
    case 2:  // haveScalefactor
      for (size_t i = 0; i < valueCount; ++i) data[i] *= scalefactor;
      break;
    case 1:  // haveMissVal
      if (missValIsNaN)
      {
        for (size_t i = 0; i < valueCount; ++i) missValCount += (size_t) DBL_IS_NAN(data[i]);
      }
      else
      {
        for (size_t i = 0; i < valueCount; ++i) missValCount += (size_t) DBL_IS_EQUAL(data[i], missVal);
      }
      break;
  }

  return missValCount;
}

static size_t
cdfDoInputDataTransformationSP(int vlistID, int varID, size_t valueCount, float *data)
{
  double missVal = vlistInqVarMissval(vlistID, varID);
  int haveMissVal = vlistInqVarMissvalUsed(vlistID, varID);
  double validRange[2];
  if (!(haveMissVal && vlistInqVarValidrange(vlistID, varID, validRange))) validRange[0] = DBL_MIN, validRange[1] = DBL_MAX;
  double addoffset = 0.0, scalefactor = 1.0;
  int haveAddoffset = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_ADDOFFSET, &addoffset) == CDI_NOERR);
  int haveScalefactor = (cdiInqKeyFloat(vlistID, varID, CDI_KEY_SCALEFACTOR, &scalefactor) == CDI_NOERR);

  bool missValIsNaN = DBL_IS_NAN(missVal);
  size_t missValCount = 0;

  double validMin = validRange[0];
  double validMax = validRange[1];
  if (IS_EQUAL(validMin, VALIDMISS)) validMin = DBL_MIN;
  if (IS_EQUAL(validMax, VALIDMISS)) validMax = DBL_MAX;

  int haveRangeCheck = (IS_NOT_EQUAL(validMax, DBL_MAX)) | (IS_NOT_EQUAL(validMin, DBL_MIN));
  assert(!haveRangeCheck || haveMissVal);

  switch (haveMissVal | (haveScalefactor << 1) | (haveAddoffset << 2) | (haveRangeCheck << 3))
  {
    case 15:  // haveRangeCheck & haveMissVal & haveScalefactor & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? (float) missVal : isMissVal ? data[i] : (float) (data[i] * scalefactor + addoffset);
      }
      break;
    case 13:  // haveRangeCheck & haveMissVal & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? (float) missVal : isMissVal ? data[i] : (float) (data[i] + addoffset);
      }
      break;
    case 11:  // haveRangeCheck & haveMissVal & haveScalefactor
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? (float) missVal : isMissVal ? data[i] : (float) (data[i] * scalefactor);
      }
      break;
    case 9:  // haveRangeCheck & haveMissVal
      for (size_t i = 0; i < valueCount; ++i)
      {
        int outOfRange = (data[i] < validMin || data[i] > validMax);
        int isMissVal = DBL_IS_EQUAL(data[i], missVal);
        missValCount += (size_t) (outOfRange | isMissVal);
        data[i] = outOfRange ? (float) missVal : data[i];
      }
      break;
    case 7:  // haveMissVal & haveScalefactor & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
        if (DBL_IS_EQUAL(data[i], missVal))
          missValCount++;
        else
          data[i] = (float) (data[i] * scalefactor + addoffset);
      break;
    case 6:  // haveAddoffset & haveScalefactor
      for (size_t i = 0; i < valueCount; ++i) data[i] = (float) (data[i] * scalefactor + addoffset);
      break;
    case 5:  // haveMissVal & haveAddoffset
      for (size_t i = 0; i < valueCount; ++i)
        if (DBL_IS_EQUAL(data[i], missVal))
          missValCount++;
        else
          data[i] = (float) (data[i] + addoffset);
      break;
    case 4:  // haveAddoffset
      for (size_t i = 0; i < valueCount; ++i) data[i] = (float) (data[i] + addoffset);
      break;
    case 3:  // haveMissVal & haveScalefactor
      for (size_t i = 0; i < valueCount; ++i)
        if (DBL_IS_EQUAL(data[i], missVal))
          missValCount++;
        else
          data[i] = (float) (data[i] * scalefactor);
      break;
    case 2:  // haveScalefactor
      for (size_t i = 0; i < valueCount; ++i) data[i] = (float) (data[i] * scalefactor);
      break;
    case 1:  // haveMissVal
      if (missValIsNaN)
      {
        for (size_t i = 0; i < valueCount; ++i) missValCount += (size_t) DBL_IS_NAN(data[i]);
      }
      else
      {
        for (size_t i = 0; i < valueCount; ++i) missValCount += (size_t) DBL_IS_EQUAL(data[i], missVal);
      }
      break;
  }

  return missValCount;
}

static size_t
min_size(size_t a, size_t b)
{
  return a < b ? a : b;
}

static void
transpose2dArrayDP(int gridId, double *data)
{
  size_t inWidth = (size_t) gridInqYsize(gridId);
  size_t inHeight = (size_t) gridInqXsize(gridId);

  size_t cacheBlockSize = 256;  // Purely an optimization parameter. Current value of 32 means we are handling 8kB blocks,
                                // which should be a decent compromise on many architectures.
  double **out = (double **) malloc(inWidth * sizeof(double *));
  double **temp = (double **) malloc(inHeight * sizeof(double *));
  temp[0] = (double *) malloc(inHeight * inWidth * sizeof(double));
  memcpy(temp[0], data, inHeight * inWidth * sizeof(double));
  for (size_t i = 0; i < inWidth; ++i) out[i] = data + (inHeight * i);
  for (size_t i = 1; i < inHeight; ++i) temp[i] = temp[0] + (inWidth * i);

  /*
  for (size_t y = 0; y < inHeight; ++y)
    for (size_t x = 0; x < inWidth; ++x)
      out[x][y] = temp[y][x];
  */

  for (size_t yBlock = 0; yBlock < inHeight; yBlock += cacheBlockSize)
    for (size_t xBlock = 0; xBlock < inWidth; xBlock += cacheBlockSize)
      for (size_t y = yBlock, yEnd = min_size(yBlock + cacheBlockSize, inHeight); y < yEnd; y++)
        for (size_t x = xBlock, xEnd = min_size(xBlock + cacheBlockSize, inWidth); x < xEnd; x++) { out[x][y] = temp[y][x]; }

  free(out);
  free(temp[0]);
  free(temp);
}

static void
transpose2dArraySP(int gridId, float *data)
{
  size_t inWidth = (size_t) gridInqYsize(gridId);
  size_t inHeight = (size_t) gridInqXsize(gridId);

  size_t cacheBlockSize = 256;  // Purely an optimization parameter. Current value of 32 means we are handling 8kB blocks,
                                // which should be a decent compromise on many architectures.
  float **out = (float **) malloc(inWidth * sizeof(float *));
  float **temp = (float **) malloc(inHeight * sizeof(float *));
  temp[0] = (float *) malloc(inHeight * inWidth * sizeof(float));
  memcpy(temp[0], data, inHeight * inWidth * sizeof(float));
  for (size_t i = 0; i < inWidth; i++) out[i] = data + (inHeight * i);
  for (size_t i = 1; i < inHeight; i++) temp[i] = temp[0] + (inWidth * i);

  /*
  for (size_t y = 0; y < inHeight; ++y)
    for (size_t x = 0; x < inWidth; ++x)
      out[x][y] = temp[y][x];
  */

  for (size_t yBlock = 0; yBlock < inHeight; yBlock += cacheBlockSize)
    for (size_t xBlock = 0; xBlock < inWidth; xBlock += cacheBlockSize)
      for (size_t y = yBlock, yEnd = min_size(yBlock + cacheBlockSize, inHeight); y < yEnd; y++)
        for (size_t x = xBlock, xEnd = min_size(xBlock + cacheBlockSize, inWidth); x < xEnd; x++) { out[x][y] = temp[y][x]; }

  free(out);
  free(temp[0]);
  free(temp);
}

static void
cdf_inq_dimIds(stream_t *streamptr, int varId, int (*outDimIds)[4])
{
  int vlistID = streamptr->vlistID;
  int gridId = vlistInqVarGrid(vlistID, varId);
  int gridindex = vlistGridIndex(vlistID, gridId);
  const int *ncIDs = streamptr->cdfInfo.cdfGridList[gridindex].ncIdList;

  switch (gridInqType(gridId))
  {
    case GRID_TRAJECTORY: cdfReadGridTraj(streamptr, gridId); break;
    case GRID_UNSTRUCTURED:
      (*outDimIds)[0] = ncIDs[CDF_DIMID_X];
      (*outDimIds)[3] = ncIDs[CDF_DIMID_E];                                      // used only for cube_sphere grids
      if ((*outDimIds)[3] != CDI_UNDEFID) (*outDimIds)[1] = ncIDs[CDF_DIMID_Y];  // used only for cube_sphere grids
      break;
    case GRID_GAUSSIAN_REDUCED: (*outDimIds)[0] = ncIDs[CDF_DIMID_X]; break;
    default:
      (*outDimIds)[0] = ncIDs[CDF_DIMID_X];
      (*outDimIds)[1] = ncIDs[CDF_DIMID_Y];
      break;
  }

  int zaxisID = vlistInqVarZaxis(vlistID, varId);
  int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  (*outDimIds)[2] = streamptr->cdfInfo.zaxisIdList[zaxisindex];
}

static size_t
stream_inq_dimlen(stream_t *streamptr, int dimid)
{
  const CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  const size_t *ncdimlen = cdfInfo->ncDimLenList;
  const int *ncdimid = cdfInfo->ncDimIdList;
  int ndims = cdfInfo->ncNumDims;
  for (int i = 0; i < ndims; ++i)
  {
    if (dimid == ncdimid[i]) return ncdimlen[i];
  }

  size_t size = 0;
  cdf_inq_dimlen(streamptr->fileID, dimid, &size);
  return size;
}

static int
stream_get_skip_dim(stream_t *streamptr, int ncvarid, int dimIds[3])
{
  if (dimIds[0] != CDI_UNDEFID || dimIds[1] != CDI_UNDEFID) return 0;

  int fileId = streamptr->fileID;
  int nvdims;
  cdf_inq_varndims(fileId, ncvarid, &nvdims);
  if (nvdims != 3) return 0;

  int varDimIds[3] = { -1, -1, -1 };
  cdf_inq_vardimid(fileId, ncvarid, varDimIds);

  if (dimIds[2] == varDimIds[2])
  {
    if (stream_inq_dimlen(streamptr, varDimIds[1]) == 1) return 1;
  }
  else if (dimIds[2] == varDimIds[1])
  {
    if (stream_inq_dimlen(streamptr, varDimIds[2]) == 1) return 2;
  }

  return 0;
}

enum
{
  cdfSliceNDim = MAX_DIMENSIONS
};

static void
cdfGetSliceSlapDescription(stream_t *streamptr, long tsID, int varID, int levelID, bool *outSwapXY, size_t start[cdfSliceNDim],
                           size_t count[cdfSliceNDim])
{
  int fileId = streamptr->fileID;
  int vlistID = streamptr->vlistID;
  int ncvarid = streamptr->vars[varID].ncvarid;
  size_t ncStepIndex = (size_t) streamptr->tsteps[tsID].ncStepIndex;

  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);
  int timetype = vlistInqVarTimetype(vlistID, varID);
  SizeType gridsize = gridInqSize(gridID);

  streamptr->numvals += gridsize;

  int dimIds[4] = { -1, -1, -1, -1 };  // this array joins the old variables xid, yid, and zid
  cdf_inq_dimIds(streamptr, varID, &dimIds);

  int skipdim = stream_get_skip_dim(streamptr, ncvarid, dimIds);

  int dimorder[4] = { 3, 4, 2, 1 };  // order of cube sphere grid
  if (dimIds[3] == CDI_UNDEFID)
  {
    int tmpdimorder[3];
    vlistInqVarDimorder(vlistID, varID, tmpdimorder);
    for (int i = 0; i < 3; ++i) dimorder[i] = tmpdimorder[i];
    dimorder[3] = 4;
    *outSwapXY = ((dimorder[2] == 2 || dimorder[0] == 1) && (dimIds[0] != CDI_UNDEFID) && (dimIds[1] != CDI_UNDEFID));
  }

  int ndims = 0;

#define addDimension(startIndex, extent) \
  do {                                   \
    start[ndims] = startIndex;           \
    count[ndims] = extent;               \
    ndims++;                             \
  } while (0)

  if (timetype != TIME_CONSTANT) addDimension(ncStepIndex, 1);
  if (skipdim == 1) addDimension(0, 1);

  int gridIndex = vlistGridIndex(vlistID, gridID);
  const CdfGrid *cdfGrid = &(streamptr->cdfInfo.cdfGridList[gridIndex]);
  bool readGridPart = (cdfGrid->gridID == gridID && cdfGrid->start != -1 && cdfGrid->count != -1);

  int zaxisIndex = vlistZaxisIndex(vlistID, zaxisID);
  const CdfZaxis *cdfZaxis = &(streamptr->cdfInfo.cdfZaxisList[zaxisIndex]);
  bool readZaxisPart = (cdfZaxis->zaxisID == zaxisID && cdfZaxis->start != -1 && cdfZaxis->count != -1);

  for (int id = 0; id < 4; ++id)
  {
    int curDimId = dimIds[dimorder[id] - 1];
    if (curDimId == CDI_UNDEFID) continue;
    switch (dimorder[id])
    {
      case 1:
      case 2:
      case 4:
        if (readGridPart && curDimId == dimIds[0])
          addDimension((size_t) cdfGrid->start, (size_t) cdfGrid->count);
        else
          addDimension(0, stream_inq_dimlen(streamptr, curDimId));
        break;
      case 3:
        if (readZaxisPart)
          addDimension((size_t) (levelID + cdfZaxis->start), 1);
        else
          addDimension((size_t) levelID, 1);
        break;
      default: Error("Internal errror: Malformed dimension order encountered. Please report this bug.");
    }
  }

  if (skipdim == 2) addDimension(0, 1);

  assert(ndims <= cdfSliceNDim);

#undef addDimension

  if (CDI_Debug)
    for (int idim = 0; idim < ndims; ++idim) Message("dim = %d  start = %zu  count = %zu", idim, start[idim], count[idim]);

  int nvdims;
  cdf_inq_varndims(fileId, ncvarid, &nvdims);
  if (nvdims != ndims)
  {
    char name[CDI_MAX_NAME];
    vlistInqVarName(vlistID, varID, name);
    Error("Internal error, variable %s has an unsupported array structure!", name);
  }
}

static size_t
getSizeVar3D(int vlistID, int varID)
{
  int gridID = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);
  return (size_t) gridInqSize(gridID) * (size_t) zaxisInqSize(zaxisID);
}

static void
cdfReadDataSliceSP2DP(int fileID, int ncvarid, size_t length, size_t start[MAX_DIMENSIONS], size_t count[MAX_DIMENSIONS],
                      double *data)
{
  float *data_fp = (float *) Malloc(length * sizeof(*data_fp));
  cdf_get_vara_float(fileID, ncvarid, start, count, data_fp);
  for (size_t i = 0; i < length; ++i) data[i] = (double) data_fp[i];
  Free(data_fp);
}

static void
cdfReadDataSliceDP2SP(int fileID, int ncvarid, size_t length, size_t start[MAX_DIMENSIONS], size_t count[MAX_DIMENSIONS],
                      float *data)
{
  double *data_dp = (double *) Malloc(length * sizeof(*data_dp));
  cdf_get_vara_double(fileID, ncvarid, start, count, data_dp);
  for (size_t i = 0; i < length; ++i) data[i] = (float) data_dp[i];
  Free(data_dp);
}

static void
cdfCheckDataFP64_UINT8(int fileID, int ncvarid, int vlistID, int varID, size_t length, double *data)
{
  if (vlistInqVarDatatype(vlistID, varID) == CDI_DATATYPE_UINT8)
  {
    nc_type xtype;
    cdf_inq_vartype(fileID, ncvarid, &xtype);
    if (xtype == NC_BYTE)
    {
      for (size_t i = 0; i < length; ++i)
        if (data[i] < 0) data[i] += 256;
    }
  }
}

static void
cdfCheckDataFP32_UINT8(int fileID, int ncvarid, int vlistID, int varID, size_t length, float *data)
{
  if (vlistInqVarDatatype(vlistID, varID) == CDI_DATATYPE_UINT8)
  {
    nc_type xtype;
    cdf_inq_vartype(fileID, ncvarid, &xtype);
    if (xtype == NC_BYTE)
    {
      for (size_t i = 0; i < length; ++i)
        if (data[i] < 0) data[i] += 256;
    }
  }
}

static void
print_scarray(int n, const size_t *start, const size_t *count, const char *name)
{
  printf("%s: ", name);
  printf("start:");
  for (int i = 0; i < n; ++i) printf(" %zu", start[i]);
  printf(" count:");
  for (int i = 0; i < n; ++i) printf(" %zu", count[i]);
  printf("\n");
}

static size_t
get_cache_offset(int fileID, int ncvarid, const size_t *start, const size_t *count, CdfCache *cache, int datatype)
{
  int numDims = cache->numDims;
  if ((CDI_Debug || CDI_Cache_Info) && cache->numSteps == 0)
  {
    print_scarray(numDims, cache->start, cache->count, "cache");
    print_scarray(numDims, start, count, "data");
  }
  bool fillCache = false;
  int lastDim = numDims - 1;
  assert(numDims <= 3);
  // assert(start[lastDim] == 0);
  assert(count[0] == 1);
  assert(cache->count[lastDim] == count[lastDim]);
  // cache->start[lastDim] = start[lastDim];

  if (cache->bufferSize == 0)
  {
    assert(cache->numSteps == 0);
    if (cache->buffer != NULL) Error("Cache buffer already allocated!");
    cache->buffer = Malloc(cache->cacheSize);
    cache->bufferSize = cache->cacheSize;
    fillCache = true;
    // printf("Cache buffer allocated: size=%zu\n", cache->cacheSize);
  }
  while (start[0] >= (cache->start[0] + cache->count[0]))
  {
    cache->start[0] += cache->count[0];
    fillCache = true;
  }
  if (fillCache)
  {
    cache->numSteps += cache->count[0];
    if (cache->numSteps > cache->maxSteps) { cache->count[0] = cache->maxSteps % cache->count[0]; }
    if (datatype == CDI_DATATYPE_FLT32)
      cdf_get_vara_float(fileID, ncvarid, cache->start, cache->count, cache->buffer);
    else
      cdf_get_vara_double(fileID, ncvarid, cache->start, cache->count, cache->buffer);
  }
  size_t offset = (start[0] % cache->count[0]) * cache->count[lastDim];
  if (numDims == 3) { offset = offset * cache->count[1] + (start[1] - cache->start[1]) * cache->count[2]; }
  // printf("step: %zu %zu %zu\n", start[0], cache->count[0], start[0] % cache->count[0]);

  return offset;
}

static void
cdf_get_vara_float_from_cache(int fileID, int ncvarid, const size_t *start, const size_t *count, float *data, CdfCache *cache)
{
  int datatype = CDI_DATATYPE_FLT32;
  int lastDim = cache->numDims - 1;
  size_t offset = get_cache_offset(fileID, ncvarid, start, count, cache, datatype);
  memcpy(data, (float *) cache->buffer + offset, sizeof(float) * cache->count[lastDim]);
}

static void
cdf_get_vara_double_from_cache(int fileID, int ncvarid, const size_t *start, const size_t *count, double *data, CdfCache *cache)
{
  int datatype = CDI_DATATYPE_FLT64;
  int lastDim = cache->numDims - 1;
  size_t offset = get_cache_offset(fileID, ncvarid, start, count, cache, datatype);
  memcpy(data, (double *) cache->buffer + offset, sizeof(double) * cache->count[lastDim]);
}

static void
cdfReadDataFP64(stream_t *streamptr, int varID, size_t length, size_t start[MAX_DIMENSIONS], size_t count[MAX_DIMENSIONS],
                double *data)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int ncvarid = streamptr->vars[varID].ncvarid;
  int datatype = vlistInqVarDatatype(vlistID, varID);

  if (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64)
  {
    cdf_get_vara(fileID, ncvarid, start, count, data);
    if (datatype == CDI_DATATYPE_CPX32)
    {
      for (long i = (long) length - 1; i >= 0; --i)
      {
        data[2 * i] = (double) (((float *) data)[2 * i]);
        data[2 * i + 1] = (double) (((float *) data)[2 * i + 1]);
      }
    }
  }
  else
  {
    if (datatype == CDI_DATATYPE_FLT32) { cdfReadDataSliceSP2DP(fileID, ncvarid, length, start, count, data); }
    else
    {
      if (streamptr->vars[varID].cdfCache)
        cdf_get_vara_double_from_cache(fileID, ncvarid, start, count, data, streamptr->vars[varID].cdfCache);
      else
        cdf_get_vara_double(fileID, ncvarid, start, count, data);
      cdfCheckDataFP64_UINT8(fileID, ncvarid, vlistID, varID, length, data);
    }
  }
}

static void
cdfReadDataFP32(stream_t *streamptr, int varID, size_t length, size_t start[MAX_DIMENSIONS], size_t count[MAX_DIMENSIONS],
                float *data)
{
  int vlistID = streamptr->vlistID;
  int fileID = streamptr->fileID;
  int ncvarid = streamptr->vars[varID].ncvarid;
  int datatype = vlistInqVarDatatype(vlistID, varID);
  CdfCache *cdfCache = streamptr->vars[varID].cdfCache;

  if (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64)
  {
    if (datatype == CDI_DATATYPE_CPX64)
    {
      double *cdata = (double *) Malloc(2 * length * sizeof(double));
      cdf_get_vara(fileID, ncvarid, start, count, cdata);
      for (size_t i = 0; i < length; ++i)
      {
        data[2 * i] = (float) (cdata[2 * i]);
        data[2 * i + 1] = (float) (cdata[2 * i + 1]);
      }
      Free(cdata);
    }
    else { cdf_get_vara(fileID, ncvarid, start, count, data); }
  }
  else
  {
    if (datatype == CDI_DATATYPE_FLT64) { cdfReadDataSliceDP2SP(fileID, ncvarid, length, start, count, data); }
    else
    {
      if (cdfCache)
        cdf_get_vara_float_from_cache(fileID, ncvarid, start, count, data, cdfCache);
      else
        cdf_get_vara_float(fileID, ncvarid, start, count, data);
      cdfCheckDataFP32_UINT8(fileID, ncvarid, vlistID, varID, length, data);
    }
  }
}

static void
cdfReadVarDP(stream_t *streamptr, int varID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;

  size_t start[MAX_DIMENSIONS], count[MAX_DIMENSIONS];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  size_t length = getSizeVar3D(vlistID, varID);
  cdfReadDataFP64(streamptr, varID, length, start, count, data);

  *numMissVals = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

static void
cdfReadVarSP(stream_t *streamptr, int varID, float *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;

  size_t start[MAX_DIMENSIONS], count[MAX_DIMENSIONS];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  size_t length = getSizeVar3D(vlistID, varID);
  cdfReadDataFP32(streamptr, varID, length, start, count, data);

  *numMissVals = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}

void
cdf_read_var(stream_t *streamptr, int varID, int memtype, void *data, size_t *numMissVals)
{
  if (memtype == MEMTYPE_DOUBLE)
    cdfReadVarDP(streamptr, varID, (double *) data, numMissVals);
  else
    cdfReadVarSP(streamptr, varID, (float *) data, numMissVals);
}

static void
cdf_read_var_slice_DP(stream_t *streamptr, long tsID, int varID, int levelID, double *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID=%d  tsID=%ld varID=%d  levelID=%d", streamptr->self, tsID, varID, levelID);

  bool swapxy = false;
  size_t start[cdfSliceNDim], count[cdfSliceNDim];
  cdfGetSliceSlapDescription(streamptr, tsID, varID, levelID, &swapxy, start, count);

  int vlistID = streamptr->vlistID;
  int gridID = vlistInqVarGrid(vlistID, varID);
  size_t length = (size_t) gridInqSize(gridID);
  cdfReadDataFP64(streamptr, varID, length, start, count, data);

  if (swapxy) transpose2dArrayDP(gridID, data);

  *numMissVals = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

static void
cdfReadVarSliceDP(stream_t *streamptr, int varID, int levelID, double *data, size_t *numMissVals)
{
  cdf_read_var_slice_DP(streamptr, streamptr->curTsID, varID, levelID, data, numMissVals);
}

static void
cdf_read_var_slice_SP(stream_t *streamptr, long tsID, int varID, int levelID, float *data, size_t *numMissVals)
{
  if (CDI_Debug) Message("streamID=%d  tsID=%ld varID=%d  levelID=%d", streamptr->self, tsID, varID, levelID);

  bool swapxy = false;
  size_t start[cdfSliceNDim], count[cdfSliceNDim];
  cdfGetSliceSlapDescription(streamptr, tsID, varID, levelID, &swapxy, start, count);

  int vlistID = streamptr->vlistID;
  int gridID = vlistInqVarGrid(vlistID, varID);
  size_t length = (size_t) gridInqSize(gridID);
  cdfReadDataFP32(streamptr, varID, length, start, count, data);

  if (swapxy) transpose2dArraySP(gridID, data);

  *numMissVals = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}

static void
cdfReadVarSliceSP(stream_t *streamptr, int varID, int levelID, float *data, size_t *numMissVals)
{
  cdf_read_var_slice_SP(streamptr, streamptr->curTsID, varID, levelID, data, numMissVals);
}

void
cdf_read_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, void *data, size_t *numMissVals)
{
  if (memtype == MEMTYPE_DOUBLE)
    cdfReadVarSliceDP(streamptr, varID, levelID, (double *) data, numMissVals);
  else
    cdfReadVarSliceSP(streamptr, varID, levelID, (float *) data, numMissVals);
}

typedef struct JobArgsCDF
{
  stream_t *streamptr;
  int varID, levelID, memtype;
  long recID, tsID;
  void *data;
  size_t gridsize, numMissVals;
} JobArgsCDF;

static int
cdf_read_data_async(void *untypedArgs)
{
  JobArgsCDF *args = (JobArgsCDF *) untypedArgs;

  if (args->memtype == MEMTYPE_DOUBLE)
    cdf_read_var_slice_DP(args->streamptr, args->tsID, args->varID, args->levelID, (double *) args->data, &args->numMissVals);
  else
    cdf_read_var_slice_SP(args->streamptr, args->tsID, args->varID, args->levelID, (float *) args->data, &args->numMissVals);

  return 0;
}

static size_t
cdf_read_data(stream_t *streamptr, int recID, int memtype, void *data)
{
  int tsID = streamptr->curTsID;
  recinfo_t *recInfo = &(streamptr->tsteps[tsID].recinfo[recID]);

  size_t numMissVals = 0;
  if (memtype == MEMTYPE_DOUBLE)
    cdf_read_var_slice_DP(streamptr, tsID, recInfo->varID, recInfo->levelID, (double *) data, &numMissVals);
  else
    cdf_read_var_slice_SP(streamptr, tsID, recInfo->varID, recInfo->levelID, (float *) data, &numMissVals);

  return numMissVals;
}

typedef struct JobDescriptorCDF
{
  JobArgsCDF args;
  AsyncJob *job;
} JobDescriptorCDF;

static JobArgsCDF
job_args_init(stream_t *streamptr, long tsID, long recID, int memtype, void *data)
{
  recinfo_t *recInfo = &(streamptr->tsteps[tsID].recinfo[recID]);
  size_t gridsize = (size_t) gridInqSize(vlistInqVarGrid(streamptr->vlistID, recInfo->varID));

  if (!data) data = Malloc(gridsize * ((memtype == MEMTYPE_FLOAT) ? sizeof(float) : sizeof(double)));

  return (JobArgsCDF){
    .streamptr = streamptr,
    .varID = recInfo->varID,
    .levelID = recInfo->levelID,
    .memtype = memtype,
    .recID = recID,
    .tsID = tsID,
    .data = data,
    .gridsize = gridsize,
    .numMissVals = 0,
  };
}

static void
JobDescriptor_startJobCDF(AsyncManager *jobManager, JobDescriptorCDF *me, stream_t *streamptr, long tsID, long recID, int memtype)
{
  me->args = job_args_init(streamptr, tsID, recID, memtype, NULL);
  me->job = AsyncWorker_requestWork(jobManager, cdf_read_data_async, &me->args);
  if (!me->job) xabort("error while trying to send job to worker thread");
}

static void
cdf_JobDescriptor_finishJob(AsyncManager *jobManager, JobDescriptorCDF *me, void *data, size_t *numMissVals)
{
  if (AsyncWorker_wait(jobManager, me->job)) xabort("error executing job in worker thread");
  memcpy(data, me->args.data, me->args.gridsize * ((me->args.memtype == MEMTYPE_FLOAT) ? sizeof(float) : sizeof(double)));
  *numMissVals = me->args.numMissVals;

  Free(me->args.data);
  me->args.recID = -1;  // mark as inactive
  me->args.tsID = -1;   // mark as inactive
}
/*
static long
get_global_recId(stream_t *streamptr, int tsID, int recID)
{
  const tsteps_t *tsteps = streamptr->tsteps;
  long globalRecId = recID;
  if (tsID > 0) globalRecId += tsteps[0].nrecs;
  if (tsID > 1) globalRecId += tsteps[1].nrecs * (tsID - 1);
  return globalRecId;
}
*/

struct recTsId
{
  long recID, tsID;
};

static struct recTsId
cdf_get_local_step_and_recId(stream_t *streamptr, long globalRecId)
{
  int localTsId = 0;
  long numSteps = streamptr->ntsteps;
  const tsteps_t *tsteps = streamptr->tsteps;
  if (numSteps > 0 && globalRecId >= tsteps[0].nrecs)
  {
    localTsId++;
    globalRecId -= tsteps[0].nrecs;
  }
  while (globalRecId >= tsteps[1].nrecs)
  {
    localTsId++;
    globalRecId -= tsteps[1].nrecs;
  }

  return (struct recTsId){ .recID = globalRecId, .tsID = localTsId };
}

static void
cdf_read_next_record_async(AsyncManager *jobManager, JobDescriptorCDF *jd, stream_t *streamptr, int memtype)
{
  struct recTsId ids = cdf_get_local_step_and_recId(streamptr, streamptr->nextGlobalRecId);
  int xRecId = streamptr->tsteps[ids.tsID].recIDs[ids.recID];
  JobDescriptor_startJobCDF(jobManager, jd, streamptr, ids.tsID, xRecId, memtype);
  streamptr->nextGlobalRecId++;
}

static void
cdf_read_next_record(stream_t *streamptr, int recID, int memtype, void *data, size_t *numMissVals)
{
  bool jobFound = false;

  int workerCount = streamptr->numWorker;
  if (workerCount > 0)
  {
    int tsID = streamptr->curTsID;

    AsyncManager *jobManager = (AsyncManager *) streamptr->jobManager;
    JobDescriptorCDF *jobs = (JobDescriptorCDF *) streamptr->jobs;

    // if this is the first call, init and start worker threads
    if (!jobs)
    {
      jobs = (JobDescriptorCDF *) Malloc((size_t) workerCount * sizeof(*jobs));
      streamptr->jobs = jobs;
      for (int i = 0; i < workerCount; i++) jobs[i].args.recID = -1;
      for (int i = 0; i < workerCount; i++) jobs[i].args.tsID = -1;
      if (AsyncWorker_init(&jobManager, workerCount)) xabort("error while trying to start worker threads");
      streamptr->jobManager = jobManager;

      // Start as many new jobs as possible.
      for (int i = 0; streamptr->nextGlobalRecId < streamptr->maxGlobalRecs && i < workerCount; i++)
        if (jobs[i].args.recID < 0 && jobs[i].args.tsID < 0) cdf_read_next_record_async(jobManager, jobs + i, streamptr, memtype);
    }

    // search for a job descriptor with the given tsID and recID, and use its results if it exists
    for (int i = 0; !jobFound && i < workerCount; i++)
      if (jobs[i].args.recID == recID && jobs[i].args.tsID == tsID)
      {
        jobFound = true;
        cdf_JobDescriptor_finishJob(jobManager, jobs + i, data, numMissVals);
        if (streamptr->nextGlobalRecId < streamptr->maxGlobalRecs)
          cdf_read_next_record_async(jobManager, jobs + i, streamptr, memtype);
      }
  }

  // perform the work synchronously if we didn't start a job for it yet
  if (!jobFound) *numMissVals = cdf_read_data(streamptr, recID, memtype, data);
}

void
cdf_read_field(stream_t *streamptr, int memtype, void *data, size_t *numMissVals)
{
  int tsID = streamptr->curTsID;
  int vrecID = streamptr->tsteps[tsID].curRecID;
  int recID = streamptr->tsteps[tsID].recIDs[vrecID];

  cdf_read_next_record(streamptr, recID, memtype, data, numMissVals);
}

//----------------------------------------------------------------------------
// Parallel Version
//----------------------------------------------------------------------------

void
cdfReadVarSliceDPPart(stream_t *streamptr, int varID, int levelID, int varType, int startpoint, size_t length, double *data,
                      size_t *numMissVals)
{
  (void) (varType);

  if (CDI_Debug) Message("streamID = %d  varID = %d  levelID = %d", streamptr->self, varID, levelID);

  int vlistID = streamptr->vlistID;

  bool swapxy = false;
  size_t start[cdfSliceNDim], count[cdfSliceNDim];
  cdfGetSliceSlapDescription(streamptr, streamptr->curTsID, varID, levelID, &swapxy, start, count);

  int gridId = vlistInqVarGrid(vlistID, varID);
  SizeType gridsize = gridInqSize(gridId);

  int position = 0;
  for (int i = 0; i < MAX_DIMENSIONS; ++i)
    if (count[i] == (size_t) gridsize) position = i;

  start[position] += (size_t) startpoint;
  count[position] = length;

  cdfReadDataFP64(streamptr, varID, length, start, count, data);

  if (swapxy) transpose2dArrayDP(gridId, data);

  *numMissVals = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

void
cdfReadVarSliceSPPart(stream_t *streamptr, int varID, int levelID, int varType, int startpoint, size_t length, float *data,
                      size_t *numMissVals)
{
  (void) (varType);

  if (CDI_Debug) Message("streamID = %d  varID = %d  levelID = %d", streamptr->self, varID, levelID);

  int vlistID = streamptr->vlistID;

  bool swapxy = false;
  size_t start[cdfSliceNDim], count[cdfSliceNDim];
  cdfGetSliceSlapDescription(streamptr, streamptr->curTsID, varID, levelID, &swapxy, start, count);

  int gridId = vlistInqVarGrid(vlistID, varID);
  SizeType gridsize = gridInqSize(gridId);

  int position = 0;
  for (int i = 0; i < MAX_DIMENSIONS; ++i)
    if (count[i] == (size_t) gridsize) position = i;

  start[position] += (size_t) startpoint;
  count[position] = length;

  cdfReadDataFP32(streamptr, varID, length, start, count, data);

  if (swapxy) transpose2dArraySP(gridId, data);

  *numMissVals = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}

void
cdf_read_var_slice_part(stream_t *streamPtr, int varID, int levelID, int memType, int varType, int start, size_t size, void *data,
                        size_t *numMissVals)
{
  if (memType == MEMTYPE_FLOAT)
    cdfReadVarSliceSPPart(streamPtr, varID, levelID, varType, start, size, (float *) data, numMissVals);
  else
    cdfReadVarSliceDPPart(streamPtr, varID, levelID, varType, start, size, (double *) data, numMissVals);
}

void
cdfReadVarDPPart(stream_t *streamptr, int varID, int varType, int startpoint, size_t length, double *data, size_t *numMissVals)
{
  (void) (varType);
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  int ncvarid = streamptr->vars[varID].ncvarid;

  size_t start[MAX_DIMENSIONS], count[MAX_DIMENSIONS];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  int ltime = (TIME_CONSTANT != vlistInqVarTimetype(vlistID, varID));
  start[1 + ltime] += (size_t) startpoint;
  count[1 + ltime] = length;

  cdf_get_vara_double(streamptr->fileID, ncvarid, start, count, data);

  *numMissVals = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

void
cdfReadVarSPPart(stream_t *streamptr, int varID, int varType, int startpoint, size_t length, float *data, size_t *numMissVals)
{
  (void) (varType);
  if (CDI_Debug) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  int ncvarid = streamptr->vars[varID].ncvarid;

  size_t start[MAX_DIMENSIONS], count[MAX_DIMENSIONS];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  int ltime = (TIME_CONSTANT != vlistInqVarTimetype(vlistID, varID));
  start[1 + ltime] = (size_t) startpoint;
  count[1 + ltime] = length;

  cdf_get_vara_float(streamptr->fileID, ncvarid, start, count, data);

  *numMissVals = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}

void
cdf_read_var_part(stream_t *streamPtr, int varID, int memType, int varType, int start, size_t size, void *data, size_t *numMissVals)
{
  if (memType == MEMTYPE_FLOAT)
    cdfReadVarSPPart(streamPtr, varID, varType, start, size, (float *) data, numMissVals);
  else
    cdfReadVarDPPart(streamPtr, varID, varType, start, size, (double *) data, numMissVals);
}

#endif /* HAVE_LIBNETCDF */
/* Subroutines and data structures for storing "subtypes".             */
/*                                                                     */
/* A subtype is, for example, a list of TILES. This can be interpreted */
/* as an additional axis like the vertical axis.                       */
/*                                                                     */
/* @author 02/2015 F. Prill, DWD                                       */
/*                                                                     */
/*  DATA LAYOUT:                                                       */
/*                                                                     */
/*  A subtype contains several "subtype entries", each of which        */
/*  contains a linked list of subtype attributes.                      */
/*                                                                     */
/*  The number of subtype entries is not specified in advance, but the */
/*  list of entries is itself dynamically growing. There is no         */
/*  guaranteed ordering of the entries, therefore each entry must be   */
/*  identifiable by its attributes.                                    */
/*                                                                     */
/*  [subtype_t]                                                        */
/*      |                                                              */
/*      |------- globals                  [subtype_entry_t]            */
/*      |          |--- atts              [subtype_attr_t]             */
/*      |                                                              */
/*      |------- entries                                               */
/*                 |- entry #0                                         */
/*                 |  |--- atts              [subtype_attr_t]          */
/*                 |- entry #1                                         */
/*                 |  |--- atts              [subtype_attr_t]          */
/*                 |- entry #2                                         */
/*                 .  |--- atts              [subtype_attr_t]          */
/*                 .                                                   */

/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */


/* Literal constants corresponding to the different subtypes of the
   enumeration "subtype_kind". */
static const char *subtypeName[] = { "tileset" };

const char *const cdiSubtypeAttributeName[] = { "tileIndex",     "totalNumberOfTileAttributePairs", "tileClassification",
                                                "numberOfTiles", "numberOfTileAttributes",          "tileAttribute" };

/* prototypes: */
static int subtypeCompareP(subtype_t *z1, subtype_t *z2);
static void subtypeDestroyP(void *subtype_ptr);
static void subtypePrintP(void *subtype_ptr, FILE *fp);
static int subtypeGetPackSize(void *subtype_ptr, void *context);
static void subtypePack(void *subtype_ptr, void *buffer, int size, int *pos, void *context);
static int subtypeTxCode(void *subtype_ptr);

static const resOps subtypeOps = { (int (*)(void *, void *)) subtypeCompareP,
                                   (void (*)(void *)) subtypeDestroyP,
                                   (void (*)(void *, FILE *)) subtypePrintP,
                                   (int (*)(void *, void *)) subtypeGetPackSize,
                                   subtypePack,
                                   subtypeTxCode };

enum
{
  differ = 1,
};

/* ------------------------------------------------------------------- */
/* SUBROUTINES FOR ATTRIBUTE LISTS                                     */
/* ------------------------------------------------------------------- */

static int
attribute_to_index(const char *key)
{
  if (key == NULL) Error("Internal error!");
  for (int i = 0; i < nSubtypeAttributes; i++)
    if (str_is_equal(key, cdiSubtypeAttributeName[i])) return i;
  return -1;
}

/*
  @Function  subtypeAttrNewList
  @Title     Create new linked list of subtype attributes.
  @EndFunction
*/
static struct subtype_attr_t *
subtypeAttrNewList(struct subtype_entry_t *head, int key, int val)
{
  if (head == NULL) Error("Internal error!");
  struct subtype_attr_t *ptr = (struct subtype_attr_t *) Malloc(sizeof(struct subtype_attr_t));
  if (NULL == ptr) Error("Node creation failed");
  ptr->key = key;
  ptr->val = val;
  ptr->next = NULL;

  head->atts = ptr;
  return ptr;
}

/*
  @Function  subtypeAttrInsert

  @Title Add subtype attribute to linked list, s.t. the result is a
         smallest-to-largest ordered list.
  @EndFunction
*/
static struct subtype_attr_t *
subtypeAttrInsert(struct subtype_entry_t *head, int key, int val)
{
  if (head == NULL) Error("Internal error!");
  if (head->atts == NULL) return (subtypeAttrNewList(head, key, val));

  /* create new attribute */
  struct subtype_attr_t *ptr = (struct subtype_attr_t *) Malloc(sizeof(struct subtype_attr_t));
  if (NULL == ptr) Error("Node creation failed");

  ptr->key = key;
  ptr->val = val;
  ptr->next = NULL;

  /* find the right place for insertion: */
  if (head->atts->key >= key)
  {
    /* insert at position 0 */
    ptr->next = head->atts;
    head->atts = ptr;
  }
  else
  {
    struct subtype_attr_t **predec = &head->atts;
    while (((*predec)->next != NULL) && ((*predec)->next->key < key)) { predec = &((*predec)->next); }
    ptr->next = (*predec)->next;
    (*predec)->next = ptr;
  }
  return ptr;
}

/* Recursively free a linked list with attributes. */
static void
subtypeAttrDestroy(struct subtype_attr_t *head)
{
  if (head == NULL) return;
  subtypeAttrDestroy(head->next);
  Free(head);
  head = NULL;
}

/* Find an attribute in linked list by its key or return NULL
   otherwise. */
static struct subtype_attr_t *
subtypeAttrFind(struct subtype_attr_t *head, int key)
{
  if (head == NULL)
    return NULL;
  else if (head->key == key)
    return head;
  else
    return subtypeAttrFind(head->next, key);
}

/* Recursively compares two subtype attribute lists under the implicit
   assumptions that both lists are ordered by their keys and that keys
   are unique. */
static int
subtypeAttsCompare(struct subtype_attr_t *a1, struct subtype_attr_t *a2)
{
  if ((a1 == NULL) && (a2 == NULL))
    return 0;
  else if ((a1 == NULL) && (a2 != NULL)) { return differ; }
  else if ((a1 != NULL) && (a2 == NULL)) { return differ; }

  if (a1->key != a2->key) { return differ; }
  if (a1->val != a2->val) return differ;

  return subtypeAttsCompare(a1->next, a2->next);
}

/* (Recursively) duplicate linked list of attributes. */
static void
subtypeAttsDuplicate(struct subtype_attr_t *a1, struct subtype_entry_t *dst)
{
  if (a1 == NULL) return;
  /* duplicate "a1->key", "a1->val" */
  subtypeAttsDuplicate(a1->next, dst);
  (void) subtypeAttrInsert(dst, a1->key, a1->val);
}

/* ------------------------------------------------------------------- */
/* SUBROUTINES FOR LIST OF ENTRIES                                     */
/* ------------------------------------------------------------------- */

/*
  @Function  subtypeEntryNewList
  @Title     Create new linked list of subtype entries.
  @EndFunction
*/
static struct subtype_entry_t *
subtypeEntryNewList(subtype_t *head)
{
  struct subtype_entry_t *ptr = (struct subtype_entry_t *) Malloc(sizeof(struct subtype_entry_t));
  if (NULL == ptr) Error("Node creation failed");
  ptr->atts = NULL;
  ptr->next = NULL;
  head->entries = ptr;
  head->nentries = 0;
  ptr->self = head->nentries++;
  return ptr;
}

/*
  @Function  subtypeEntryInsert

  @Title Add subtype entry to the head of a linked list.
  @EndFunction
*/
struct subtype_entry_t *
subtypeEntryInsert(subtype_t *head)
{
  if (head == NULL) Error("Internal error!");
  if (head->entries == NULL) return (subtypeEntryNewList(head));

  /* create new entry */
  struct subtype_entry_t *ptr = (struct subtype_entry_t *) Malloc(sizeof(struct subtype_entry_t));
  if (NULL == ptr) Error("Node creation failed");

  ptr->atts = NULL;
  ptr->self = head->nentries++;

  /* find the right place for insertion: */
  if (head->entries->self >= ptr->self)
  {
    /* insert at position 0 */
    ptr->next = head->entries;
    head->entries = ptr;
  }
  else
  {
    struct subtype_entry_t **predec = &head->entries;
    while (((*predec)->next != NULL) && ((*predec)->next->self < ptr->self)) { predec = &((*predec)->next); }
    ptr->next = (*predec)->next;
    (*predec)->next = ptr;
  }
  return ptr;
}

/*
  @Function  subtypeEntryAppend

  @Title Append subtype entry to the end of a linked list.
  @EndFunction
*/
static struct subtype_entry_t *
subtypeEntryAppend(subtype_t *head)
{
  if (head == NULL) Error("Internal error!");
  if (head->entries == NULL) return (subtypeEntryNewList(head));

  /* create new entry */
  struct subtype_entry_t *ptr = (struct subtype_entry_t *) Malloc(sizeof(struct subtype_entry_t));
  if (NULL == ptr) Error("Node creation failed");

  ptr->atts = NULL;
  ptr->next = NULL;
  ptr->self = head->nentries++;

  /* find last position of linked list */
  struct subtype_entry_t *prec_ptr = head->entries;
  while (prec_ptr->next != NULL) prec_ptr = prec_ptr->next;

  prec_ptr->next = ptr;
  return ptr;
}

/* Recursively free a list of subtype entries. */
static void
subtypeEntryDestroy(struct subtype_entry_t *entry)
{
  if (entry == NULL) return;
  subtypeEntryDestroy(entry->next);
  subtypeAttrDestroy(entry->atts);
  Free(entry);
  entry = NULL;
}

/* Compares two subtype entries. */
static int
subtypeEntryCompare(struct subtype_entry_t *e1, struct subtype_entry_t *e2)
{
  if (e1 == NULL) Error("Internal error!");
  if (e2 == NULL) Error("Internal error!");
  return (e1->self == e2->self) && subtypeAttsCompare(e1->atts, e2->atts);
}

/* (Recursively) duplicate list of entries. */
static void
subtypeEntryDuplicate(struct subtype_entry_t *a1, subtype_t *dst)
{
  if (a1 == NULL) return;
  /* append entry to dst pointer */
  struct subtype_entry_t *ptr = subtypeEntryAppend(dst);
  /* duplicate attributes */
  subtypeAttsDuplicate(a1->atts, ptr);
  ptr->self = a1->self;
  /* call next link in linked list */
  subtypeEntryDuplicate(a1->next, dst);
}

/* ------------------------------------------------------------------- */
/* SUBROUTINES FOR THE SUBTYPE ITSELF                                  */
/* ------------------------------------------------------------------- */

/* Print-out subtype data structure together with its attributes. */
static void
subtypePrintKernel(subtype_t *subtype_ptr, FILE *fp)
{
  if (subtype_ptr == NULL) Error("Internal error!");
  fprintf(fp, "# %s (subtype ID %d)\n", subtypeName[subtype_ptr->subtype], subtype_ptr->self);
  /* print global attributes of this subtype */
  struct subtype_attr_t *ptr = subtype_ptr->globals.atts;
  if (ptr != NULL) fprintf(fp, "#\n# global attributes:\n");
  while (ptr != NULL)
  {
    fprintf(fp, "#   %-40s   (%2d) : %d\n", cdiSubtypeAttributeName[ptr->key], ptr->key, ptr->val);
    ptr = ptr->next;
  }
  /* print attributes for each subtype */
  fprintf(fp, "# %d local entries:\n", subtype_ptr->nentries);
  struct subtype_entry_t *entry = subtype_ptr->entries;
  while (entry != NULL)
  {
    fprintf(fp, "# subtype entry %d\n", entry->self);
    ptr = entry->atts;
    if (ptr != NULL) fprintf(fp, "#   attributes:\n");
    while (ptr != NULL)
    {
      fprintf(fp, "#     %-40s (%2d) : %d\n", cdiSubtypeAttributeName[ptr->key], ptr->key, ptr->val);
      ptr = ptr->next;
    }
    entry = entry->next;
  }
  fprintf(fp, "\n");
}

/* Compares two subtype data structures. Pointer version of this
   method. */
static int
subtypeCompareP(subtype_t *s1, subtype_t *s2)
{
  xassert(s1 && s2);
  if (s1->subtype != s2->subtype) return differ;
  if (subtypeEntryCompare(&s1->globals, &s2->globals) != 0) return differ;

  struct subtype_entry_t *entry1 = s1->entries;
  struct subtype_entry_t *entry2 = s2->entries;
  while ((entry1 != NULL) && (entry2 != NULL))
  {
    if (subtypeEntryCompare(entry1, entry2) != 0) return differ;
    entry1 = entry1->next;
    entry2 = entry2->next;
  }
  /* compare list lengths: */
  if ((entry1 != NULL) || (entry2 != NULL)) return differ;
  return 0;
}

/* Clean up data structure. */
static void
subtypeDestroyP(void *ptr)
{
  subtype_t *subtype_ptr = (subtype_t *) ptr;
  /* destroy global attributes */
  subtypeAttrDestroy(subtype_ptr->globals.atts);
  /* destroy list of subtype entries */
  subtypeEntryDestroy(subtype_ptr->entries);
  subtype_ptr->entries = NULL;
  Free(subtype_ptr);
  subtype_ptr = NULL;
}

/* Non-static wrapper function for "subtypeDestroyP". */
void
subtypeDestroyPtr(void *ptr)
{
  subtypeDestroyP(ptr);
}

/* Non-static wrapper function for "subtypeCompareP". */
int
subtypeComparePtr(int s1_ID, subtype_t *s2)
{
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(s1_ID, &subtypeOps);
  if (subtype_ptr == NULL) Error("Internal error");
  return subtypeCompareP(subtype_ptr, s2);
}

/* Print-out subtype data structure together with its attributes.
   Pointer version of this method. */
static void
subtypePrintP(void *subtype_ptr, FILE *fp)
{
  subtypePrintKernel((subtype_t *) subtype_ptr, fp);
}

/* Print-out subtype data structure together with its attributes. */
void
subtypePrintPtr(subtype_t *subtype_ptr)
{
  subtypePrintKernel(subtype_ptr, stdout);
}

/* Fill subtype data structure with default values. */
static void
subtypeDefaultValue(subtype_t *subtype_ptr)
{
  if (subtype_ptr == NULL) Error("Internal error!");
  subtype_ptr->self = CDI_UNDEFID;
  subtype_ptr->nentries = 0;
  subtype_ptr->entries = NULL;
  subtype_ptr->globals.atts = NULL;
  subtype_ptr->globals.next = NULL;
  subtype_ptr->globals.self = -1;
  subtype_ptr->active_subtype_index = 0;
}

void
subtypeAllocate(subtype_t **subtype_ptr2, int subtype)
{
  /* allocate new subtype */
  (*subtype_ptr2) = (subtype_t *) Malloc(sizeof(subtype_t));
  subtype_t *subtype_ptr = *subtype_ptr2;
  subtypeDefaultValue(subtype_ptr);
  subtype_ptr->subtype = subtype;
  subtype_ptr->self = CDI_UNDEFID;
}

/* Create a copy of an existing subtype data structure. */
void
subtypeDuplicate(subtype_t *subtype_ptr, subtype_t **dst_ptr)
{
  if (subtype_ptr == NULL) Error("Internal error!");
  subtypeAllocate(dst_ptr, subtype_ptr->subtype);
  subtype_t *dst = (*dst_ptr);
  /* create duplicate of subtype globals */
  subtypeAttsDuplicate(subtype_ptr->globals.atts, &dst->globals);
  dst->globals.self = subtype_ptr->globals.self;
  /* create duplicate of subtype entries */
  subtypeEntryDuplicate(subtype_ptr->entries, dst);
}

/* Register subtype object at resource handler. */
int
subtypePush(subtype_t *subtype_ptr)
{
  if (subtype_ptr == NULL) Error("Internal error!");
  subtype_ptr->self = reshPut(subtype_ptr, &subtypeOps);
  return subtype_ptr->self; /* subtypeID */
}

/* Sets an attribute for a subtype (for example a set of TILES). If
   the attribute has already been defined, then its value is
   overwritten. */
void
subtypeDefGlobalDataP(subtype_t *subtype_ptr, int key, int val)
{
  if (subtype_ptr == NULL) Error("Internal error!");
  /* find entry in linked list or append otherwise */
  struct subtype_attr_t *att_ptr = subtypeAttrFind(subtype_ptr->globals.atts, key);
  if (att_ptr == NULL)
    subtypeAttrInsert(&subtype_ptr->globals, key, val);
  else
    att_ptr->val = val;
}

/* Sets an attribute for a subtype (for example a set of TILES). If
   the attribute has already been defined, then its value is
   overwritten. */
void
subtypeDefGlobalData(int subtypeID, int key, int val)
{
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  subtypeDefGlobalDataP(subtype_ptr, key, val);
}

/* Retrieves an attribute for a subtype (for example a set of TILES).
   If the attribute has not been defined, then return -1. */
int
subtypeGetGlobalDataP(subtype_t *subtype_ptr, int key)
{
  if (subtype_ptr == NULL) Error("Internal error!");
  /* find entry in linked list */
  struct subtype_attr_t *att_ptr = subtypeAttrFind(subtype_ptr->globals.atts, key);
  if (att_ptr == NULL)
    return -1;
  else
    return att_ptr->val;
}

/* Retrieves an attribute for a subtype (for example a set of TILES) .
   If the attribute has not been defined, then return -1. */
int
subtypeGetGlobalData(int subtypeID, int key)
{
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  return subtypeGetGlobalDataP(subtype_ptr, key);
}

/* Sets an attribute for a single subtype entry (e.g. a single TILE).
   If the attribute has already been defined, then its value is
   overwritten. */
void
subtypeDefEntryDataP(struct subtype_entry_t *subtype_entry_ptr, int key, int val)
{
  if (subtype_entry_ptr == NULL) Error("Internal error!");
  /* find entry in linked list or append otherwise */
  struct subtype_attr_t *att_ptr = subtypeAttrFind(subtype_entry_ptr->atts, key);
  if (att_ptr == NULL)
    subtypeAttrInsert(subtype_entry_ptr, key, val);
  else
    att_ptr->val = val;
}

/* ------------------------------------------------------------------- */
/* IMPLEMENTATIONS FOR KEY-VALUE-PAIR QUERIES                          */
/* ------------------------------------------------------------------- */

/* Generate a "query object" out of a key-value pair. */
subtype_query_t
keyValuePair(const char *key, int value)
{
  subtype_query_t result;
  result.nAND = 1;
  result.key_value_pairs[0][0] = attribute_to_index(key);
  result.key_value_pairs[1][0] = value;
  if (CDI_Debug) Message("key  %s matches %d", key, result.key_value_pairs[0][0]);
  if (CDI_Debug) Message("%d --?-- %d", result.key_value_pairs[0][0], result.key_value_pairs[1][0]);
  return result;
}

/* Generate an AND-combined "query object" out of two previous query
   objects. */
subtype_query_t
matchAND(subtype_query_t q1, subtype_query_t q2)
{
  if ((q1.nAND + q2.nAND) > MAX_KV_PAIRS_MATCH) Error("Internal error");
  subtype_query_t result;
  memset(&result, 0, sizeof(subtype_query_t));
  result.nAND = q1.nAND;
  for (int i = 0; i < q1.nAND; i++)
  {
    result.key_value_pairs[0][i] = q1.key_value_pairs[0][i];
    result.key_value_pairs[1][i] = q1.key_value_pairs[1][i];
  }
  for (int i = 0; i < q2.nAND; i++)
  {
    result.key_value_pairs[0][result.nAND] = q2.key_value_pairs[0][i];
    result.key_value_pairs[1][result.nAND] = q2.key_value_pairs[1][i];
    result.nAND++;
  }

  if (CDI_Debug) Message("combined criterion:");
  if (CDI_Debug)
    for (int i = 0; i < result.nAND; i++) Message("%d --?-- %d", result.key_value_pairs[0][i], result.key_value_pairs[1][i]);

  return result;
}

/* ------------------------------------------------------------------- */
/* SPECIFIC IMPLEMENTATIONS FOR TILE SETS                              */
/* ------------------------------------------------------------------- */

/* Integrate tile set "s2" into the tile set "subtype1_ID":

   Insert all entries set 2 to set 1 together with its attributes.
*/
void
tilesetInsertP(subtype_t *s1, subtype_t *s2)
{
  if (s1 == NULL) Error("Internal error!");
  if (s2 == NULL) Error("Internal error!");
  struct subtype_entry_t *entry1 = s1->entries, *entry2 = s2->entries;
  struct subtype_attr_t *att_ptr2;

  /* test all entries of set 2 against set 1, to check if entry
     already exists: */
  if (subtypeAttsCompare(s1->globals.atts, s2->globals.atts) != differ)
  {
    while (entry1 != NULL)
    {
      int found = 1;
      entry2 = s2->entries;
      while (entry2 != NULL)
      {
        found &= (subtypeAttsCompare(entry1->atts, entry2->atts) != differ);
        entry2 = entry2->next;
      }
      if (found) { return; }
      entry1 = entry1->next;
    }

    entry2 = s2->entries;
    while (entry2 != NULL)
    {
      entry1 = subtypeEntryInsert(s1);

      att_ptr2 = entry2->atts;
      while (att_ptr2 != NULL)
      {
        (void) subtypeAttrInsert(entry1, att_ptr2->key, att_ptr2->val);
        att_ptr2 = att_ptr2->next;
      }
      entry2 = entry2->next;
    }
  }
  else
  {
    fprintf(stderr, "\n# SUBTYPE A:\n");
    subtypePrintKernel(s1, stderr);
    fprintf(stderr, "\n# SUBTYPE B:\n");
    subtypePrintKernel(s2, stderr);
    Error("Attempting to insert subtype entry into subtype with different global attributes!");
  }
}

/* ------------------------------------------------------------------- */
/* IMPLEMENTATIONS FOR ROUTINES VISIBLE THROUGH CDI.H                  */
/* ------------------------------------------------------------------- */

/*
  @Function  subtypeCreate
  @Title     Create a variable subtype

  @Prototype int subtypeCreate(int subtype)
  @Parameter
  @Item  subtype  The type of the variable subtype, one of the set of predefined CDI variable subtypes.
  The valid CDI variable subtypes are @func{SUBTYPE_TILES}

  @Description
  The function @func{subtypeCreate} creates a variable subtype.

  @Result
  @func{subtypeCreate} returns an identifier to the variable subtype.

  @EndFunction
*/
int
subtypeCreate(int subtype)
{
  if (CDI_Debug) Message("subtype: %d ", subtype);

  /* allocate new subtype */
  subtype_t *subtype_ptr;
  subtypeAllocate(&subtype_ptr, subtype);
  /* register object at resource handler */
  return subtypePush(subtype_ptr);
}

/* Print-out subtype data structure together with its attributes. */
void
subtypePrint(int subtypeID)
{
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  subtypePrintKernel(subtype_ptr, stdout);
}

/* Compares two subtype data structures. */
int
subtypeCompare(int subtypeID1, int subtypeID2)
{
  subtype_t *subtype_ptr1 = (subtype_t *) reshGetVal(subtypeID1, &subtypeOps);
  subtype_t *subtype_ptr2 = (subtype_t *) reshGetVal(subtypeID2, &subtypeOps);
  return subtypeCompareP(subtype_ptr1, subtype_ptr2);
}

/*  Get the size of a subtype (e.g. no. of tiles). */
int
subtypeInqSize(int subtypeID)
{
  if (subtypeID == CDI_UNDEFID) { return 0; }
  else
  {
    subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
    return subtype_ptr->nentries;
  }
}

/* Get the currently active index of a subtype (e.g. current tile index). */
int
subtypeInqActiveIndex(int subtypeID)
{
  if (subtypeID == CDI_UNDEFID) return 0;
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  return subtype_ptr->active_subtype_index;
}

/* Set the currently active index of a subtype (e.g. current tile index). */
void
subtypeDefActiveIndex(int subtypeID, int index)
{
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  subtype_ptr->active_subtype_index = index;
}

/* subtypeInqSubEntry: Returns subtype entry ID for a given
   criterion. */
int
subtypeInqSubEntry(int subtypeID, subtype_query_t criterion)
{
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  struct subtype_entry_t *entry = subtype_ptr->entries;
  /* loop over all entries of this subtype */
  while (entry != NULL)
  {
    {
      int match = 1;
      /* test if this entry matches ALL criteria. */
      for (int j = 0; (j < criterion.nAND) && (match); j++)
      {
        if (CDI_Debug)
          Message("check criterion %d :  %d --?-- %d", j, criterion.key_value_pairs[0][j], criterion.key_value_pairs[1][j]);
        struct subtype_attr_t *att_ptr = subtypeAttrFind(entry->atts, criterion.key_value_pairs[0][j]);
        if (att_ptr == NULL)
        {
          match = 0;
          if (CDI_Debug) Message("did not find %d", criterion.key_value_pairs[0][j]);
        }
        else
        {
          if (CDI_Debug) Message("found %d", criterion.key_value_pairs[0][j]);
          match &= (att_ptr->val == criterion.key_value_pairs[1][j]);
        }
      }
      if (match) return entry->self;
    }
    entry = entry->next;
  }
  return CDI_UNDEFID;
}

int
subtypeInqTile(int subtypeID, int tileindex, int attribute)
{
  return subtypeInqSubEntry(subtypeID, matchAND(keyValuePair(cdiSubtypeAttributeName[SUBTYPE_ATT_TILEINDEX], tileindex),
                                                keyValuePair(cdiSubtypeAttributeName[SUBTYPE_ATT_TILEATTRIBUTE], attribute)));
}

int
subtypeInqAttribute(int subtypeID, int index, const char *key, int *outValue)
{
  // Validate input params.
  if (subtypeID == CDI_UNDEFID)
    xabort("CDI_UNDEFID was passed to %s() as a subtypeID. Please check the origin of that ID.", __func__);
  subtype_t *subtype_ptr = (subtype_t *) reshGetVal(subtypeID, &subtypeOps);
  if (!subtype_ptr) xabort("Internal error: subtypeID %d resolved to NULL.", subtypeID);

  if ((unsigned) index >= (unsigned) subtype_ptr->nentries)
  {
    xabort("index argument of %s() is out of range. Expected 0 <= index < %d, but got index = %d.", __func__, subtype_ptr->nentries,
           index);
  }

  if (!key) return CDI_EINVAL;
  int iKey = attribute_to_index(key);
  if (iKey < 0) return CDI_EINVAL;

  // Find the entry.
  struct subtype_entry_t *entry = subtype_ptr->entries;
  for (; index--; entry = entry->next)
    if (!entry) xabort("internal error: preliminary end of subtype entry list");

  // Find the attribute.
  for (struct subtype_attr_t *attribute = entry->atts; attribute; attribute = attribute->next)
  {
    if (attribute->key == iKey)
    {
      if (outValue) *outValue = attribute->val;
      return CDI_NOERR;
    }
  }

  // Failed to find the attribute if this point is reached.
  return CDI_EINVAL;
}

/* Construct a new subtype for a tile set. If a corresponding subtype
 * already exists, then we return this subtype ID instead.
 *
 * See comment on subtype.c::tilesetMatchingPtr for the specification
 * of the term "corresponding" tile set.
 */
int
vlistDefTileSubtype(int vlistID, subtype_t *tiles)
{
  int subtypeID = CDI_UNDEFID;

  /* loop over subtypes and search for an identical tileset */
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int tileset_defined = 0;
  for (int isub = 0; isub < vlistptr->nsubtypes; isub++)
  {
    /* get the ID of the "isub"th subtype */
    subtypeID = vlistptr->subtypeIDs[isub];
    if (subtypeComparePtr(subtypeID, tiles) == 0)
    {
      tileset_defined = 1;
      break;
    }
  }

  /* tile set seems to be new: register at resource handler. */
  if (tileset_defined == 0)
  {
    subtype_t *tiles_duplicate = NULL;
    subtypeDuplicate(tiles, &tiles_duplicate);
    subtypeID = vlistptr->subtypeIDs[vlistptr->nsubtypes++] = subtypePush(tiles_duplicate);
  }

  return subtypeID;
}

int
vlistInsertTrivialTileSubtype(int vlistID)
{
  /* first, generate a subtype */
  subtype_t *subtype_ptr;
  subtypeAllocate(&subtype_ptr, SUBTYPE_TILES);

  /* create a tile set that contains only one tile/attribute pair. */
  (void) subtypeEntryInsert(subtype_ptr);

  /* register tile */
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int subtypeID = vlistptr->subtypeIDs[vlistptr->nsubtypes++] = subtypePush(subtype_ptr);
  return subtypeID;
}

/* ------------------------------------------------------------------- */
/* NOT YET IMPLEMENTED                                                 */
/* ------------------------------------------------------------------- */

static int
subtypeGetPackSize(void *subtype_ptr, void *context)
{
  (void) subtype_ptr;
  (void) context;
  Error("Not yet implemented for subtypes!");
  return 0;
}

static void
subtypePack(void *subtype_ptr, void *buffer, int size, int *pos, void *context)
{
  (void) subtype_ptr;
  (void) buffer;
  (void) size;
  (void) pos;
  (void) context;
  Error("Not yet implemented for subtypes!");
}

static int
subtypeTxCode(void *subtypePtr)
{
  (void) subtypePtr;
  Error("Not yet implemented for subtypes!");
  return 0;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <inttypes.h>
#include <stdio.h>


void
swap4byte(void *ptr, size_t size)
{
  int32_t *ptrtmp = (int32_t *) ptr;

  // clang-format off
  for (size_t i = 0; i < size; ++i)
    ptrtmp[i] = (((ptrtmp[i] >> 24) & 0x00ff) | ((ptrtmp[i] & 0x00ff) << 24) |
                 ((ptrtmp[i] >>  8) & 0xff00) | ((ptrtmp[i] & 0xff00) <<  8));
  // clang-format on
}

void
swap8byte(void *ptr, size_t size)
{
  int64_t *ptrtmp = (int64_t *) ptr;

  // clang-format off
  for (size_t i = 0; i < size; ++i)
    ptrtmp[i] = (((ptrtmp[i] >> 56) & 0x000000ff) | ((ptrtmp[i] & 0x000000ff) << 56) |
                 ((ptrtmp[i] >> 40) & 0x0000ff00) | ((ptrtmp[i] & 0x0000ff00) << 40) |
                 ((ptrtmp[i] >> 24) & 0x00ff0000) | ((ptrtmp[i] & 0x00ff0000) << 24) |
                 ((ptrtmp[i] >>  8) & 0xff000000) | ((ptrtmp[i] & 0xff000000) <<  8));
  // clang-format on
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef TABLEPAR_H
#define TABLEPAR_H

enum
{
  TABLE_DUP_NAME = 1 << 0,
  TABLE_DUP_LONGNAME = 1 << 1,
  TABLE_DUP_UNITS = 1 << 2,
};

typedef struct
{
  int id;                // Parameter number (GRIB)
  int ltype;             // Level type (GRIB)
  int dupflags;          // keep track of which attributes got strdup'ed
  const char *name;      // Parameter name
  const char *longname;  // Parameter long name
  const char *units;     // Parameter units
} param_type;

void tableLink(int tableID, const param_type *pars, int npars);
int tableDef(int modelID, int tablegribID, const char *tablename);

int tableInqParCode(int tableID, char *name, int *code);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* Automatically generated, do not edit! */
#ifndef TABLE_H
#define TABLE_H

// clang-format off

static const param_type echam4[] = {
  {   4, -1, 0, "precip",      "total precipitation",                      "m/s"      },
  {  34, -1, 0, "low_cld",     "low cloud",                                 NULL      },
  {  35, -1, 0, "mid_cld",     "mid cloud",                                 NULL      },
  {  36, -1, 0, "hih_cld",     "high cloud",                                NULL      },
  { 129, -1, 0, "geosp",       "surface geopotential (orography)",         "m^2/s^2"  },
  { 130, -1, 0, "t",           "temperature",                              "K"        },
  { 131, -1, 0, "u",           "u-velocity",                               "m/s"      },
  { 132, -1, 0, "v",           "v-velocity",                               "m/s"      },
  { 133, -1, 0, "sq",          "specific humidity",                        "kg/kg"    },
  { 134, -1, 0, "aps",         "Surface pressure",                         "Pa"       },
  { 135, -1, 0, "omega",       "vertical velocity",                        "Pa/s"     },
  { 138, -1, 0, "svo",         "vorticity",                                "1/s"      },
  { 139, -1, 0, "ts",          "surface temperature",                      "K"        },
  { 140, -1, 0, "ws",          "soil wetness",                             "m"        },
  { 141, -1, 0, "sn",          "snow depth",                               "m"        },
  { 142, -1, 0, "aprl",        "large scale precipitation",                "m/s"      },
  { 143, -1, 0, "aprc",        "convective  precipitation",                "m/s"      },
  { 144, -1, 0, "aprs",        "snow fall",                                "m/s"      },
  { 145, -1, 0, "vdis",        "boundary layer dissipation",               "W/m^2"    },
  { 146, -1, 0, "ahfs",        "surface sensible heat flux",               "W/m^2"    },
  { 147, -1, 0, "ahfl",        "surface latent heat flux",                 "W/m^2"    },
  { 148, -1, 0, "stream",      "streamfunction",                           "m^2/s"    },
  { 149, -1, 0, "velopot",     "velocity potential",                       "m^2/s"    },
  { 151, -1, 0, "slp",         "mean sea level pressure",                  "Pa"       },
  { 152, -1, 0, "lsp",         "log surface pressure",                      NULL      },
  { 153, -1, 0, "sx",          "liquid water content",                     "kg/kg"    },
  { 155, -1, 0, "sd",          "divergence",                               "1/s"      },
  { 156, -1, 0, "geopoth",     "geopotential height",                      "m"        },
  { 157, -1, 0, "rhumidity",   "relative humidity",                        "fraction" },
  { 158, -1, 0, "var158",      "tendency of surface pressure",             "Pa/s"     },
  { 159, -1, 0, "ustar3",      "ustar3",                                   "m^3/s^3"  },
  { 160, -1, 0, "runoff",      "surface runoff",                           "m/s"      },
  { 161, -1, 0, "alwc",        "liquid water content",                     "kg/kg"    },
  { 162, -1, 0, "aclc",        "cloud cover",                              "fraction" },
  { 163, -1, 0, "aclcv",       "total cloud cover",                        "fraction" },
  { 164, -1, 0, "aclcov",      "total cloud cover",                        "fraction" },
  { 165, -1, 0, "u10",         "10m u-velocity",                           "m/s"      },
  { 166, -1, 0, "v10",         "10m v-velocity",                           "m/s"      },
  { 167, -1, 0, "temp2",       "2m temperature",                           "K"        },
  { 168, -1, 0, "dew2",        "2m dew point temperature",                 "K"        },
  { 169, -1, 0, "tsurf",       "surface temperature",                      "K"        },
  { 170, -1, 0, "td",          "deep soil temperature",                    "K"        },
  { 171, -1, 0, "wind10",      "10m windspeed",                            "m/s"      },
  { 172, -1, 0, "slm",         "land sea mask",                            "fraction" },
  { 173, -1, 0, "az0",         "surface roughness length",                 "m"        },
  { 174, -1, 0, "alb",         "surface background albedo",                "fraction" },
  { 175, -1, 0, "albedo",      "surface albedo",                           "fraction" },
  { 176, -1, 0, "srads",       "net surface solar radiation",              "W/m^2"    },
  { 177, -1, 0, "trads",       "net surface thermal radiation",            "W/m^2"    },
  { 178, -1, 0, "srad0",       "net top solar radiation",                  "W/m^2"    },
  { 179, -1, 0, "trad0",       "top thermal radiation (OLR)",              "W/m^2"    },
  { 180, -1, 0, "ustr",        "surface u-stress",                         "Pa"       },
  { 181, -1, 0, "vstr",        "surface v-stress",                         "Pa"       },
  { 182, -1, 0, "evap",        "surface evaporation",                      "m/s"      },
  { 183, -1, 0, "tdcl",        "soil temperature",                         "K"        },
  { 185, -1, 0, "srafs",       "net surf. solar radiation   (clear sky)",  "W/m^2"    },
  { 186, -1, 0, "trafs",       "net surf. thermal radiation (clear sky)",  "W/m^2"    },
  { 187, -1, 0, "sraf0",       "net top solar radiation     (clear sky)",  "W/m^2"    },
  { 188, -1, 0, "traf0",       "net top thermal radiation   (clear sky)",  "W/m^2"    },
  { 189, -1, 0, "sclfs",       "surface solar cloud forcing",              "W/m^2"    },
  { 190, -1, 0, "tclfs",       "surface thermal cloud forcing",            "W/m^2"    },
  { 191, -1, 0, "sclf0",       "top solar cloud forcing",                  "W/m^2"    },
  { 192, -1, 0, "tclf0",       "top thermal cloud forcing",                "W/m^2"    },
  { 193, -1, 0, "wl",          "skin reservoir content",                   "m"        },
  { 194, -1, 0, "wlm1",        "skin reservoir content of plants",         "m"        },
  { 195, -1, 0, "ustrgw",      "u-gravity wave stress",                    "Pa"       },
  { 196, -1, 0, "vstrgw",      "v-gravity wave stress",                    "Pa"       },
  { 197, -1, 0, "vdisgw",      "gravity wave dissipation",                 "W/m^2"    },
  { 198, -1, 0, "vgrat",       "vegetation ratio",                         "fraction" },
  { 199, -1, 0, "varor",       "orographic variance",                      "m^2"      },
  { 200, -1, 0, "vlt",         "leaf area index",                           NULL      },
  { 201, -1, 0, "t2max",       "maximum 2m-temperature",                   "K"        },
  { 202, -1, 0, "t2min",       "minimum 2m-temperature",                   "K"        },
  { 203, -1, 0, "srad0u",      "top solar radiation upward",               "W/m^2"    },
  { 204, -1, 0, "sradsu",      "surface solar radiation upward",           "W/m^2"    },
  { 205, -1, 0, "tradsu",      "surface thermal radiation upward",         "W/m^2"    },
  { 206, -1, 0, "tsn",         "snow temperature",                         "K"        },
  { 207, -1, 0, "td3",         "soil temperature 3",                       "K"        },
  { 208, -1, 0, "td4",         "soil temperature 4",                       "K"        },
  { 209, -1, 0, "td5",         "soil temperature 5",                       "K"        },
  { 210, -1, 0, "seaice",      "sea ice cover",                            "fraction" },
  { 211, -1, 0, "siced",       "sea ice depth",                            "m"        },
  { 212, -1, 0, "forest",      "vegetation type",                          "fraction" },
  { 213, -1, 0, "teff",        "(effective) sea-ice skin temperature",     "K"        },
  { 214, -1, 0, "tsmax",       "maximum surface temperature",              "K"        },
  { 215, -1, 0, "tsmin",       "minimum surface temperature",              "K"        },
  { 216, -1, 0, "wimax",       "maximum 10m-wind speed",                   "m/s"      },
  { 217, -1, 0, "topmax",      "maximum height of convective cloud tops",  "Pa"       },
  { 218, -1, 0, "snmel",       "snow melt",                                "m/s"      },
  { 219, -1, 0, "runtoc",      "surface runoff into ocean",                 NULL      },
  { 220, -1, 0, "tslin",       "land: residual surface heat budget",       "W/m^2"    },
  { 221, -1, 0, "dsnac",       "snow depth change",                        "m/s"      },
  { 222, -1, 0, "alwcac",      "liquid water content",                     "kg/kg"    },
  { 223, -1, 0, "aclcac",      "cloud cover",                              "fraction" },
  { 224, -1, 0, "tke",         "turbulent kinetic energy",                  NULL      },
  { 225, -1, 0, "tkem1",       "turbulent kinetic energy (t-1)",            NULL      },
  { 226, -1, 0, "fao",         "FAO data set (soil data flags)",            NULL      },
  { 227, -1, 0, "rgcgn",       "heat capacity of soil",                     NULL      },
  { 228, -1, 0, "sodif",       "soil diffusivity",                          NULL      },
  { 229, -1, 0, "wsmx",        "field capacity of soil",                   "m"        },
  { 230, -1, 0, "qvi",         "vertically integrated specific humidity",  "kg/m^2"   },
  { 231, -1, 0, "alwcvi",      "vertically integrated liquid water cont.", "kg/m^2"   },
  { 232, -1, 0, "glac",        "glacier mask",                             "fraction" },
  { 233, -1, 0, "runlnd",      "surface runoff not running into ocean",     NULL      },
  { 259, -1, 0, "windspeed",   "windspeed (sqrt(u^2+v^2))",                 NULL      },
  { 260, -1, 0, "precip",      "total precipitation",                      "m/s"      },
  { 261, -1, 0, "net_top",     "total top radiation",                       NULL      },
  { 262, -1, 0, "net_bot",     "total surface radiation",                   NULL      },
  { 263, -1, 0, "net_heat",    "net surface heat flux",                     NULL      },
  { 264, -1, 0, "net_water",   "total surface water",                       NULL      },
  { 268, -1, 0, "sw_atm",       NULL,                                       NULL      },
  { 269, -1, 0, "lw_atm",       NULL,                                       NULL      },
  { 270, -1, 0, "net_atm",      NULL,                                       NULL      },
  { 271, -1, 0, "surf_runoff", "surface runoff",                            NULL      },
  { 275, -1, 0, "fresh_water",  NULL,                                       NULL      },
};

static const param_type echam5[] = {
  {   4, -1, 0, "precip",     "total precipitation",                       "kg/m^2s" },
  {  79, -1, 0, "swnirac",    "net surface NIR flux acc.",                 "W/m^2"   },
  {  80, -1, 0, "swdifnirac", "fraction of diffuse NIR acc.",              "W/m^2"   },
  {  81, -1, 0, "swvisac",    "net surface visible flux acc.",             "W/m^2"   },
  {  82, -1, 0, "swdifvisac", "fraction of diffuse visible acc.",          "W/m^2"   },
  {  83, -1, 0, "ocu",        "ocean eastw. velocity (coupled mode)",      "m/s"     },
  {  84, -1, 0, "ocv",        "ocean northw. velocity (coupled mode)",     "m/s"     },
  {  85, -1, 0, "tradl",      "net LW radiation 200mb",                    "W/m^2"   },
  {  86, -1, 0, "sradl",      "net SW radiation 200mb",                    "W/m^2"   },
  {  87, -1, 0, "trafl",      "net LW radiation 200mb (clear sky)",        "W/m^2"   },
  {  88, -1, 0, "srafl",      "net SW radiation 200mb (clear sky)",        "W/m^2"   },
  {  89, -1, 0, "amlcorac",   "mixed layer flux correction",               "W/m^2"   },
  {  90, -1, 0, "amlheatac",  "mixed layer heat content",                  "J/m^2"   },
  {  91, -1, 0, "trfliac",    "net LW radiation over ice",                 "W/m^2"   },
  {  92, -1, 0, "trflwac",    "net LW radiation over water",               "W/m^2"   },
  {  93, -1, 0, "trfllac",    "net LW radiation over land",                "W/m^2"   },
  {  94, -1, 0, "sofliac",    "net SW radiation over ice",                 "W/m^2"   },
  {  95, -1, 0, "soflwac",    "net SW radiation over water",               "W/m^2"   },
  {  96, -1, 0, "sofllac",    "net SW radiation over land",                "W/m^2"   },
  {  97, -1, 0, "friac",      "ice cover (fraction of grid box)",           NULL     },
  { 102, -1, 0, "tsi",        "surface temperature of ice",                "K"       },
  { 103, -1, 0, "tsw",        "surface temperature of water",              "K"       },
  { 104, -1, 0, "ustri",      "zonal      wind stress over ice",           "Pa"      },
  { 105, -1, 0, "vstri",      "meridional wind stress over ice",           "Pa"      },
  { 106, -1, 0, "ustrw",      "zonal      wind stress over water",         "Pa"      },
  { 107, -1, 0, "vstrw",      "meridional wind stress over water",         "Pa"      },
  { 108, -1, 0, "ustrl",      "zonal      wind stress over land",          "Pa"      },
  { 109, -1, 0, "vstrl",      "meridional wind stress over land",          "Pa"      },
  { 110, -1, 0, "ahfliac",    "latent heat flux over ice",                 "W/m^2"   },
  { 111, -1, 0, "ahflwac",    "latent heat flux over water",               "W/m^2"   },
  { 112, -1, 0, "ahfllac",    "latent heat flux over land",                "W/m^2"   },
  { 113, -1, 0, "evapiac",    "evaporation over ice",                      "kg/m^2s" },
  { 114, -1, 0, "evapwac",    "evaporation over water",                    "kg/m^2s" },
  { 115, -1, 0, "evaplac",    "evaporation over land",                     "kg/m^2s" },
  { 116, -1, 0, "az0i",       "roughness length over ice",                 "m"       },
  { 117, -1, 0, "az0w",       "roughness length over water",               "m"       },
  { 118, -1, 0, "az0l",       "roughness length over land",                "m"       },
  { 119, -1, 0, "ahfsiac",    "sensible heat flux over ice",               "W/m^2"   },
  { 120, -1, 0, "ahfswac",    "sensible heat flux over water",             "W/m^2"   },
  { 121, -1, 0, "ahfslac",    "sensible heat flux over land",              "W/m^2"   },
  { 122, -1, 0, "alsoi",      "albedo of ice",                              NULL     },
  { 123, -1, 0, "alsow",      "albedo of water",                            NULL     },
  { 124, -1, 0, "alsol",      "albedo of land",                             NULL     },
  { 125, -1, 0, "ahfice",     "conductive heat flux through ice",          "W/m^2"   },
  { 126, -1, 0, "qres",       "residual heat flux for melting sea ice",    "W/m^2"   },
  { 127, -1, 0, "alake",      "lake fraction",                              NULL     },
  { 128, -1, 0, "rintop",     "low level inversion",                        NULL     },
  { 129, -1, 0, "geosp",      "surface geopotential (orography)",          "m^2/s^2" },
  { 130, -1, 0, "t",          "temperature",                               "K"       },
  { 131, -1, 0, "u",          "u-velocity",                                "m/s"     },
  { 132, -1, 0, "v",          "v-velocity",                                "m/s"     },
  { 133, -1, 0, "q",          "specific humidity",                         "kg/kg"   },
  { 134, -1, 0, "aps",        "surface pressure",                          "Pa"      },
  { 135, -1, 0, "omega",      "vertical velocity",                         "Pa/s"    },
  { 136, -1, 0, "acdnc",      "cloud droplet number concentration",        "1/m^3"   },
  { 137, -1, 0, "apmeb",      "(P-E) error",                               "kg/m^2s" },
  { 138, -1, 0, "svo",        "vorticity",                                 "1/s"     },
  { 139, -1, 0, "tslm1",      "surface temperature of land",               "K"       },
  { 140, -1, 0, "ws",         "soil wetness",                              "m"       },
  { 141, -1, 0, "sn",         "water equivalent snow depth",               "m"       },
  { 142, -1, 0, "aprl",       "large scale precipitation",                 "kg/m^2s" },
  { 143, -1, 0, "aprc",       "convective  precipitation",                 "kg/m^2s" },
  { 144, -1, 0, "aprs",       "snow fall",                                 "kg/m^2s" },
  { 145, -1, 0, "vdis",       "boundary layer dissipation",                "W/m^2"   },
  { 146, -1, 0, "ahfs",       "sensible heat flux",                        "W/m^2"   },
  { 147, -1, 0, "ahfl",       "latent heat flux",                          "W/m^2"   },
  { 148, -1, 0, "stream",     "streamfunction",                            "m^2/s"   },
  { 149, -1, 0, "velopot",    "velocity potential",                        "m^2/s"   },
  { 150, -1, 0, "xivi",       "vertically integrated cloud ice",           "kg/m^2"  },
  { 151, -1, 0, "slp",        "mean sea level pressure",                   "Pa"      },
  { 152, -1, 0, "lsp",        "log surface pressure",                       NULL     },
  { 153, -1, 0, "xl",         "cloud water",                               "kg/kg"   },
  { 154, -1, 0, "xi",         "cloud ice",                                 "kg/kg"   },
  { 155, -1, 0, "sd",         "divergence",                                "1/s"     },
  { 156, -1, 0, "geopoth",    "geopotential height",                       "m"       },
  { 157, -1, 0, "rhumidity",  "relative humidity",                          NULL     },
  { 159, -1, 0, "wind10w",    "10m windspeed over water",                  "m/s"     },
  { 160, -1, 0, "runoff",     "surface runoff and drainage",               "kg/m^2s" },
  { 161, -1, 0, "drain",      "drainage",                                  "kg/m^2s" },
  { 162, -1, 0, "aclc",       "cloud cover",                                NULL     },
  { 164, -1, 0, "aclcov",     "total cloud cover",                          NULL     },
  { 165, -1, 0, "u10",        "10m u-velocity",                            "m/s"     },
  { 166, -1, 0, "v10",        "10m v-velocity",                            "m/s"     },
  { 167, -1, 0, "temp2",      "2m temperature",                            "K"       },
  { 168, -1, 0, "dew2",       "2m dew point temperature",                  "K"       },
  { 169, -1, 0, "tsurf",      "surface temperature",                       "K"       },
  { 170, -1, 0, "xvar",       "variance of total water amount",            "kg/kg"   },
  { 171, -1, 0, "wind10",     "10m windspeed",                             "m/s"     },
  { 172, -1, 0, "slm",        "land sea mask (1. = land, 0. = sea/lakes)",  NULL     },
  { 173, -1, 0, "az0",        "roughness length",                          "m"       },
  { 174, -1, 0, "alb",        "surface background albedo",                  NULL     },
  { 175, -1, 0, "albedo",     "surface albedo",                             NULL     },
  { 176, -1, 0, "srads",      "net surface SW radiation",                  "W/m^2"   },
  { 177, -1, 0, "trads",      "net surface LW radiation",                  "W/m^2"   },
  { 178, -1, 0, "srad0",      "net top SW radiation",                      "W/m^2"   },
  { 179, -1, 0, "trad0",      "net top LW radiation (-OLR)",               "W/m^2"   },
  { 180, -1, 0, "ustr",       "u-stress",                                  "Pa"      },
  { 181, -1, 0, "vstr",       "v-stress",                                  "Pa"      },
  { 182, -1, 0, "evap",       "evaporation",                               "kg/m^2s" },
  { 183, -1, 0, "xskew",      "skewness of total water amount qv+qi+ql",    NULL     },
  { 184, -1, 0, "srad0d",     "top incoming SW radiation",                 "W/m^2"   },
  { 185, -1, 0, "srafs",      "net surface SW radiation (clear sky)",      "W/m^2"   },
  { 186, -1, 0, "trafs",      "net surface LW radiation (clear sky)",      "W/m^2"   },
  { 187, -1, 0, "sraf0",      "net top SW radiation   (clear sky)",        "W/m^2"   },
  { 188, -1, 0, "traf0",      "net top LW radiation   (clear sky)",        "W/m^2"   },
  { 189, -1, 0, "sclfs",      "net surface SW cloud forcing (176-185)",    "W/m^2"   },
  { 190, -1, 0, "tclfs",      "net surface LW cloud forcing (177-186)",    "W/m^2"   },
  { 191, -1, 0, "sclf0",      "net SW top cloud forcing (178-187)",        "W/m^2"   },
  { 192, -1, 0, "tclf0",      "net LW top cloud forcing (179-188)",        "W/m^2"   },
  { 193, -1, 0, "wl",         "skin reservoir content",                    "m"       },
  { 194, -1, 0, "slf",        "fractional land cover",                      NULL     },
  { 195, -1, 0, "ustrgw",     "u-gravity wave stress",                     "Pa"      },
  { 196, -1, 0, "vstrgw",     "v-gravity wave stress",                     "Pa"      },
  { 197, -1, 0, "vdisgw",     "gravity wave dissipation",                  "W/m^2"   },
  { 198, -1, 0, "vgrat",      "vegetation ratio",                           NULL     },
  { 199, -1, 0, "orostd",     "orographic standard deviation",             "m"       },
  { 200, -1, 0, "vlt",        "leaf area index",                            NULL     },
  { 201, -1, 0, "t2max",      "maximum 2m-temperature",                    "K"       },
  { 202, -1, 0, "t2min",      "minimum 2m-temperature",                    "K"       },
  { 203, -1, 0, "srad0u",     "top SW radiation upward",                   "W/m^2"   },
  { 204, -1, 0, "sradsu",     "surface SW radiation upward",               "W/m^2"   },
  { 205, -1, 0, "tradsu",     "surface LW radiation upward",               "W/m^2"   },
  { 206, -1, 0, "grndflux",   "surface ground heat flux",                   NULL     },
  { 207, -1, 0, "tsoil",      "deep soil temperatures (5 layers)",         "K"       },
  { 208, -1, 0, "ahfcon",     "conductive heat flux through ice",          "W/m^2"   },
  { 209, -1, 0, "ahfres",     "res. heat flux for melting ice",            "W/m^2"   },
  { 210, -1, 0, "seaice",     "ice cover (fraction of ice+water)",          NULL     },
  { 211, -1, 0, "siced",      "ice thickness",                             "m"       },
  { 212, -1, 0, "forest",     "forest fraction",                            NULL     },
  { 213, -1, 0, "gld",        "glacier thickness",                         "m"       },
  { 214, -1, 0, "sni",        "water equivalent of snow on ice",           "m"       },
  { 215, -1, 0, "rogl",       "glacier runoff",                            "kg/m^2s" },
  { 216, -1, 0, "wimax",      "maximum 10m-wind speed",                    "m/s"     },
  { 217, -1, 0, "topmax",     "maximum height of convective cloud tops",   "Pa"      },
  { 218, -1, 0, "snmel",      "snow melt",                                 "kg/m^2s" },
  { 219, -1, 0, "runtoc",     "surface runoff into ocean",                 "kg/m^2s" },
  { 220, -1, 0, "runlnd",     "surface runoff not running into ocean",     "kg/m^2s" },
  { 221, -1, 0, "apmegl",     "P-E over land ice",                         "kg/m^2s" },
  { 222, -1, 0, "snacl",      "snow accumulation over land",               "kg/m^2s" },
  { 223, -1, 0, "aclcac",     "cloud cover",                                NULL     },
  { 224, -1, 0, "tke",        "turbulent kinetic energy",                  "m^2/s^2" },
  { 225, -1, 0, "tkem1",      "turbulent kinetic energy (t-1)",            "m^2/s^2" },
  { 226, -1, 0, "fao",        "FAO data set (soil data flags) 0...5",       NULL     },
  { 227, -1, 0, "rgcgn",      "heat capacity of soil",                      NULL     },
  { 228, -1, 0, "sodif",      "soil diffusivity",                          "m^2/s"   },
  { 229, -1, 0, "wsmx",       "field capacity of soil",                    "m"       },
  { 230, -1, 0, "qvi",        "vertically integrated water vapor",         "kg/m^2"  },
  { 231, -1, 0, "xlvi",       "vertically integrated cloud water",         "kg/m^2"  },
  { 232, -1, 0, "glac",       "fraction of land covered by glaciers",       NULL     },
  { 233, -1, 0, "snc",        "snow depth at the canopy",                  "m"       },
  { 234, -1, 0, "rtype",      "type of convection",                        "0...3"   },
  { 235, -1, 0, "abso4",      "anthropogenic sulfur burden",               "kg/m^2"  },
  { 236, -1, 0, "ao3",        "ipcc ozone",                                "kg/m^2"  },
  { 237, -1, 0, "tropo",      "WMO defined tropopause height",             "Pa"      },
  { 259, -1, 0, "windspeed",  "windspeed (sqrt(u^2+v^2))",                 "m/s"     },
  { 260, -1, 0, "precip",     "total precipitation  (142+143)",            "kg/m^2s" },
  { 261, -1, 0, "net_top",    "total top radiation  (178+179)",            "W/m^2"   },
  { 262, -1, 0, "net_bot",    "total surface radiation (176+177)",         "W/m^2"   },
  { 272, -1, 0, "mastrfu",    "mass stream function",                      "kg/s"    },
};

static const param_type echam6[] = {
  {   4, -1, 0, "precip",         "total precipitation",                       "kg m-2 s-1" },
  {  34, -1, 0, "low_cld",        "low cloud",                                  NULL        },
  {  35, -1, 0, "mid_cld",        "mid cloud",                                  NULL        },
  {  36, -1, 0, "hih_cld",        "high cloud",                                 NULL        },
  {  68, -1, 0, "fage",           "aging factor of snow on ice",                NULL        },
  {  69, -1, 0, "snifrac",        "fraction of ice covered with snow",          NULL        },
  {  70, -1, 0, "barefrac",       "bare ice fraction",                          NULL        },
  {  71, -1, 0, "alsom",          "albedo of melt ponds",                       NULL        },
  {  72, -1, 0, "alsobs",         "albedo of bare ice and snow",                NULL        },
  {  73, -1, 0, "sicepdw",        "melt pond depth on sea-ice",                "m"          },
  {  74, -1, 0, "sicepdi",        "ice thickness on melt pond",                "m"          },
  {  75, -1, 0, "tsicepdi",       "ice temperature on frozen melt pond",       "K"          },
  {  76, -1, 0, "sicepres",       "residual heat flux",                        "W m-2"      },
  {  77, -1, 0, "ameltdepth",     "total melt pond depth",                     "m"          },
  {  78, -1, 0, "ameltfrac",      "fractional area of melt ponds on sea-ice",   NULL        },
  {  79, -1, 0, "albedo_vis_dir", "surface albedo visible range direct",        NULL        },
  {  80, -1, 0, "albedo_nir_dir", "surface albedo NIR range direct",            NULL        },
  {  81, -1, 0, "albedo_vis_dif", "surface albedo visible range diffuse",       NULL        },
  {  82, -1, 0, "albedo_nir_dif", "surface albedo NIR range diffuse",           NULL        },
  {  83, -1, 0, "ocu",            "ocean eastw. velocity (coupled mode)",      "m/s"        },
  {  84, -1, 0, "ocv",            "ocean northw. velocity (coupled mode)",     "m/s"        },
  {  85, -1, 0, "tradl",          "thermal radiation 200mb",                   "W m-2"      },
  {  86, -1, 0, "sradl",          "solar radiation 200mb",                     "W m-2"      },
  {  87, -1, 0, "trafl",          "thermal radiation 200mb (clear sky)",       "W m-2"      },
  {  88, -1, 0, "srafl",          "solar radiation 200mb (clear sky)",         "W m-2"      },
  {  89, -1, 0, "amlcorac",       "mixed layer flux correction",               "W m-2"      },
  {  90, -1, 0, "amlheatac",      "mixed layer heat content",                  "J m-2"      },
  {  91, -1, 0, "trfliac",        "LW flux over ice",                          "W m-2"      },
  {  92, -1, 0, "trflwac",        "LW flux over water",                        "W m-2"      },
  {  93, -1, 0, "trfllac",        "LW flux over land",                         "W m-2"      },
  {  94, -1, 0, "sofliac",        "SW flux over ice",                          "W m-2"      },
  {  95, -1, 0, "soflwac",        "SW flux over water",                        "W m-2"      },
  {  96, -1, 0, "sofllac",        "SW flux over land",                         "W m-2"      },
  {  97, -1, 0, "friac",          "ice cover (fraction of grid box)",           NULL        },
  { 102, -1, 0, "tsi",            "surface temperature of ice",                "K"          },
  { 103, -1, 0, "tsw",            "surface temperature of water",              "K"          },
  { 104, -1, 0, "ustri",          "zonal      wind stress over ice",           "Pa"         },
  { 105, -1, 0, "vstri",          "meridional wind stress over ice",           "Pa"         },
  { 106, -1, 0, "ustrw",          "zonal      wind stress over water",         "Pa"         },
  { 107, -1, 0, "vstrw",          "meridional wind stress over water",         "Pa"         },
  { 108, -1, 0, "ustrl",          "zonal      wind stress over land",          "Pa"         },
  { 109, -1, 0, "vstrl",          "meridional wind stress over land",          "Pa"         },
  { 110, -1, 0, "ahfliac",        "latent heat flux over ice",                 "W m-2"      },
  { 111, -1, 0, "ahflwac",        "latent heat flux over water",               "W m-2"      },
  { 112, -1, 0, "ahfllac",        "latent heat flux over land",                "W m-2"      },
  { 113, -1, 0, "evapiac",        "evaporation over ice",                      "kg m-2 s-1" },
  { 114, -1, 0, "evapwac",        "evaporation over water",                    "kg m-2 s-1" },
  { 115, -1, 0, "evaplac",        "evaporation over land",                     "kg m-2 s-1" },
  { 116, -1, 0, "az0i",           "roughness length over ice",                 "m"          },
  { 117, -1, 0, "az0w",           "roughness length over water",               "m"          },
  { 118, -1, 0, "az0l",           "roughness length over land",                "m"          },
  { 119, -1, 0, "ahfsiac",        "sensible heat flux over ice",               "W m-2"      },
  { 120, -1, 0, "ahfswac",        "sensible heat flux over water",             "W m-2"      },
  { 121, -1, 0, "ahfslac",        "sensible heat flux over land",              "W m-2"      },
  { 122, -1, 0, "alsoi",          "albedo of ice",                              NULL        },
  { 123, -1, 0, "alsow",          "albedo of water",                            NULL        },
  { 124, -1, 0, "alsol",          "albedo of land",                             NULL        },
  { 125, -1, 0, "ahfice",         "conductive heat flux",                      "W m-2"      },
  { 126, -1, 0, "qres",           "residual heat flux for melting sea ice",    "W m-2"      },
  { 127, -1, 0, "alake",          "lake fraction of grid box",                 "fraction"   },
  { 128, -1, 0, "rintop",         "low level inversion",                        NULL        },
  { 129, -1, 0, "geosp",          "surface geopotential (orography)",          "m^2/s^2"    },
  { 130, -1, 0, "t",              "temperature",                               "K"          },
  { 131, -1, 0, "u",              "u-velocity",                                "m/s"        },
  { 132, -1, 0, "v",              "v-velocity",                                "m/s"        },
  { 133, -1, 0, "q",              "specific humidity",                         "kg/kg"      },
  { 134, -1, 0, "aps",            "surface pressure",                          "Pa"         },
  { 135, -1, 0, "omega",          "vertical velocity",                         "Pa/s"       },
  { 136, -1, 0, "acdnc",          "cloud droplet number concentration",        "1 m-3"      },
  { 137, -1, 0, "apmeb",          "vert. integr. tendencies of water",         "kg m-2 s-1" },
  { 138, -1, 0, "svo",            "vorticity",                                 "1/s"        },
  { 139, -1, 0, "tslm1",          "surface temperature of land",               "K"          },
  { 140, -1, 0, "ws",             "soil wetness",                              "m"          },
  { 141, -1, 0, "sn",             "snow depth",                                "m"          },
  { 142, -1, 0, "aprl",           "large scale precipitation",                 "kg m-2 s-1" },
  { 143, -1, 0, "aprc",           "convective  precipitation",                 "kg m-2 s-1" },
  { 144, -1, 0, "aprs",           "snow fall",                                 "kg m-2 s-1" },
  { 145, -1, 0, "vdis",           "boundary layer dissipation",                "W m-2"      },
  { 146, -1, 0, "ahfs",           "sensible heat flux",                        "W m-2"      },
  { 147, -1, 0, "ahfl",           "latent heat flux",                          "W m-2"      },
  { 148, -1, 0, "stream",         "streamfunction",                            "m^2/s"      },
  { 149, -1, 0, "velopot",        "velocity potential",                        "m^2/s"      },
  { 150, -1, 0, "xivi",           "vertically integrated cloud ice",           "kg m-2"     },
  { 151, -1, 0, "slp",            "mean sea level pressure",                   "Pa"         },
  { 152, -1, 0, "lsp",            "log surface pressure",                       NULL        },
  { 153, -1, 0, "xl",             "cloud water",                               "kg/kg"      },
  { 154, -1, 0, "xi",             "cloud ice",                                 "kg/kg"      },
  { 155, -1, 0, "sd",             "divergence",                                "1/s"        },
  { 156, -1, 0, "geopoth",        "geopotential height",                       "m"          },
  { 157, -1, 0, "rhumidity",      "relative humidity",                         "fraction"   },
  { 158, -1, 0, "var158",         "tendency of surface pressure",              "Pa/s"       },
  { 159, -1, 0, "wind10w",        "10m windspeed over water",                  "m/s"        },
  { 160, -1, 0, "runoff",         "surface runoff and drainage",               "kg m-2 s-1" },
  { 161, -1, 0, "drain",          "drainage",                                  "kg m-2 s-1" },
  { 162, -1, 0, "aclc",           "cloud cover",                                NULL        },
  { 163, -1, 0, "aclcv",          "total cloud cover",                          NULL        },
  { 164, -1, 0, "aclcov",         "total cloud cover (mean)",                   NULL        },
  { 165, -1, 0, "u10",            "10m u-velocity",                            "m/s"        },
  { 166, -1, 0, "v10",            "10m v-velocity",                            "m/s"        },
  { 167, -1, 0, "temp2",          "2m temperature",                            "K"          },
  { 168, -1, 0, "dew2",           "2m dew point temperature",                  "K"          },
  { 169, -1, 0, "tsurf",          "surface temperature",                       "K"          },
  { 170, -1, 0, "xvar",           "variance of total water amount qv+qi+ql",   "kg/kg"      },
  { 171, -1, 0, "wind10",         "10m windspeed",                             "m/s"        },
  { 172, -1, 0, "slm",            "land sea mask (1. = land, 0. = sea/lakes)",  NULL        },
  { 173, -1, 0, "az0",            "roughness length",                          "m"          },
  { 174, -1, 0, "alb",            "surface background albedo",                  NULL        },
  { 175, -1, 0, "albedo",         "surface albedo",                             NULL        },
  { 176, -1, 0, "srads",          "net surface solar radiation",               "W m-2"      },
  { 177, -1, 0, "trads",          "net surface thermal radiation",             "W m-2"      },
  { 178, -1, 0, "srad0",          "net top solar radiation",                   "W m-2"      },
  { 179, -1, 0, "trad0",          "top thermal radiation (OLR)",               "W m-2"      },
  { 180, -1, 0, "ustr",           "u-stress",                                  "Pa"         },
  { 181, -1, 0, "vstr",           "v-stress",                                  "Pa"         },
  { 182, -1, 0, "evap",           "evaporation",                               "kg m-2 s-1" },
  { 183, -1, 0, "xskew",          "skewness of total water amount qv+qi+ql",    NULL        },
  { 184, -1, 0, "srad0d",         "top incoming solar radiation",              "W m-2"      },
  { 185, -1, 0, "srafs",          "net surf. solar radiation   (clear sky)",   "W m-2"      },
  { 186, -1, 0, "trafs",          "net surf. thermal radiation (clear sky)",   "W m-2"      },
  { 187, -1, 0, "sraf0",          "net top solar radiation     (clear sky)",   "W m-2"      },
  { 188, -1, 0, "traf0",          "net top thermal radiation   (clear sky)",   "W m-2"      },
  { 189, -1, 0, "sclfs",          "surface solar cloud forcing",               "W m-2"      },
  { 190, -1, 0, "tclfs",          "surface thermal cloud forcing",             "W m-2"      },
  { 191, -1, 0, "sclf0",          "SW top cloud forcing (178-187)",            "W m-2"      },
  { 192, -1, 0, "tclf0",          "LW top cloud forcing (179-188)",            "W m-2"      },
  { 193, -1, 0, "wl",             "skin reservoir content",                    "m"          },
  { 194, -1, 0, "slf",            "sea land fraction",                          NULL        },
  { 195, -1, 0, "ustrgw",         "u-gravity wave stress",                     "Pa"         },
  { 196, -1, 0, "vstrgw",         "v-gravity wave stress",                     "Pa"         },
  { 197, -1, 0, "vdisgw",         "gravity wave dissipation",                  "W m-2"      },
  { 198, -1, 0, "vgrat",          "vegetation ratio",                           NULL        },
  { 199, -1, 0, "orostd",         "orographic standard deviation",             "m"          },
  { 200, -1, 0, "vlt",            "leaf area index",                            NULL        },
  { 201, -1, 0, "t2max",          "maximum 2m-temperature",                    "K"          },
  { 202, -1, 0, "t2min",          "minimum 2m-temperature",                    "K"          },
  { 203, -1, 0, "srad0u",         "top solar radiation upward",                "W m-2"      },
  { 204, -1, 0, "sradsu",         "surface solar radiation upward",            "W m-2"      },
  { 205, -1, 0, "tradsu",         "surface thermal radiation upward",          "W m-2"      },
  { 206, -1, 0, "grndflux",       "surface ground heat flux",                   NULL        },
  { 207, -1, 0, "tsoil",          "deep soil temperatures (5 layers)",         "K"          },
  { 208, -1, 0, "ahfcon",         "conductive heat flux through ice",          "W m-2"      },
  { 209, -1, 0, "ahfres",         "melting of ice",                            "W m-2"      },
  { 210, -1, 0, "seaice",         "ice cover (fraction of 1-SLM)",              NULL        },
  { 211, -1, 0, "siced",          "ice depth",                                 "m"          },
  { 212, -1, 0, "forest",         "forest fraction",                            NULL        },
  { 213, -1, 0, "gld",            "glacier depth",                             "m"          },
  { 214, -1, 0, "sni",            "water equivalent of snow on ice",           "m"          },
  { 215, -1, 0, "rogl",           "glacier runoff",                            "kg m-2 s-1" },
  { 216, -1, 0, "wimax",          "maximum 10m-wind speed",                    "m/s"        },
  { 217, -1, 0, "topmax",         "maximum height of convective cloud tops",   "Pa"         },
  { 218, -1, 0, "snmel",          "snow melt",                                 "kg m-2 s-1" },
  { 219, -1, 0, "runtoc",         "surface runoff into ocean",                 "kg m-2 s-1" },
  { 220, -1, 0, "runlnd",         "surface runoff not running into ocean",     "kg m-2 s-1" },
  { 221, -1, 0, "apmegl",         "P-E over land ice",                         "kg m-2 s-1" },
  { 222, -1, 0, "snacl",          "snow accumulation over land",               "kg m-2 s-1" },
  { 223, -1, 0, "aclcac",         "cloud cover",                                NULL        },
  { 224, -1, 0, "tke",            "turbulent kinetic energy",                  "m^2/s^2"    },
  { 225, -1, 0, "tkem1",          "turbulent kinetic energy (t-1)",            "m^2/s^2"    },
  { 226, -1, 0, "fao",            "FAO data set (soil data flags)",            "0...5"      },
  { 227, -1, 0, "rgcgn",          "heat capacity of soil",                      NULL        },
  { 228, -1, 0, "sodif",          "diffusivity of soil and land ice",          "m^2/s"      },
  { 229, -1, 0, "wsmx",           "field capacity of soil",                    "m"          },
  { 230, -1, 0, "qvi",            "vertically integrated water vapor",         "kg m-2"     },
  { 231, -1, 0, "xlvi",           "vertically integrated cloud water",         "kg m-2"     },
  { 232, -1, 0, "glac",           "fraction of land covered by glaciers",       NULL        },
  { 233, -1, 0, "snc",            "snow depth at the canopy",                  "m"          },
  { 234, -1, 0, "rtype",          "type of convection",                        "0...3"      },
  { 235, -1, 0, "abso4",          "antropogenic sulfur burden",                "kg m-2"     },
  { 236, -1, 0, "ao3",            "ipcc ozone",                                "kg m-2"     },
  { 237, -1, 0, "tropo",          "WMO defined tropopause height",             "Pa"         },
  { 259, -1, 0, "windspeed",      "windspeed (sqrt(u^2+v^2))",                 "m/s"        },
  { 260, -1, 0, "precip",         "total precipitation  (142+143)",            "kg m-2 s-1" },
  { 261, -1, 0, "net_top",        "total top radiation  (178+179)",            "W m-2"      },
  { 262, -1, 0, "net_bot",        "total surface radiation (176+177)",         "W m-2"      },
  { 272, -1, 0, "mastfru",        "mass stream function",                      "kg/s"       },
};

static const param_type mpiom1[] = {
  {   2, -1, 0, "THO",      "temperature",                     "C"        },
  {   5, -1, 0, "SAO",      "salinity",                        "psu"      },
  {   3, -1, 0, "UKO",      "zon. velocity",                   "m/s"      },
  {   4, -1, 0, "VKE",      "mer. velocity",                   "m/s"      },
  { 303, -1, 0, "UKOMFL",   "zon. velocity (divergence free)", "m/s"      },
  { 304, -1, 0, "VKEMFL",   "mer. velocity (divergence free)", "m/s"      },
  {   7, -1, 0, "WO",       "ver. velocity",                   "m/s"      },
  {   8, -1, 0, "RHO",      "insitu density",                  "kg/m**3"  },
  {   6, -1, 0, "PO",       "pressure",                        "Pa"       },
  {  67, -1, 0, "EMINPO",   "freshwaterflux by restoring",     "m/s"      },
  {  70, -1, 0, "FLUM",     "total heatflux",                  "W/m**2"   },
  {  79, -1, 0, "PEM",      "total freshwaterflux",            "m/s"      },
  {  13, -1, 0, "SICTHO",   "ice thickness",                   "m"        },
  {  15, -1, 0, "SICOMO",   "ice compactness",                 "frac."    },
  {  35, -1, 0, "SICUO",    "zon. ice velocity",               "m/s"      },
  {  36, -1, 0, "SICVE",    "mer. ice velocity",               "m/s"      },
  {  92, -1, 0, "TAFO",     "surface air temperature",         "C"        },
  { 164, -1, 0, "FCLOU",    "cloud cover",                      NULL      },
  {  52, -1, 0, "TXO",      "surface u-stress",                "Pa/1025." },
  {  53, -1, 0, "TYE",      "surface v-stress",                "Pa/1025." },
  { 260, -1, 0, "FPREC",    "prescr. precipitation",           "m/s"      },
  {  80, -1, 0, "FSWR",     "downward shortwave rad.",         "W/m**2"   },
  {  81, -1, 0, "FTDEW",    "dewpoint temperature",            "K"        },
  { 171, -1, 0, "FU10",     "10m windspeed",                   "m/s"      },
  { 141, -1, 0, "SICSNO",   "snow thickness",                  "m"        },
  { 176, -1, 0, "QSWO",     "heat flux shortwave",             "W/m**2"   },
  { 177, -1, 0, "QLWO",     "heat flux longwave",              "W/m**2"   },
  { 147, -1, 0, "QLAO",     "heat flux latent",                "W/m**2"   },
  { 146, -1, 0, "QSEO",     "heat flux sensible",              "W/m**2"   },
  {  65, -1, 0, "PRECO",    "net freshwater flux + runoff",    "m/s"      },
  {   1, -1, 0, "ZO",       "sealevel",                        "m"        },
  {  82, -1, 0, "Z1O",      "sealevel change",                 "m"        },
  {  69, -1, 0, "KCONDEP",  "depth of convection",             "level"    },
  {  27, -1, 0, "PSIUWE",   "hor. bar. streamfunction",        "Sv"       },
  {  83, -1, 0, "AMLD",     "mixed layer depth",               "m"        },
  { 172, -1, 0, "WETO",     "landseamask (pressure points)",    NULL      },
  { 507, -1, 0, "AMSUE",    "landseamask (vector points v)",    NULL      },
  { 508, -1, 0, "AMSUO",    "landseamask (vector points u)",    NULL      },
  {  84, -1, 0, "DEPTO",    "depth at pressure points",        "m"        },
  { 484, -1, 0, "DEUTO",    "depth at vector points (u)",      "m"        },
  { 584, -1, 0, "DEUTE",    "depth at vector points (v)",      "m"        },
  { 184, -1, 0, "DDUO",     "level thickness (vector u )",     "m"        },
  { 284, -1, 0, "DDUE",     "level thickness (vector v )",     "m"        },
  { 384, -1, 0, "DDPO",     "level thickness (pressure )",     "m"        },
  {  85, -1, 0, "DLXP",     "grid distance x",                 "m"        },
  {  86, -1, 0, "DLYP",     "grid distance y",                 "m"        },
  { 185, -1, 0, "DLXU",     "grid distance x  (vector u)",     "m"        },
  { 186, -1, 0, "DLYU",     "grid distance y  (vector u)",     "m"        },
  { 285, -1, 0, "DLXV",     "grid distance x  (vector v)",     "m"        },
  { 286, -1, 0, "DLYV",     "grid distance y  (vector v)",     "m"        },
  {  54, -1, 0, "GILA",     "latitude in radiants",            "rad"      },
  {  55, -1, 0, "GIPH",     "longitude in radiants",           "rad"      },
  { 354, -1, 0, "ALAT",     "latitude in degrees (pressure)",  "deg"      },
  { 355, -1, 0, "ALON",     "longitude in degrees (pressure)", "deg"      },
  { 154, -1, 0, "ALATU",    "latitude in degrees (vector u)",  "deg"      },
  { 155, -1, 0, "ALONU",    "longitude in degrees (vector u)", "deg"      },
  { 254, -1, 0, "ALATV",    "latitude in degrees (vector v)",  "deg"      },
  { 255, -1, 0, "ALONV",    "longitude in degrees (vector v)", "deg"      },
  { 110, -1, 0, "AVO",      "vertical impuls diffusion",       "m**2/s"   },
  { 111, -1, 0, "DVO",      "vertical T,S diffusion",          "m**2/s"   },
  { 142, -1, 0, "SICTRU",   "seaice transport x",              "m**2/s"   },
  { 143, -1, 0, "SICTRV",   "seaice transport y",              "m**2/s"   },
  { 612, -1, 0, "WTMIX",    "wind mixing",                     "m**2/s"   },
  { 183, -1, 0, "zmld",     "mixed layer depth (SJ)",          "m"        },
  { 207, -1, 0, "WGO",      "GM vertical velocity",            "m/s"      },
  { 305, -1, 0, "rivrun",   "RiverRunoff",                     "m/s"      },
  { 158, -1, 0, "TMCDO",    "mon. mean depth of convection",   "level"    },
  { 247, -1, 0, "DQSWO",    "heatflux sw over water",          "W/m**2"   },
  { 248, -1, 0, "DQLWO",    "heatflux lw over water",          "W/m**2"   },
  { 249, -1, 0, "DQSEO",    "heatflux se over water",          "W/m**2"   },
  { 250, -1, 0, "DQLAO",    "heatflux la over water",          "W/m**2"   },
  { 251, -1, 0, "DQTHO",    "heatflux net over water",         "W/m**2"   },
  { 252, -1, 0, "DQSWI",    "heatflux sw over seaice",         "W/m**2"   },
  { 253, -1, 0, "DQLWI",    "heatflux lw over seaice",         "W/m**2"   },
  { 254, -1, 0, "DQSEI",    "heatflux se over seaice",         "W/m**2"   },
  { 255, -1, 0, "DQLAI",    "heatflux la over seaice",         "W/m**2"   },
  { 256, -1, 0, "DQTHI",    "heatflux net over seaice",        "W/m**2"   },
  { 257, -1, 0, "DTICEO",   "Equi. temp over seaice",          "K"        },
  { 270, -1, 0, "AOFLNHWO", "oasis net heat flux water",       "W/m**2"   },
  { 271, -1, 0, "AOFLSHWO", "oasis downward short wave",       "W/m**2"   },
  { 272, -1, 0, "AOFLRHIO", "oasis residual heat flux ice",    "W/m**2"   },
  { 273, -1, 0, "AOFLCHIO", "oasis conduct. heat flux ice",    "W/m**2"   },
  { 274, -1, 0, "AOFLFRWO", "oasis fluid fresh water flux",    "m/s"      },
  { 275, -1, 0, "AOFLFRIO", "oasis solid fresh water flux",    "m/s"      },
  { 276, -1, 0, "AOFLTXWO", "oasis wind stress water x",       "Pa/102"   },
  { 277, -1, 0, "AOFLTYWO", "oasis wind stress water y",       "Pa/102"   },
  { 278, -1, 0, "AOFLTXIO", "oasis wind stress ice x",         "Pa/102"   },
  { 279, -1, 0, "AOFLTYIO", "oasis wind stress ice x",         "Pa/102"   },
  { 280, -1, 0, "AOFLWSVO", "oasis wind speed",                "m/s"      },
};

static const param_type ecmwf[] = {
  {   1, -1, 0, "STRF",     "Stream function",                                            "m**2 s**-1"            },
  {   2, -1, 0, "VPOT",     "Velocity potential",                                         "m**2 s**-1"            },
  {   3, -1, 0, "PT",       "Potential temperature",                                      "K"                     },
  {   4, -1, 0, "EQPT",     "Equivalent potential temperature",                           "K"                     },
  {   5, -1, 0, "SEPT",     "Saturated equivalent potential temperature",                 "K"                     },
  {  11, -1, 0, "UDVW",     "U component of divergent wind",                              "m s**-1"               },
  {  12, -1, 0, "VDVW",     "V component of divergent wind",                              "m s**-1"               },
  {  13, -1, 0, "URTW",     "U component of rotational wind",                             "m s**-1"               },
  {  14, -1, 0, "VRTW",     "V component of rotational wind",                             "m s**-1"               },
  {  21, -1, 0, "UCTP",     "Unbalanced component of temperature",                        "K"                     },
  {  22, -1, 0, "UCLN",     "Unbalanced component of logarithm of surface pressure",       NULL                   },
  {  23, -1, 0, "UCDV",     "Unbalanced component of divergence",                         "s**-1"                 },
  {  26, -1, 0, "CL",       "Lake cover",                                                  NULL                   },
  {  27, -1, 0, "CVL",      "Low vegetation cover",                                        NULL                   },
  {  28, -1, 0, "CVH",      "High vegetation cover",                                       NULL                   },
  {  29, -1, 0, "TVL",      "Type of low vegetation",                                      NULL                   },
  {  30, -1, 0, "TVH",      "Type of high vegetation",                                     NULL                   },
  {  31, -1, 0, "CI",       "Sea-ice cover",                                               NULL                   },
  {  32, -1, 0, "ASN",      "Snow albedo",                                                 NULL                   },
  {  33, -1, 0, "RSN",      "Snow density kg",                                            "m**-3"                 },
  {  34, -1, 0, "SSTK",     "Sea surface temperature",                                    "K"                     },
  {  35, -1, 0, "ISTL1",    "Ice surface temperature layer 1",                            "K"                     },
  {  36, -1, 0, "ISTL2",    "Ice surface temperature layer 2",                            "K"                     },
  {  37, -1, 0, "ISTL3",    "Ice surface temperature layer 3",                            "K"                     },
  {  38, -1, 0, "ISTL4",    "Ice surface temperature layer 4",                            "K"                     },
  {  39, -1, 0, "SWVL1",    "Volumetric soil water layer 1",                              "m**3 m**-3"            },
  {  40, -1, 0, "SWVL2",    "Volumetric soil water layer 2",                              "m**3 m**-3"            },
  {  41, -1, 0, "SWVL3",    "Volumetric soil water layer 3",                              "m**3 m**-3"            },
  {  42, -1, 0, "SWVL4",    "Volumetric soil water layer 4",                              "m**3 m**-3"            },
  {  43, -1, 0, "SLT",      "Soil type",                                                   NULL                   },
  {  44, -1, 0, "ES",       "Snow evaporation m of water",                                 NULL                   },
  {  45, -1, 0, "SMLT",     "Snowmelt m of water",                                         NULL                   },
  {  46, -1, 0, "SDUR",     "Solar duration",                                             "s"                     },
  {  47, -1, 0, "DSRP",     "Direct solar radiation",                                     "w m**-2"               },
  {  48, -1, 0, "MAGSS",    "Magnitude of surface stress",                                "N m**-2 s"             },
  {  49, -1, 0, "WG10",     "Wind gust at 10 metres",                                     "m s**-1"               },
  {  50, -1, 0, "LSPF",     "Large-scale precipitation fraction",                         "s"                     },
  {  51, -1, 0, "MX2T24",   "Maximum 2 metre temperature",                                "K"                     },
  {  52, -1, 0, "MN2T24",   "Minimum 2 metre temperature",                                "K"                     },
  {  53, -1, 0, "MONT",     "Montgomery potential",                                       "m**2 s**-2"            },
  {  54, -1, 0, "PRES",     "Pressure",                                                   "Pa"                    },
  {  55, -1, 0, "MEAN2T24", "Mean 2 metre temperature past 24 hours",                     "K"                     },
  {  56, -1, 0, "MEAN2D24", "Mean 2 metre dewpoint temperature past 24 hours",            "K"                     },
  {  60, -1, 0, "PV",       "Potential vorticity",                                        "K m**2 kg**-1 s**-1"   },
  { 127, -1, 0, "AT",       "Atmospheric tide",                                            NULL                   },
  { 128, -1, 0, "BV",       "Budget values",                                               NULL                   },
  { 129, -1, 0, "Z",        "Geopotential",                                               "m**2 s**-2"            },
  { 130, -1, 0, "T",        "Temperature",                                                "K"                     },
  { 131, -1, 0, "U",        "U velocity",                                                 "m s**-1"               },
  { 132, -1, 0, "V",        "V velocity",                                                 "m s**-1"               },
  { 133, -1, 0, "Q",        "Specific humidity",                                          "kg kg**-1"             },
  { 134, -1, 0, "SP",       "Surface pressure",                                           "Pa"                    },
  { 135, -1, 0, "W",        "Vertical velocity",                                          "Pa s**-1"              },
  { 136, -1, 0, "TCW",      "Total column water",                                         "kg m**-2"              },
  { 137, -1, 0, "TCWV",     "Total column water vapour",                                  "kg m**-2"              },
  { 138, -1, 0, "VO",       "Vorticity (relative)",                                       "s**-1"                 },
  { 139, -1, 0, "STL1",     "Soil temperature level 1",                                   "K"                     },
  { 140, -1, 0, "SWL1",     "Soil wetness level 1 m of water",                             NULL                   },
  { 141, -1, 0, "SD",       "Snow depth         1 m of water equivalent",                  NULL                   },
  { 142, -1, 0, "LSP",      "Stratiform precipitation (Large scale precipitation)",       "m"                     },
  { 143, -1, 0, "CP",       "Convective precipitation",                                   "m"                     },
  { 144, -1, 0, "SF",       "Snowfall (convective + stratiform)",                         "m"                     },
  { 145, -1, 0, "BLD",      "Boundary layer dissipation",                                 "W m**-2 s"             },
  { 146, -1, 0, "SSHF",     "Surface sensible heat flux",                                 "W m**-2 s"             },
  { 147, -1, 0, "SLHF",     "Surface latent heat flux",                                   "W m**-2 s"             },
  { 148, -1, 0, "CHNK",     "Charnock",                                                    NULL                   },
  { 149, -1, 0, "SNR",      "Surface net radiation",                                      "W m**-2 s"             },
  { 150, -1, 0, "TNR",      "Top net radiation",                                           NULL                   },
  { 151, -1, 0, "MSL",      "Mean sea-level pressure",                                    "Pa"                    },
  { 152, -1, 0, "LNSP",     "Logarithm of surface pressure",                               NULL                   },
  { 153, -1, 0, "SWHR",     "Short-wave heating rate",                                    "K"                     },
  { 154, -1, 0, "LWHR",     "Long-wave heating rate",                                     "K"                     },
  { 155, -1, 0, "D",        "Divergence",                                                 "s**-1"                 },
  { 156, -1, 0, "GH",       "Height m Geopotential height",                                NULL                   },
  { 157, -1, 0, "R",        "Relative humidity",                                          "%"                     },
  { 158, -1, 0, "TSP",      "Tendency of surface pressure",                               "Pa s**-1"              },
  { 159, -1, 0, "BLH",      "Boundary layer height",                                      "m"                     },
  { 160, -1, 0, "SDOR",     "Standard deviation of orography",                             NULL                   },
  { 161, -1, 0, "ISOR",     "Anisotropy of sub-gridscale orography",                       NULL                   },
  { 162, -1, 0, "ANOR",     "Angle of sub-gridscale orography",                           "rad"                   },
  { 163, -1, 0, "SLOR",     "Slope of sub-gridscale orography",                            NULL                   },
  { 164, -1, 0, "TCC",      "Total cloud cover",                                           NULL                   },
  { 165, -1, 0, "U10M",     "10 metre U wind component",                                  "m s**-1"               },
  { 166, -1, 0, "V10M",     "10 metre V wind component",                                  "m s**-1"               },
  { 167, -1, 0, "T2M",      "2 metre temperature",                                        "K"                     },
  { 168, -1, 0, "D2M",      "2 metre dewpoint temperature",                               "K"                     },
  { 169, -1, 0, "SSRD",     "Surface solar radiation downwards",                          "W m**-2 s"             },
  { 170, -1, 0, "STL2",     "Soil temperature level 2",                                   "K"                     },
  { 171, -1, 0, "SWL2",     "Soil wetness level 2",                                       "m of water"            },
  { 172, -1, 0, "LSM",      "Land/sea mask",                                               NULL                   },
  { 173, -1, 0, "SR",       "Surface roughness",                                          "m"                     },
  { 174, -1, 0, "AL",       "Albedo",                                                      NULL                   },
  { 175, -1, 0, "STRD",     "Surface thermal radiation downwards",                        "W m**-2 s"             },
  { 176, -1, 0, "SSR",      "Surface solar radiation",                                    "W m**-2 s"             },
  { 177, -1, 0, "STR",      "Surface thermal radiation",                                  "W m**-2 s"             },
  { 178, -1, 0, "TSR",      "Top solar radiation",                                        "W m**-2 s"             },
  { 179, -1, 0, "TTR",      "Top thermal radiation",                                      "W m**-2 s"             },
  { 180, -1, 0, "EWSS",     "East/West surface stress",                                   "N m**-2 s"             },
  { 181, -1, 0, "NSSS",     "North/South surface stress",                                 "N m**-2 s"             },
  { 182, -1, 0, "E",        "Evaporation",                                                "m of water"            },
  { 183, -1, 0, "STL3",     "Soil temperature level 3",                                   "K"                     },
  { 184, -1, 0, "SWL3",     "Soil wetness level 3",                                       "m of water"            },
  { 185, -1, 0, "CCC",      "Convective cloud cover",                                      NULL                   },
  { 186, -1, 0, "LCC",      "Low cloud cover",                                             NULL                   },
  { 187, -1, 0, "MCC",      "Medium cloud cover",                                          NULL                   },
  { 188, -1, 0, "HCC",      "High cloud cover",                                            NULL                   },
  { 189, -1, 0, "SUND",     "Sunshine duration",                                          "s"                     },
  { 190, -1, 0, "EWOV",     "EW component of subgrid orographic variance",                "m**2"                  },
  { 191, -1, 0, "NSOV",     "NS component of subgrid orographic variance",                "m**2"                  },
  { 192, -1, 0, "NWOV",     "NWSE component of subgrid orographic variance",              "m**2"                  },
  { 193, -1, 0, "NEOV",     "NESW component of subgrid orographic variance",              "m**2"                  },
  { 194, -1, 0, "BTMP",     "Brightness temperature",                                     "K"                     },
  { 195, -1, 0, "LGWS",     "Lat. component of gravity wave stress",                      "N m**-2 s"             },
  { 196, -1, 0, "MGWS",     "Meridional component of gravity wave stress",                "N m**-2 s"             },
  { 197, -1, 0, "GWD",      "Gravity wave dissipation",                                   "W m**-2 s"             },
  { 198, -1, 0, "SRC",      "Skin reservoir content",                                     "m of water"            },
  { 199, -1, 0, "VEG",      "Vegetation fraction",                                         NULL                   },
  { 200, -1, 0, "VSO",      "Variance of sub-gridscale orography",                        "m**2"                  },
  { 201, -1, 0, "MX2T",     "Maximum 2 metre temperature since previous post-processing", "K"                     },
  { 202, -1, 0, "MN2T",     "Minimum 2 metre temperature since previous post-processing", "K"                     },
  { 203, -1, 0, "O3",       "Ozone mass mixing ratio",                                    "kg kg**-1"             },
  { 204, -1, 0, "PAW",      "Precipiation analysis weights",                               NULL                   },
  { 205, -1, 0, "RO",       "Runoff",                                                     "m"                     },
  { 206, -1, 0, "TCO3",     "Total column ozone",                                         "kg m**-2"              },
  { 207, -1, 0, "WS10",     "10 meter windspeed",                                         "m s**-1"               },
  { 208, -1, 0, "TSRC",     "Top net solar radiation, clear sky",                         "W m**-2"               },
  { 209, -1, 0, "TTRC",     "Top net thermal radiation, clear sky",                       "W m**-2"               },
  { 210, -1, 0, "SSRC",     "Surface net solar radiation, clear sky",                     "W m**-2"               },
  { 211, -1, 0, "STRC",     "Surface net thermal radiation, clear sky",                   "W m**-2"               },
  { 212, -1, 0, "SI",       "Solar insolation",                                           "W m**-2"               },
  { 214, -1, 0, "DHR",      "Diabatic heating by radiation",                              "K"                     },
  { 215, -1, 0, "DHVD",     "Diabatic heating by vertical diffusion",                     "K"                     },
  { 216, -1, 0, "DHCC",     "Diabatic heating by cumulus convection",                     "K"                     },
  { 217, -1, 0, "DHLC",     "Diabatic heating large-scale condensation",                  "K"                     },
  { 218, -1, 0, "VDZW",     "Vertical diffusion of zonal wind",                           "m s**-1"               },
  { 219, -1, 0, "VDMW",     "Vertical diffusion of meridional wind",                      "m s**-1"               },
  { 220, -1, 0, "EWGD",     "EW gravity wave drag tendency",                              "m s**-1"               },
  { 221, -1, 0, "NSGD",     "NS gravity wave drag tendency",                              "m s**-1"               },
  { 222, -1, 0, "CTZW",     "Convective tendency of zonal wind",                          "m s**-1"               },
  { 223, -1, 0, "CTMW",     "Convective tendency of meridional wind",                     "m s**-1"               },
  { 224, -1, 0, "VDH",      "Vertical diffusion of humidity",                             "kg kg**-1"             },
  { 225, -1, 0, "HTCC",     "Humidity tendency by cumulus convection",                    "kg kg**-1"             },
  { 226, -1, 0, "HTLC",     "Humidity tendency large-scale condensation",                 "kg kg**-1"             },
  { 227, -1, 0, "CRNH",     "Change from removing negative humidity",                     "kg kg**-1"             },
  { 228, -1, 0, "TP",       "Total precipitation",                                        "m"                     },
  { 229, -1, 0, "IEWS",     "Instantaneous X surface stress",                             "N m**-2"               },
  { 230, -1, 0, "INSS",     "Instantaneous Y surface stress",                             "N m**-2"               },
  { 231, -1, 0, "ISHF",     "Instantaneous surface heat flux",                            "W m**-2"               },
  { 232, -1, 0, "IE",       "Instantaneous moisture flux",                                "kg m**-2 s"            },
  { 233, -1, 0, "ASQ",      "Apparent surface humidity",                                  "kg kg**-1"             },
  { 234, -1, 0, "LSRH",     "Logarithm of surface roughness length for heat",              NULL                   },
  { 235, -1, 0, "SKT",      "Skin temperature",                                           "K"                     },
  { 236, -1, 0, "STL4",     "Soil temperature level 4",                                   "K"                     },
  { 237, -1, 0, "SWL4",     "Soil wetness level 4",                                       "m"                     },
  { 238, -1, 0, "TSN",      "Temperature of snow layer",                                  "K"                     },
  { 239, -1, 0, "CSF",      "Convective snowfall",                                        "m of water equivalent" },
  { 240, -1, 0, "LSF",      "Large-scale snowfall",                                       "m of water equivalent" },
  { 241, -1, 0, "ACF",      "Accumulated cloud fraction tendency",                         NULL                   },
  { 242, -1, 0, "ALW",      "Accumulated liquid water tendency",                           NULL                   },
  { 243, -1, 0, "FAL",      "Forecast albedo",                                             NULL                   },
  { 244, -1, 0, "FSR",      "Forecast surface roughness",                                 "m"                     },
  { 245, -1, 0, "FLSR",     "Forecast log of surface roughness for heat",                  NULL                   },
  { 246, -1, 0, "CLWC",     "Cloud liquid water content",                                 "kg kg**-1"             },
  { 247, -1, 0, "CIWC",     "Cloud ice water content",                                    "kg kg**-1"             },
  { 248, -1, 0, "CC",       "Cloud cover",                                                 NULL                   },
  { 249, -1, 0, "AIW",      "Accumulated ice water tendency",                              NULL                   },
  { 250, -1, 0, "ICE",      "Ice age",                                                     NULL                   },
  { 251, -1, 0, "ATTE",     "Adiabatic tendency of temperature",                          "K"                     },
  { 252, -1, 0, "ATHE",     "Adiabatic tendency of humidity",                             "kg kg**-1"             },
  { 253, -1, 0, "ATZE",     "Adiabatic tendency of zonal wind",                           "m s**-1"               },
  { 254, -1, 0, "ATMW",     "Adiabatic tendency of meridional wind",                      "m s**-1"               },
};

static const param_type remo[] = {
  {  14, -1, 0, "FTKVM",     "turbulent transfer coefficient of momentum in the atmosphere",   NULL           },
  {  15, -1, 0, "FTKVH",     "turbulent transfer coefficient of heat in the atmosphere",       NULL           },
  {  38, -1, 0, "U10ER",     "10m u-velocity",                                                "m/s"           },
  {  39, -1, 0, "V10ER",     "10m v-velocity",                                                "m/s"           },
  {  40, -1, 0, "CAPE",      "convetive available potential energy",                           NULL           },
  {  41, -1, 0, "GHPBL",     "height of the planetary boudary layer",                         "gpm"           },
  {  42, -1, 0, "BETA",      "BETA",                                                           NULL           },
  {  43, -1, 0, "WMINLOK",   "WMINLOK",                                                        NULL           },
  {  44, -1, 0, "WMAXLOK",   "WMAXLOK",                                                        NULL           },
  {  45, -1, 0, "VBM10M",    "maximum of the expected gust velocity near the surface",        "m/s"           },
  {  46, -1, 0, "BFLHS",     "surface sensible heat flux",                                    "W/m**2"        },
  {  47, -1, 0, "BFLQDS",    "surface latent heat flux",                                      "W/m**2"        },
  {  48, -1, 0, "TMCM",      "turbulent transfer coefficient of momentum at the surface",      NULL           },
  {  49, -1, 0, "TRSOL",     "TRSOL",                                                          NULL           },
  {  50, -1, 0, "TMCH",      "turbulent transfer coefficient of heat at the surface",          NULL           },
  {  51, -1, 0, "EMTEF",     "EMTEF",                                                          NULL           },
  {  52, -1, 0, "TRSOF",     "TRSOF",                                                          NULL           },
  {  53, -1, 0, "DRAIN",     "drainage",                                                      "mm"            },
  {  54, -1, 0, "TSL",       "surface temperature (land)",                                    "K"             },
  {  55, -1, 0, "TSW",       "surface temperature (water)",                                   "K"             },
  {  56, -1, 0, "TSI",       "surface temperature (ice)",                                     "K"             },
  {  57, -1, 0, "USTRL",     "surface u-stress (land)",                                       "Pa"            },
  {  58, -1, 0, "USTRW",     "surface u-stress (water)",                                      "Pa"            },
  {  59, -1, 0, "USTRI",     "surface u-stress (ice)",                                        "Pa"            },
  {  60, -1, 0, "VSTRL",     "surface v-stress (land)",                                       "Pa"            },
  {  61, -1, 0, "VSTRW",     "surface v-stress (water)",                                      "Pa"            },
  {  62, -1, 0, "VSTRI",     "surface v-stress (ice)",                                        "Pa"            },
  {  63, -1, 0, "EVAPL",     "surface evaporation (land)",                                    "mm"            },
  {  64, -1, 0, "EVAPW",     "surface evaporation (water)",                                   "mm"            },
  {  65, -1, 0, "EVAPI",     "surface evaporation (ice)",                                     "mm"            },
  {  66, -1, 0, "AHFLL",     "surface latent heat flux (land)",                               "W/m**2"        },
  {  67, -1, 0, "AHFLW",     "surface latent heat flux (water)",                              "W/m**2"        },
  {  68, -1, 0, "AHFLI",     "surface latent heat flux (ice)",                                "W/m**2"        },
  {  69, -1, 0, "AHFSL",     "surface sensible heat flux (land)",                             "W/m**2"        },
  {  70, -1, 0, "AHFSW",     "surface sensible heat flux (water)",                            "W/m**2"        },
  {  71, -1, 0, "AHFSI",     "surface sensible heat flux (ice)",                              "W/m**2"        },
  {  72, -1, 0, "AZ0L",      "surface roughness length (land)",                               "m"             },
  {  73, -1, 0, "AZ0W",      "surface roughness length (water)",                              "m"             },
  {  74, -1, 0, "AZ0I",      "surface roughness length (ice)",                                "m"             },
  {  75, -1, 0, "ALSOL",     "surface albedo (land)",                                         "fract."        },
  {  76, -1, 0, "ALSOW",     "surface albedo (water)",                                        "fract."        },
  {  77, -1, 0, "ALSOI",     "surface albedo (ice)",                                          "fract."        },
  {  81, -1, 0, "TMCHL",     "turbulent transfer coefficient of heat at the surface (land)",   NULL           },
  {  82, -1, 0, "TMCHW",     "turbulent transfer coefficient of heat at the surface (water)",  NULL           },
  {  83, -1, 0, "TMCHI",     "turbulent transfer coefficient of heat at the surface (ice)",    NULL           },
  {  84, -1, 0, "QDBL",      "specific humidity surface (land)",                              "kg/kg"         },
  {  85, -1, 0, "QDBW",      "specific humidity surface (water)",                             "kg/kg"         },
  {  86, -1, 0, "QDBI",      "specific humidity surface (ice)",                               "kg/kg"         },
  {  87, -1, 0, "BFLHSL",    "surface sensible heat flux (land)",                             "W/m**2"        },
  {  88, -1, 0, "BFLHSW",    "surface sensible heat flux (water)",                            "W/m**2"        },
  {  89, -1, 0, "BFLHSI",    "surface sensible heat flux (ice)",                              "W/m**2"        },
  {  90, -1, 0, "BFLQDSL",   "surface latent heat flux (land)",                               "W/m**2"        },
  {  91, -1, 0, "BFLQDSW",   "surface latent heat flux (water)",                              "W/m**2"        },
  {  92, -1, 0, "BFLQDSI",   "surface latent heat flux (ice)",                                "W/m**2"        },
  {  93, -1, 0, "AHFICE",    "sea-ice: conductive heat",                                      "W/m"           },
  {  94, -1, 0, "QRES",      "residual heat flux for melting sea ice",                        "W/m**2"        },
  {  95, -1, 0, "SRFL",      "SRFL",                                                           NULL           },
  {  96, -1, 0, "QDBOXS",    "horizontal transport of water vapour",                          "kg/m**2"       },
  {  97, -1, 0, "QWBOXS",    "horizontal transport of cloud water",                           "kg/m**2"       },
  {  98, -1, 0, "EKBOXS",    "horizontal transport of kinetic energy",                        "(3600*J)/m**2" },
  {  99, -1, 0, "FHBOXS",    "horizontal transport of sensible heat",                         "(3600*J)/m**2" },
  { 100, -1, 0, "FIBOXS",    "horizontal transport of potential energy",                      "(3600*J)/m**2" },
  { 101, -1, 0, "TLAMBDA",   "heat conductivity of dry soil",                                 "W/(K*m)"       },
  { 103, -1, 0, "DLAMBDA",   "parameter for increasing the heat conductivity of the soil",     NULL           },
  { 104, -1, 0, "PORVOL",    "pore volume",                                                    NULL           },
  { 105, -1, 0, "FCAP",      "field capacity of soil",                                         NULL           },
  { 106, -1, 0, "WI3",       "fraction of frozen soil",                                        NULL           },
  { 107, -1, 0, "WI4",       "fraction of frozen soil",                                        NULL           },
  { 108, -1, 0, "WI5",       "fraction of frozen soil",                                        NULL           },
  { 109, -1, 0, "WI",        "fraction of frozen soil",                                        NULL           },
  { 110, -1, 0, "WICL",      "fraction of frozen soil",                                        NULL           },
  { 112, -1, 0, "QDB",       "specific humidity surface",                                     "kg/kg"         },
  { 129, -1, 0, "FIB",       "surface geopotential (orography)",                              "m"             },
  { 130, -1, 0, "T",         "temperature",                                                   "K"             },
  { 131, -1, 0, "U",         "u-velocity",                                                    "m/s"           },
  { 132, -1, 0, "V",         "v-velocity",                                                    "m/s"           },
  { 133, -1, 0, "QD",        "specific humidity",                                             "kg/kg"         },
  { 134, -1, 0, "PS",        "Surface pressure",                                              "Pa"            },
  { 135, -1, 0, "VERVEL",    "Vertical velocity",                                             "Pa/s"          },
  { 138, -1, 0, "SVO",       "vorticity",                                                     "1/s"           },
  { 139, -1, 0, "TS",        "surface temperature",                                           "K"             },
  { 140, -1, 0, "WS",        "soil wetness",                                                  "m"             },
  { 141, -1, 0, "SN",        "snow depth",                                                    "m"             },
  { 142, -1, 0, "APRL",      "large scale precipitation",                                     "mm"            },
  { 143, -1, 0, "APRC",      "convective  precipitation",                                     "mm"            },
  { 144, -1, 0, "APRS",      "snow fall",                                                     "mm"            },
  { 145, -1, 0, "VDIS",      "boundary layer dissipation",                                    "W/m**2"        },
  { 146, -1, 0, "AHFS",      "surface sensible heat flux",                                    "W/m**2"        },
  { 147, -1, 0, "AHFL",      "surface latent heat flux",                                      "W/m**2"        },
  { 148, -1, 0, "STREAM",    "streamfunction",                                                "m**2/s"        },
  { 149, -1, 0, "VELOPOT",   "velocity potential",                                            "m**2/s"        },
  { 151, -1, 0, "PSRED",     "mean sea level pressure",                                       "Pa"            },
  { 152, -1, 0, "LSP",       "log surface pressure",                                           NULL           },
  { 153, -1, 0, "QW",        "liquid water content",                                          "kg/kg"         },
  { 155, -1, 0, "SD",        "divergence",                                                    "1/s"           },
  { 156, -1, 0, "FI",        "geopotential height",                                           "gpm"           },
  { 159, -1, 0, "USTAR3",    "ustar**3",                                                      "m**3/s**3"     },
  { 160, -1, 0, "RUNOFF",    "surface runoff",                                                "mm"            },
  { 162, -1, 0, "ACLC",      "cloud cover",                                                   "fract."        },
  { 163, -1, 0, "ACLCV",     "total cloud cover",                                             "fract."        },
  { 164, -1, 0, "ACLCOV",    "total cloud cover",                                             "fract."        },
  { 165, -1, 0, "U10",       "10m u-velocity",                                                "m/s"           },
  { 166, -1, 0, "V10",       "10m v-velocity",                                                "m/s"           },
  { 167, -1, 0, "TEMP2",     "2m temperature",                                                "K"             },
  { 168, -1, 0, "DEW2",      "2m dew point temperature",                                      "K"             },
  { 169, -1, 0, "TSURF",     "surface temperature (land)",                                    "K"             },
  { 170, -1, 0, "TD",        "deep soil temperature",                                         "K"             },
  { 171, -1, 0, "WIND10",    "10m windspeed",                                                 "m/s"           },
  { 172, -1, 0, "BLA",       "land sea mask",                                                 "fract."        },
  { 173, -1, 0, "AZ0",       "surface roughness length",                                      "m"             },
  { 174, -1, 0, "ALB",       "surface background albedo",                                     "fract."        },
  { 175, -1, 0, "ALBEDO",    "surface albedo",                                                "fract."        },
  { 176, -1, 0, "SRADS",     "net surface solar radiation",                                   "W/m**2"        },
  { 177, -1, 0, "TRADS",     "net surface thermal radiation",                                 "W/m**2"        },
  { 178, -1, 0, "SRAD0",     "net top solar radiation",                                       "W/m**2"        },
  { 179, -1, 0, "TRAD0",     "top thermal radiation (OLR)",                                   "W/m**2"        },
  { 180, -1, 0, "USTR",      "surface u-stress",                                              "Pa"            },
  { 181, -1, 0, "VSTR",      "surface v-stress",                                              "Pa"            },
  { 182, -1, 0, "EVAP",      "surface evaporation",                                           "mm"            },
  { 183, -1, 0, "TDCL",      "soil temperature",                                              "K"             },
  { 185, -1, 0, "SRAFS",     "net surf. solar radiation   (clear sky)",                       "W/m**2"        },
  { 186, -1, 0, "TRAFS",     "net surf. thermal radiation (clear sky)",                       "W/m**2"        },
  { 187, -1, 0, "SRAF0",     "net top solar radiation     (clear sky)",                       "W/m**2"        },
  { 188, -1, 0, "TRAF0",     "net top thermal radiation   (clear sky)",                       "W/m**2"        },
  { 189, -1, 0, "SCLFS",     "surface solar cloud forcing",                                   "W/m**2"        },
  { 190, -1, 0, "TCLFS",     "surface thermal cloud forcing",                                 "W/m**2"        },
  { 191, -1, 0, "SCLF0",     "top solar cloud forcing",                                       "W/m**2"        },
  { 192, -1, 0, "TCLF0",     "top thermal cloud forcing",                                     "W/m**2"        },
  { 194, -1, 0, "WL",        "skin reservoir content",                                        "m"             },
  { 195, -1, 0, "USTRGW",    "u-gravity wave stress",                                         "Pa"            },
  { 196, -1, 0, "VSTRGW",    "v-gravity wave stress",                                         "Pa"            },
  { 197, -1, 0, "VDISGW",    "gravity wave dissipation",                                      "W/m**2"        },
  { 198, -1, 0, "VGRAT",     "vegetation ratio",                                               NULL           },
  { 199, -1, 0, "VAROR",     "orographic variance (for surface runoff)",                       NULL           },
  { 200, -1, 0, "VLT",       "leaf area index",                                                NULL           },
  { 201, -1, 0, "T2MAX",     "maximum 2m-temperature",                                        "K"             },
  { 202, -1, 0, "T2MIN",     "minimum 2m-temperature",                                        "K"             },
  { 203, -1, 0, "SRAD0U",    "top solar radiation upward",                                    "W/m**2"        },
  { 204, -1, 0, "SRADSU",    "surface solar radiation upward",                                "W/m**2"        },
  { 205, -1, 0, "TRADSU",    "surface thermal radiation upward",                              "W/m**2"        },
  { 206, -1, 0, "TSN",       "snow temperature",                                              "K"             },
  { 207, -1, 0, "TD3",       "soil temperature",                                              "K"             },
  { 208, -1, 0, "TD4",       "soil temperature",                                              "K"             },
  { 209, -1, 0, "TD5",       "soil temperature",                                              "K"             },
  { 210, -1, 0, "SEAICE",    "sea ice cover",                                                 "fract."        },
  { 211, -1, 0, "SICED",     "sea ice depth",                                                 "m"             },
  { 212, -1, 0, "FOREST",    "vegetation type",                                                NULL           },
  { 213, -1, 0, "TEFF",      "(effective) sea-ice skin temperature",                          "K"             },
  { 214, -1, 0, "TSMAX",     "maximum surface temperature",                                   "K"             },
  { 215, -1, 0, "TSMIN",     "minimum surface temperature",                                   "K"             },
  { 216, -1, 0, "WIMAX",     "maximum 10m-wind speed",                                        "m/s"           },
  { 217, -1, 0, "TOPMAX",    "maximum height of convective cloud tops",                       "Pa"            },
  { 218, -1, 0, "SNMEL",     "snow melt",                                                     "mm"            },
  { 220, -1, 0, "TSLIN",     "land: residual surface heat budget",                            "W/m**2"        },
  { 221, -1, 0, "DSNAC",     "snow depth change",                                             "mm"            },
  { 222, -1, 0, "EMTER",     "EMTER",                                                          NULL           },
  { 223, -1, 0, "ACLCAC",    "cloud cover",                                                   "fract."        },
  { 224, -1, 0, "TKE",       "turbulent kinetic energy",                                       NULL           },
  { 226, -1, 0, "FAO",       "FAO data set (soil data flags)",                                 NULL           },
  { 227, -1, 0, "RGCGN",     "heat capacity of soil",                                          NULL           },
  { 229, -1, 0, "WSMX",      "field capacity of soil",                                         NULL           },
  { 230, -1, 0, "QVI",       "vertically integrated specific humidity",                       "kg/m**2"       },
  { 231, -1, 0, "ALWCVI",    "vertically integrated liquid water cont.",                      "kg/m**2"       },
  { 232, -1, 0, "GLAC",      "glacier mask",                                                   NULL           },
  { 253, -1, 0, "PHI",       "latitude in real coordinates",                                  "degrees_north" },
  { 254, -1, 0, "RLA",       "longitude in real coordinates",                                 "degrees_east"  },
  { 259, -1, 0, "WINDSPEED", "windspeed (sqrt(u**2+v**2))",                                    NULL           },
  { 260, -1, 0, "PRECIP",    "total precipitation",                                            NULL           },
};

static const param_type cosmo002[] = {
  {   1, -1, 0, "P",         "pressure",                                          "Pa"         },
  {   2, -1, 0, "PMSL",      "mean sea level pressure",                           "Pa"         },
  {   3, -1, 0, "DPSDT",     "surface pressure change",                           "Pa s-1"     },
  {   6, -1, 0, "FI",        "geopotential",                                      "m2 s-2"     },
  {   8, -1, 0, "HH",        "height",                                            "m"          },
  {  10, -1, 0, "TO3",       "vertical integrated ozone content",                 "Dobson"     },
  {  11, -1, 0, "T",         "temperature",                                       "K"          },
  {  15, -1, 0, "TMAX",      "2m maximum temperature",                            "K"          },
  {  16, -1, 0, "TMIN",      "2m minimum temperature",                            "K"          },
  {  17, -1, 0, "TD",        "2m dew point temperature",                          "K"          },
  {  31, -1, 0, "DD",        "undefined",                                         "undefined"  },
  {  32, -1, 0, "FF",        "undefined",                                         "undefined"  },
  {  33, -1, 0, "U",         "U-component of wind",                               "m s-1"      },
  {  34, -1, 0, "V",         "V-component of wind",                               "m s-1"      },
  {  39, -1, 0, "OMEGA",     "omega",                                             "Pa s-1"     },
  {  40, -1, 0, "W",         "vertical wind velocity",                            "m s-1"      },
  {  51, -1, 0, "QV",        "specific humidity",                                 "kg kg-1"    },
  {  52, -1, 0, "RELHUM",    "relative humidity",                                 "%"          },
  {  54, -1, 0, "TQV",       "precipitable water",                                "kg m-2"     },
  {  57, -1, 0, "AEVAP",     "surface evaporation",                               "kg m-2"     },
  {  58, -1, 0, "TQI",       "vertical integrated cloud ice",                     "kg m-2"     },
  {  59, -1, 0, "TOT_PR",    "total precipitation rate",                          "kg m-2 s-1" },
  {  61, -1, 0, "TOT_PREC",  "total precipitation amount",                        "kg m-2"     },
  {  65, -1, 0, "W_SNOW",    "surface snow amount",                               "m"          },
  {  66, -1, 0, "H_SNOW",    "thickness of snow",                                 "m"          },
  {  71, -1, 0, "CLCT",      "total cloud cover",                                 "1"          },
  {  72, -1, 0, "CLC_CON",   "convective cloud area fraction",                    "1"          },
  {  73, -1, 0, "CLCL",      "low cloud cover",                                   "1"          },
  {  74, -1, 0, "CLCM",      "medium cloud cover",                                "1"          },
  {  75, -1, 0, "CLCH",      "high cloud cover",                                  "1"          },
  {  76, -1, 0, "TQC",       "vertical integrated cloud water",                   "kg m-2"     },
  {  78, -1, 0, "SNOW_CON",  "convective snowfall",                               "kg m-2"     },
  {  79, -1, 0, "SNOW_GSP",  "large scale snowfall",                              "kg m-2"     },
  {  81, -1, 0, "FR_LAND",   "land-sea fraction",                                 "1"          },
  {  83, -1, 0, "Z0",        "surface roughness length",                          "m"          },
  {  84, -1, 0, "ALB_RAD",   "surface albedo",                                    "1"          },
  {  85, -1, 0, "TSOIL",     "soil surface temperature",                          "K"          },
  {  86, -1, 0, "WSOIL",     "water content of 1. soil layer",                    "m"          },
  {  87, -1, 0, "PLCOV",     "vegetation area fraction",                          "1"          },
  {  90, -1, 0, "RUNOFF",    "subsurface runoff",                                 "kg m-2"     },
  {  91, -1, 0, "FR_ICE",    "sea ice area fraction",                             "1"          },
  {  92, -1, 0, "H_ICE",     "sea ice thickness",                                 "m"          },
  { 111, -1, 0, "ASOB",      "averaged surface net downward shortwave radiation", "W m-2"      },
  { 112, -1, 0, "ATHB",      "averaged surface net downward longwave radiation",  "W m-2"      },
  { 113, -1, 0, "ASOB",      "averaged TOA net downward shortwave radiation",     "W m-2"      },
  { 114, -1, 0, "ATHB",      "averaged TOA outgoing longwave radiation",          "W m-2"      },
  { 115, -1, 0, "ASWDIR",    "direct downward sw radiation at the surface",       "W m-2"      },
  { 116, -1, 0, "ASWDIFD",   "diffuse downward sw radiation at the surface",      "W m-2"      },
  { 117, -1, 0, "ASWDIFU",   "diffuse upwnward sw radiation at the surface",      "W m-2"      },
  { 118, -1, 0, "ALWD",      "downward lw radiation at the surface",              "W m-2"      },
  { 119, -1, 0, "ALWU",      "upward lw radiation at the surface",                "W m-2"      },
  { 121, -1, 0, "ALHFL",     "averaged surface latent heat flux",                 "W m-2"      },
  { 122, -1, 0, "ASHFL",     "averaged surface sensible heat flux",               "W m-2"      },
  { 124, -1, 0, "AUMFL",     "averaged eastward stress",                          "Pa"         },
  { 125, -1, 0, "AVMFL",     "averaged northward stress",                         "Pa"         },
  { 128, -1, 0, "SUNSH",     "undefined",                                         "undefined"  },
  { 129, -1, 0, "SUNSH2",    "undefined",                                         "undefined"  },
  { 130, -1, 0, "SUN_SUM",   "undefined",                                         "undefined"  },
  { 131, -1, 0, "SUN_SUM2",  "undefined",                                         "undefined"  },
  { 133, -1, 0, "FCOR",      "undefined",                                         "undefined"  },
  { 134, -1, 0, "SKYVIEW",   "sky-view factor",                                   "1"          },
  { 137, -1, 0, "SWDIR_COR", "topo correction of direct solar radiarion",         "1"          },
};

static const param_type cosmo201[] = {
  {   5, -1, 0, "APAB",      "&",                                                         "W m-2"      },
  {  13, -1, 0, "SOHR_RAD",  "&",                                                         "K s-1"      },
  {  14, -1, 0, "THHR_RAD",  "&",                                                         "K s-1"      },
  {  20, -1, 0, "DURSUN",    "duration of sunshine",                                      "s"          },
  {  29, -1, 0, "CLC",       "cloud area fraction",                                       "1"          },
  {  30, -1, 0, "CLC_SGS",   "grid scale cloud area fraction",                            "1"          },
  {  31, -1, 0, "QC",        "specific cloud liquid water content",                       "kg kg-1"    },
  {  33, -1, 0, "QI",        "specific cloud ice content",                                "kg kg-1"    },
  {  35, -1, 0, "QR",        "specific rain content",                                     "kg kg-1"    },
  {  36, -1, 0, "QS",        "specific snow content",                                     "kg kg-1"    },
  {  37, -1, 0, "TQR",       "total rain water content vertically integrated",            "kg m-2"     },
  {  38, -1, 0, "TQS",       "total snow content vertically integrated",                  "kg m-2"     },
  {  39, -1, 0, "QG",        "specific graupel content",                                  "kg kg-1"    },
  {  40, -1, 0, "TQG",       "total graupel content vertically integrated",               "kg m-2"     },
  {  41, -1, 0, "TWATER",    "cloud condensed water content",                             "kg m-2"     },
  {  42, -1, 0, "TDIV_HUM",  "atmosphere water divergence",                               "kg m-2"     },
  {  43, -1, 0, "QC_RAD",    "sub scale specific cloud liquid water content",             "kg kg-1"    },
  {  44, -1, 0, "QI_RAD",    "sub scale specific cloud ice content",                      "kg kg-1"    },
  {  61, -1, 0, "CLW_CON",   "convective cloud liquid water",                             "1"          },
  {  68, -1, 0, "HBAS_CON",  "height of convective cloud base",                           "m"          },
  {  69, -1, 0, "HTOP_CON",  "height of convective cloud top",                            "m"          },
  {  70, -1, 0, "HBAS_CONI", "height of convective cloud base",                           "m"          },
  {  71, -1, 0, "HTOP_CONI", "height of convective cloud top",                            "m"          },
  {  72, -1, 0, "BAS_CON",   "index of convective cloud base",                            "1"          },
  {  73, -1, 0, "TOP_CON",   "index of convective cloud top",                             "1"          },
  {  74, -1, 0, "DT_CON",    "convective tendency of temperature",                        "K s-1"      },
  {  75, -1, 0, "DQV_CON",   "convective tendency of specific humidity",                  "s-1"        },
  {  78, -1, 0, "DU_CON",    "convective tendency of u-wind component",                   "m s-2"      },
  {  79, -1, 0, "DV_CON",    "convective tendency of v-wind component",                   "m s-2"      },
  {  82, -1, 0, "HTOP_DC",   "height of dry convection top",                              "m"          },
  {  84, -1, 0, "HZEROCL",   "height of freezing level",                                  "m"          },
  {  85, -1, 0, "SNOWLMT",   "height of the snow fall limit in m above sea level",        "m"          },
  {  86, -1, 0, "HCBAS",     "height of cloud base",                                      "m"          },
  {  87, -1, 0, "HCTOP",     "height of cloud top",                                       "m"          },
  {  91, -1, 0, "C_T_LK",    "&",                                                         "1"          },
  {  92, -1, 0, "GAMSO_LK",  "&",                                                         "m-1"        },
  {  93, -1, 0, "DP_BS_LK",  "&",                                                         "m"          },
  {  94, -1, 0, "H_B1_LK",   "&",                                                         "m"          },
  {  95, -1, 0, "H_ML_LK",   "&",                                                         "m"          },
  {  96, -1, 0, "DEPTH_LK",  "lake depth",                                                "m"          },
  {  97, -1, 0, "FETCH_LK",  "wind fetch over lake",                                      "m"          },
  {  99, -1, 0, "QRS",       "precipitation water (water loading)",                       "1"          },
  { 100, -1, 0, "PRR_GSP",   "mass flux density of large scale rainfall",                 "kg m-2 s-1" },
  { 101, -1, 0, "PRS_GSP",   "mass flux density of large scale snowfall",                 "kg m-2 s-1" },
  { 102, -1, 0, "RAIN_GSP",  "large scale rainfall",                                      "kg m-2"     },
  { 111, -1, 0, "PRR_CON",   "mass flux density of convective rainfall",                  "kg m-2 s-1" },
  { 112, -1, 0, "PRS_CON",   "mass flux density of convective snowfall",                  "kg m-2 s-1" },
  { 113, -1, 0, "RAIN_CON",  "convective rainfall",                                       "kg m-2"     },
  { 129, -1, 0, "FRESHSNW",  "freshness of snow",                                         "undefined"  },
  { 131, -1, 0, "PRG_GSP",   "mass flux density of large scale graupel",                  "kg m-2 s-1" },
  { 132, -1, 0, "GRAU_GSP",  "large scale graupel",                                       "kg m-2"     },
  { 133, -1, 0, "RHO_SNOW",  "density of snow",                                           "kg m-3"     },
  { 139, -1, 0, "PP",        "deviation from reference pressure",                         "Pa"         },
  { 140, -1, 0, "RCLD",      "standard deviation of saturation deficit",                  "undefined"  },
  { 143, -1, 0, "CAPE_MU",   "cape of most unstable parcel",                              "J kg-1"     },
  { 144, -1, 0, "CIN_MU",    "convective inhibition of most unstable parcel",             "J kg-1"     },
  { 145, -1, 0, "CAPE_ML",   "cape of mean surface layer parcel",                         "J kg-1"     },
  { 146, -1, 0, "CIN_ML",    "convective inhibition of mean surface layer parcel",        "J kg-1"     },
  { 147, -1, 0, "TKE_CON",   "convective turbulent kinetic energy",                       "undefined"  },
  { 148, -1, 0, "TKETENS",   "tendency of turbulent kinetic energy",                      "undefined"  },
  { 152, -1, 0, "TKE",       "turbulent kinetic energy",                                  "m2 s-2"     },
  { 153, -1, 0, "TKVM",      "diffusion coefficient of momentum",                         "m2 s-1"     },
  { 154, -1, 0, "TKVH",      "diffusion coefficient of heat",                             "m2 s-1"     },
  { 170, -1, 0, "TCM",       "drag coefficient of momentum",                              "1"          },
  { 171, -1, 0, "TCH",       "drag coefficient of heat",                                  "1"          },
  { 187, -1, 0, "VMAX",      "maximum turbulent wind gust in 10m",                        "m s-1"      },
  { 190, -1, 0, "TSOIL",     "&",                                                         "K"          },
  { 191, -1, 0, "TSOIL",     "&",                                                         "K"          },
  { 192, -1, 0, "TSOIL",     "&",                                                         "K"          },
  { 193, -1, 0, "TSOIL",     "mixed layer temperature",                                   "K"          },
  { 194, -1, 0, "TSOIL",     "mean temperature of water column",                          "K"          },
  { 197, -1, 0, "TSOIL",     "soil temperature",                                          "K"          },
  { 198, -1, 0, "W_SO",      "soil water content",                                        "m"          },
  { 199, -1, 0, "W_SO_ICE",  "soil frozen water content",                                 "m"          },
  { 200, -1, 0, "W_I",       "canopy water amount",                                       "m"          },
  { 203, -1, 0, "TSOIL",     "snow surface temperature",                                  "K"          },
  { 215, -1, 0, "TSOIL",     "temperature of ice upper surface",                          "K"          },
  { 230, -1, 0, "dBZ",       "unattenuated radar reflectivity in Rayleigh approximation", "1"          },
  { 240, -1, 0, "MFLX_CON",  "convective mass flux density",                              "kg m-2 s-1" },
  { 241, -1, 0, "CAPE_CON",  "&",                                                         "J kg-1"     },
  { 243, -1, 0, "QCVG_CON",  "&",                                                         "s-1"        },
};

static const param_type cosmo202[] = {
  {  46, -1, 0, "SSO_STDH",  "standard deviation of subgrid scale height",                "m"         },
  {  47, -1, 0, "SSO_GAMMA", "anisotropy of topography",                                  "-"         },
  {  48, -1, 0, "SSO_THETA", "angle between principal axis of orography and global east", "-"         },
  {  49, -1, 0, "SSO_SIGMA", "mean slope of subgrid scale orography",                     "-"         },
  {  55, -1, 0, "FR_LAKE",   "fraction of inland lake water",                             "1"         },
  {  57, -1, 0, "SOILTYP",   "soil type",                                                 "1"         },
  {  61, -1, 0, "LAI",       "leaf area index",                                           "1"         },
  {  62, -1, 0, "ROOTDP",    "root depth",                                                "m"         },
  {  64, -1, 0, "HMO3",      "air pressure at ozone maximum",                             "Pa"        },
  {  65, -1, 0, "VIO3",      "vertical integrated ozone amount",                          "Pa"        },
  {  67, -1, 0, "PLCOV_MX",  "vegetation area fraction maximum",                          "1"         },
  {  68, -1, 0, "PLCOV_MN",  "vegetation area fraction minimum",                          "1"         },
  {  69, -1, 0, "LAI_MX",    "leaf area index maximum",                                   "1"         },
  {  70, -1, 0, "LAI_MN",    "leaf area index minimum",                                   "1"         },
  {  75, -1, 0, "FOR_E",     "ground fraction covered by evergreen forest",               "-"         },
  {  76, -1, 0, "FOR_D",     "ground fraction covered by deciduous forest",               "-"         },
  { 104, -1, 0, "DQVDT",     "tendency of water vapor",                                   "s-1"       },
  { 105, -1, 0, "QVSFLX",    "surface flux of water vapour",                              "s-1m-2"    },
  { 113, -1, 0, "FC",        "coriolis parameter",                                        "s-1"       },
  { 114, -1, 0, "RLAT",      "latitude",                                                  "radian"    },
  { 115, -1, 0, "RLON",      "longitude",                                                 "radian"    },
  { 121, -1, 0, "ZTD",       "integrated total atmospheric refractivity",                 "undefined" },
  { 122, -1, 0, "ZWD",       "integrated wet atmospheric refractivity",                   "undefined" },
  { 123, -1, 0, "ZHD",       "integrated dry atmospheric refractivity",                   "undefined" },
  { 180, -1, 0, "O3",        "ozone mass mixing ratio",                                   "kg kg-1"   },
  { 200, -1, 0, "I131a",     "undefined",                                                 "undefined" },
  { 201, -1, 0, "I131a_DD",  "undefined",                                                 "undefined" },
  { 202, -1, 0, "I131a_WD",  "undefined",                                                 "undefined" },
  { 203, -1, 0, "Cs137",     "undefined",                                                 "undefined" },
  { 204, -1, 0, "Cs137_DD",  "undefined",                                                 "undefined" },
  { 205, -1, 0, "Cs137_WD",  "undefined",                                                 "undefined" },
  { 206, -1, 0, "Te132",     "undefined",                                                 "undefined" },
  { 207, -1, 0, "Te132_DD",  "undefined",                                                 "undefined" },
  { 208, -1, 0, "Te132_WD",  "undefined",                                                 "undefined" },
  { 209, -1, 0, "Zr95",      "undefined",                                                 "undefined" },
  { 210, -1, 0, "Zr95_DD",   "undefined",                                                 "undefined" },
  { 211, -1, 0, "Zr95_WD",   "undefined",                                                 "undefined" },
  { 212, -1, 0, "Kr85",      "undefined",                                                 "undefined" },
  { 213, -1, 0, "Kr85_DD",   "undefined",                                                 "undefined" },
  { 214, -1, 0, "Kr85_WD",   "undefined",                                                 "undefined" },
  { 215, -1, 0, "TRACER",    "undefined",                                                 "undefined" },
  { 216, -1, 0, "TRACER_DD", "undefined",                                                 "undefined" },
  { 217, -1, 0, "TRACER_WD", "undefined",                                                 "undefined" },
  { 218, -1, 0, "Xe133",     "undefined",                                                 "undefined" },
  { 219, -1, 0, "Xe133_DD",  "undefined",                                                 "undefined" },
  { 220, -1, 0, "Xe133_WD",  "undefined",                                                 "undefined" },
  { 221, -1, 0, "I131g",     "undefined",                                                 "undefined" },
  { 222, -1, 0, "I131g_DD",  "undefined",                                                 "undefined" },
  { 223, -1, 0, "I131g_WD",  "undefined",                                                 "undefined" },
  { 224, -1, 0, "I131o",     "undefined",                                                 "undefined" },
  { 225, -1, 0, "I131o_DD",  "undefined",                                                 "undefined" },
  { 226, -1, 0, "I131o_WD",  "undefined",                                                 "undefined" },
  { 227, -1, 0, "Ba140",     "undefined",                                                 "undefined" },
  { 228, -1, 0, "Ba140_DD",  "undefined",                                                 "undefined" },
  { 229, -1, 0, "Ba140_WD",  "undefined",                                                 "undefined" },
  { 230, -1, 0, "Sr90",      "undefined",                                                 "undefined" },
  { 231, -1, 0, "Sr90_DD",   "undefined",                                                 "undefined" },
  { 232, -1, 0, "Sr90_WD",   "undefined",                                                 "undefined" },
  { 233, -1, 0, "Ru103",     "undefined",                                                 "undefined" },
  { 234, -1, 0, "Ru103_DD",  "undefined",                                                 "undefined" },
  { 235, -1, 0, "Ru103_WD",  "undefined",                                                 "undefined" },
};

static const param_type cosmo203[] = {
  { 135, -1, 0, "LCL_ML",   "undefined",                  "undefined" },
  { 136, -1, 0, "LFC_ML",   "undefined",                  "undefined" },
  { 137, -1, 0, "CAPE_3KM", "undefined",                  "undefined" },
  { 138, -1, 0, "SWISS00",  "swiss00 index",              "1"         },
  { 139, -1, 0, "SWISS12",  "swiss12 index",              "1"         },
  { 147, -1, 0, "SLI",      "surface lifted index",       "K"         },
  { 149, -1, 0, "SI",       "showalter index",            "K"         },
  { 155, -1, 0, "BRN",      "undefined",                  "undefined" },
  { 156, -1, 0, "HPBL",     "undefined",                  "undefined" },
  { 203, -1, 0, "CLDEPTH",  "normalized cloud depth",     "1"         },
  { 204, -1, 0, "CLCT_MOD", "modified_total_cloud_cover", "1"         },
};

static const param_type cosmo205[] = {
  {   1, -1, 0, "SYNME5", "synthetic satellite images Meteosat5", "-" },
  {   2, -1, 0, "SYNME6", "synthetic satellite images Meteosat6", "-" },
  {   3, -1, 0, "SYNME7", "synthetic satellite images Meteosat7", "-" },
  {   4, -1, 0, "SYNMSG", "synthetic satellite images MSG",       "-" },
};

static const param_type cosmo250[] = {
  {   1, -1, 0, "QNH",       "sea level air pressure",                                         "hPa"                                },
  {  11, -1, 0, "TSOIL",     "2m temperature",                                                 "K"                                  },
  {  12, -1, 0, "TSOIL",     "2m temperature",                                                 "K"                                  },
  {  13, -1, 0, "D_T_2M_K",  "kalman correction to 2m temperature",                            "K"                                  },
  {  14, -1, 0, "TSOIL",     "2m temperature",                                                 "K"                                  },
  {  15, -1, 0, "TSOIL",     "2m temperature",                                                 "K"                                  },
  {  16, -1, 0, "RH_ICE",    "relative humidity over ice",                                     "%"                                  },
  {  17, -1, 0, "TD",        "dew point temperature",                                          "K"                                  },
  {  18, -1, 0, "D_TD",      "dew point depression",                                           "K"                                  },
  {  19, -1, 0, "THETAE",    "equivalent potential temperature",                               "K"                                  },
  {  20, -1, 0, "TD_2M_K",   "2m dew point temperature",                                       "K"                                  },
  {  21, -1, 0, "D_TD_2M_K", "kalman correction to 2m dew point temperature",                  "K"                                  },
  {  22, -1, 0, "TD_2M_OLD", "2m dew point temperature",                                       "K"                                  },
  {  23, -1, 0, "TD_2M_BUZ", "2m dew point temperature",                                       "K"                                  },
  {  24, -1, 0, "HI",        "heat index",                                                     "Fahrenheit"                         },
  {  25, -1, 0, "DURSUN_M",  "maximum duration of sunshine",                                   "s"                                  },
  {  26, -1, 0, "DURSUN_R",  "relative duration of sunshine",                                  "%"                                  },
  {  52, -1, 0, "RH_2M_K",   "2m relative humidity",                                           "%"                                  },
  {  53, -1, 0, "D_RH_2M_K", "kalman correction to 2m relative humidity",                      "%"                                  },
  {  58, -1, 0, "CLI_RATIO", "cloud ice ratio (Qi/Qc+Qi)",                                     "%"                                  },
  {  61, -1, 0, "TOT_SNOW",  "total precipitation in snow",                                    "kg/m**2"                            },
  {  62, -1, 0, "TOT_RAIN",  "total precipitation in rain",                                    "kg/m**2"                            },
  {  63, -1, 0, "TOT_CON",   "total convective precipitation",                                 "kg/m**2"                            },
  {  64, -1, 0, "TOT_GSP",   "total large scale precipitation",                                "kg/m**2"                            },
  {  65, -1, 0, "SNOW_%",    "percentage of precipitation in snow",                            "%"                                  },
  {  66, -1, 0, "CONV_%",    "percentage of convective precipitation",                         "%"                                  },
  {  67, -1, 0, "VORTP_ABS", "absolute",                                                       "VORTP_ABS 67 -1 absolute vorticity" },
  {  68, -1, 0, "VORTP_REL", "relative",                                                       "VORTP_REL 68 -1 relative vorticity" },
  {  70, -1, 0, "PDIFF_CON", "pressure difference between cloud base and cloud top",           "Pa"                                 },
  {  71, -1, 0, "TTOP_CON",  "temperature at cloud top",                                       "K"                                  },
  {  80, -1, 0, "GEM",       "emissivity of the ground",                                       "%"                                  },
  {  82, -1, 0, "Z0LOC",     "local surface roughness length",                                 "m"                                  },
  { 110, -1, 0, "LUM",       "luminosity",                                                     "klux"                               },
  { 111, -1, 0, "GLOB",      "global shortwave radiation at surface",                          "W/m**2"                             },
  { 112, -1, 0, "LW_IN_TG",  "incoming longwave radiation at surface",                         "W/m**2"                             },
  { 113, -1, 0, "LW_IN_TS",  "incoming longwave radiation at surface",                         "W/m**2"                             },
  { 114, -1, 0, "LW_IN_T2M", "incoming longwave radiation at surface",                         "W/m**2"                             },
  { 115, -1, 0, "SWISS_WE",  "Swiss",                                                          "SWISS_WE 115 1 Swiss coordinates"   },
  { 116, -1, 0, "SWISS_SN",  "Swiss",                                                          "SWISS_SN 116 1 Swiss coordinates"   },
  { 150, -1, 0, "KOINDEX",   "KO index",                                                       "K"                                  },
  { 151, -1, 0, "TTINDEX",   "total-totals index",                                             "K"                                  },
  { 152, -1, 0, "DCI",       "deep convection index",                                          "K"                                  },
  { 153, -1, 0, "SWEAT",     "severe weather thread index",                                    "undefined"                          },
  { 154, -1, 0, "ADEDO2",    "adedokun 2 index",                                               "K"                                  },
  { 160, -1, 0, "C_TSTORM",  "thunderstorm index using AdaBoost classifier",                   "undefined"                          },
  { 161, -1, 0, "CN_TSTORM", "thunderstorm probabilty using AdaBoost classifier",              "%"                                  },
  { 200, -1, 0, "WSHEARL",   "wind shear between surface and 3 km asl",                        "1/s"                                },
  { 201, -1, 0, "WSHEARM",   "wind shear between surface and 6 km asl",                        "1/s"                                },
  { 202, -1, 0, "WSHEARU",   "wind shear between 3 km (or surface) and 6 km asl",              "1/s"                                },
  { 211, -1, 0, "VWIN",      "maximum OLD turbulent wind gust in 10m",                         "m s-1"                              },
  { 212, -1, 0, "VW10M_20",  "maximum 10m wind speed",                                         "m s-1"                              },
  { 213, -1, 0, "VW10M_25",  "duration of VWIN_10M above 25 knots",                            "s"                                  },
  { 214, -1, 0, "VW10M_30",  "duration of VWIN_10M above 30 knots",                            "s"                                  },
  { 215, -1, 0, "VW10M_35",  "duration of VWIN_10M above 35 knots",                            "s"                                  },
  { 216, -1, 0, "VW10M_40",  "duration of VWIN_10M above 40 knots",                            "s"                                  },
  { 217, -1, 0, "VW10M_45",  "duration of VWIN_10M above 45 knots",                            "s"                                  },
  { 218, -1, 0, "VW10M_50",  "duration of VWIN_10M above 50 knots",                            "s"                                  },
  { 219, -1, 0, "VOLD",      "maximum turbulent wind gust in 10m",                             "m s-1"                              },
  { 220, -1, 0, "VJPS",      "maximum turbulent wind gust in 10m",                             "m s-1"                              },
  { 221, -1, 0, "VBRA",      "maximum Brasseur turbulent wind gust in 10m",                    "m s-1"                              },
  { 222, -1, 0, "VB10M_20",  "duration of VBRA_10M above 20 knots",                            "s"                                  },
  { 223, -1, 0, "VB10M_25",  "duration of VBRA_10M above 25 knots",                            "s"                                  },
  { 224, -1, 0, "VB10M_30",  "duration of VBRA_10M above 30 knots",                            "s"                                  },
  { 225, -1, 0, "VB10M_35",  "duration of VBRA_10M above 35 knots",                            "s"                                  },
  { 226, -1, 0, "VB10M_40",  "duration of VBRA_10M above 40 knots",                            "s"                                  },
  { 227, -1, 0, "VB10M_45",  "duration of VBRA_10M above 45 knots",                            "s"                                  },
  { 228, -1, 0, "VB10M_50",  "duration of VBRA_10M above 50 knots",                            "s"                                  },
  { 231, -1, 0, "VCON",      "maximum convective wind gust in 10m",                            "m s-1"                              },
  { 232, -1, 0, "VC10M_20",  "duration of VCON_10M above 20 knots",                            "s"                                  },
  { 233, -1, 0, "VC10M_25",  "duration of VCON_10M above 25 knots",                            "s"                                  },
  { 234, -1, 0, "VC10M_30",  "duration of VCON_10M above 30 knots",                            "s"                                  },
  { 235, -1, 0, "VC10M_35",  "duration of VCON_10M above 35 knots",                            "s"                                  },
  { 236, -1, 0, "VC10M_40",  "duration of VCON_10M above 40 knots",                            "s"                                  },
  { 237, -1, 0, "VC10M_45",  "duration of VCON_10M above 45 knots",                            "s"                                  },
  { 238, -1, 0, "VC10M_50",  "duration of VCON_10M above 50 knots",                            "s"                                  },
  { 241, -1, 0, "FMAX",      "maximum wind speed at k=ke",                                     "m s-1"                              },
  { 242, -1, 0, "USTARMAX",  "maximal u*=SQRT(Drag_coef)*fmax_10m",                            "m s-1"                              },
  { 243, -1, 0, "GLOB_DIF",  "global diffuse shortwave radiation at the surface",              "W/m**2"                             },
  { 244, -1, 0, "GLOB_DIR",  "global direct (beam) shortwave radiation at the surface",        "W/m**2"                             },
  { 245, -1, 0, "GLOB_vE",   "global shortwave radiation on a vertical surface facing east",   "W/m**2"                             },
  { 246, -1, 0, "GLOB_vS",   "global shortwave radiation on a vertical surface facing south",  "W/m**2"                             },
  { 247, -1, 0, "GLOB_vW",   "global shortwave radiation on a vertical surface facing west",   "W/m**2"                             },
  { 248, -1, 0, "GLOB_vN",   "global shortwave radiation on a vertical surface facing north",  "W/m**2"                             },
  { 249, -1, 0, "LW_TG_vS",  "incoming longwave radiation on a vertical surface facing south", "W/m**2"                             },
  { 250, -1, 0, "ENTH",      "enthalpy",                                                       "kJ/kg"                              },
  { 251, -1, 0, "ENTH",      "enthalpy",                                                       "kJ/kg"                              },
  { 252, -1, 0, "MIXRAT",    "mixing ratio",                                                   "g/kg"                               },
  { 253, -1, 0, "MIXRAT",    "mixing ratio",                                                   "g/kg"                               },
  { 254, -1, 0, "TW",        "wet bulb temperature",                                           "degC"                               },
  { 255, -1, 0, "TW",        "wet bulb temperature",                                           "degC"                               },
};


static
void tableDefault(void)
{

  // define table : echam4
  {
    int instID = institutInq(98, 255, "MPIMET", NULL);
    if (instID == -1) instID = institutDef(98, 255, "MPIMET", NULL);

    int modelID = modelInq(instID, 0, "ECHAM4");
    if (modelID == -1) modelID = modelDef(instID, 0, "ECHAM4");

    int tableID = tableDef(modelID, 128, "echam4");

    tableLink(tableID, echam4, sizeof(echam4) / sizeof(param_type));
  }

  // define table : echam5
  {
    int instID = institutInq(98, 232, "MPIMET", NULL);
    if (instID == -1) instID = institutDef(98, 232, "MPIMET", NULL);

    int modelID = modelInq(instID, 0, "ECHAM5");
    if (modelID == -1) modelID = modelDef(instID, 0, "ECHAM5");

    int tableID = tableDef(modelID, 128, "echam5");

    tableLink(tableID, echam5, sizeof(echam5) / sizeof(param_type));
  }

  // define table : echam6
  {
    int instID = institutInq(0, 0, "MPIMET", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MPIMET", NULL);

    int modelID = modelInq(instID, 0, "ECHAM6");
    if (modelID == -1) modelID = modelDef(instID, 0, "ECHAM6");

    int tableID = tableDef(modelID, 128, "echam6");

    tableLink(tableID, echam6, sizeof(echam6) / sizeof(param_type));
  }

  // define table : mpiom1
  {
    int instID = institutInq(0, 0, "MPIMET", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MPIMET", NULL);

    int modelID = modelInq(instID, 0, "MPIOM1");
    if (modelID == -1) modelID = modelDef(instID, 0, "MPIOM1");

    int tableID = tableDef(modelID, 128, "mpiom1");

    tableLink(tableID, mpiom1, sizeof(mpiom1) / sizeof(param_type));
  }

  // define table : ecmwf
  {
    int instID = institutInq(0, 0, "ECMWF", NULL);
    if (instID == -1) instID = institutDef(0, 0, "ECMWF", NULL);

    int modelID = modelInq(instID, 0, "");
    if (modelID == -1) modelID = modelDef(instID, 0, "");

    int tableID = tableDef(modelID, 128, "ecmwf");

    tableLink(tableID, ecmwf, sizeof(ecmwf) / sizeof(param_type));
  }

  // define table : remo
  {
    int instID = institutInq(0, 0, "MPIMET", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MPIMET", NULL);

    int modelID = modelInq(instID, 0, "REMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "REMO");

    int tableID = tableDef(modelID, 128, "remo");

    tableLink(tableID, remo, sizeof(remo) / sizeof(param_type));
  }

  // define table : cosmo002
  {
    int instID = institutInq(0, 0, "MCH", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MCH", NULL);

    int modelID = modelInq(instID, 0, "COSMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "COSMO");

    int tableID = tableDef(modelID, 002, "cosmo002");

    tableLink(tableID, cosmo002, sizeof(cosmo002) / sizeof(param_type));
  }

  // define table : cosmo201
  {
    int instID = institutInq(0, 0, "MCH", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MCH", NULL);

    int modelID = modelInq(instID, 0, "COSMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "COSMO");

    int tableID = tableDef(modelID, 201, "cosmo201");

    tableLink(tableID, cosmo201, sizeof(cosmo201) / sizeof(param_type));
  }

  // define table : cosmo202
  {
    int instID = institutInq(0, 0, "MCH", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MCH", NULL);

    int modelID = modelInq(instID, 0, "COSMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "COSMO");

    int tableID = tableDef(modelID, 202, "cosmo202");

    tableLink(tableID, cosmo202, sizeof(cosmo202) / sizeof(param_type));
  }

  // define table : cosmo203
  {
    int instID = institutInq(0, 0, "MCH", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MCH", NULL);

    int modelID = modelInq(instID, 0, "COSMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "COSMO");

    int tableID = tableDef(modelID, 203, "cosmo203");

    tableLink(tableID, cosmo203, sizeof(cosmo203) / sizeof(param_type));
  }

  // define table : cosmo205
  {
    int instID = institutInq(0, 0, "MCH", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MCH", NULL);

    int modelID = modelInq(instID, 0, "COSMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "COSMO");

    int tableID = tableDef(modelID, 205, "cosmo205");

    tableLink(tableID, cosmo205, sizeof(cosmo205) / sizeof(param_type));
  }

  // define table : cosmo250
  {
    int instID = institutInq(0, 0, "MCH", NULL);
    if (instID == -1) instID = institutDef(0, 0, "MCH", NULL);

    int modelID = modelInq(instID, 0, "COSMO");
    if (modelID == -1) modelID = modelDef(instID, 0, "COSMO");

    int tableID = tableDef(modelID, 250, "cosmo250");

    tableLink(tableID, cosmo250, sizeof(cosmo250) / sizeof(param_type));
  }
}

// clang-format on

#endif /* TABLE_H */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>



enum
{
  MAX_TABLE = 512,
  MAX_PARS = 1024
};

typedef struct
{
  bool used;
  int npars;
  int modelID;
  int number;
  char *name;
  param_type *pars;
} paramtab_type;

static paramtab_type parTable[MAX_TABLE];
static int parTableSize = MAX_TABLE;
static int parTableNum = 0;
static int ParTableInit = 0;

static char *tablePath = NULL;

static void tableDefModelID(int tableID, int modelID);
static void tableDefNum(int tableID, int tablenum);

static void
tableDefEntry(int tableID, int id, int ltype, const char *name, const char *longname, const char *units)
{
  if (tableID >= 0 && tableID < MAX_TABLE && parTable[tableID].used) {}
  else
    Error("Invalid table ID %d", tableID);

  int item = parTable[tableID].npars++;
  parTable[tableID].pars[item].id = id;
  parTable[tableID].pars[item].ltype = ltype;
  parTable[tableID].pars[item].dupflags = 0;
  parTable[tableID].pars[item].name = NULL;
  parTable[tableID].pars[item].longname = NULL;
  parTable[tableID].pars[item].units = NULL;

  if (name && name[0])
  {
    parTable[tableID].pars[item].name = strdup(name);
    parTable[tableID].pars[item].dupflags |= TABLE_DUP_NAME;
  }
  if (longname && longname[0])
  {
    parTable[tableID].pars[item].longname = strdup(longname);
    parTable[tableID].pars[item].dupflags |= TABLE_DUP_LONGNAME;
  }
  if (units && units[0])
  {
    parTable[tableID].pars[item].units = strdup(units);
    parTable[tableID].pars[item].dupflags |= TABLE_DUP_UNITS;
  }
}

void
tableLink(int tableID, const param_type *pars, int npars)
{
  for (int item = 0; item < npars; item++)
  {
    parTable[tableID].pars[item].id = pars[item].id;
    parTable[tableID].pars[item].ltype = pars[item].ltype;
    parTable[tableID].pars[item].dupflags = 0;
    parTable[tableID].pars[item].name = pars[item].name;
    parTable[tableID].pars[item].longname = pars[item].longname;
    parTable[tableID].pars[item].units = pars[item].units;
  }

  parTable[tableID].npars = npars;
}

static void
parTableInitEntry(int tableID)
{
  parTable[tableID].used = false;
  parTable[tableID].pars = NULL;
  parTable[tableID].npars = 0;
  parTable[tableID].modelID = CDI_UNDEFID;
  parTable[tableID].number = CDI_UNDEFID;
  parTable[tableID].name = NULL;
}

static void
tableGetPath(void)
{
  char *path = getenv("TABLEPATH");
  if (path) tablePath = strdup(path);
  // printf("tablePath = %s\n", tablePath);
}

static void
parTableFinalize(void)
{
  for (int tableID = 0; tableID < MAX_TABLE; ++tableID)
    if (parTable[tableID].used)
    {
      int npars = parTable[tableID].npars;
      for (int item = 0; item < npars; ++item)
      {
        param_type *param = &parTable[tableID].pars[item];
        if (param->dupflags & TABLE_DUP_NAME) free((void *) param->name);
        if (param->dupflags & TABLE_DUP_LONGNAME) free((void *) param->longname);
        if (param->dupflags & TABLE_DUP_UNITS) free((void *) param->units);
      }
      free(parTable[tableID].pars);
      free(parTable[tableID].name);
    }
}

static void
parTableInit(void)
{
  ParTableInit = 1;

  atexit(parTableFinalize);
  if (cdiPartabIntern) tableDefault();

  tableGetPath();
}

static int
tableNewEntry(void)
{
  int tableID = 0;

  static int init = 0;
  if (!init)
  {
    for (tableID = 0; tableID < parTableSize; tableID++) parTableInitEntry(tableID);
    init = 1;
  }

  // Look for a free slot in parTable.
  for (tableID = 0; tableID < parTableSize; tableID++)
  {
    if (!parTable[tableID].used) break;
  }

  if (tableID == parTableSize) Error("No more table entries (limit=%d)!", parTableSize);

  parTable[tableID].used = true;
  parTableNum++;

  return tableID;
}

static int
decodeForm1(char *pline, char *name, char *longname, char *units)
{
  char *pstart, *pend;

  // FIXME: parse success isn't verified
  /* long level =  */ strtol(pline, &pline, 10);
  while (isspace((int) *pline)) pline++;

  pstart = pline;
  while (!(isspace((int) *pline) || *pline == 0)) pline++;
  size_t len = (size_t) (pline - pstart);
  if (len > 0)
  {
    memcpy(name, pstart, len);
    name[len] = 0;
  }
  else
    return 0;

  if (pline[0] == 0) return 0;

  // Format 1 : code name add mult longname [units]
  // FIXME: successful parse isn't verified
  /* double add  =  */ strtod(pline, &pline);
  // FIXME: successful parse isn't verified
  /* double mult =  */ strtod(pline, &pline);

  while (isspace((int) *pline)) pline++;

  len = strlen(pline);
  if (len > 0)
  {
    pstart = pline;
    pend = strrchr(pline, '[');
    if (pend == pstart)
      len = 0;
    else
    {
      if (pend)
        pend--;
      else
        pend = pstart + len;
      while (isspace((int) *pend)) pend--;
      len = (size_t) (pend - pstart + 1);
    }
    if (len > 0)
    {
      memcpy(longname, pstart, len);
      longname[len] = 0;
    }
    pstart = strrchr(pline, '[');
    if (pstart)
    {
      pstart++;
      while (isspace((int) *pstart)) pstart++;
      pend = strchr(pstart, ']');
      if (!pend) return 0;
      pend--;
      while (isspace((int) *pend)) pend--;
      len = (size_t) (pend - pstart + 1);
      if (len > 0)
      {
        memcpy(units, pstart, len);
        units[len] = 0;
      }
    }
  }

  return 0;
}

static int
decodeForm2(char *pline, char *name, char *longname, char *units)
{
  // Format 2 : code | name | longname | units
  char *pend;

  pline = strchr(pline, '|');
  pline++;

  while (isspace((int) *pline)) pline++;
  if (*pline != '|')
  {
    pend = strchr(pline, '|');
    if (!pend)
    {
      pend = pline;
      while (!isspace((int) *pend)) pend++;
      size_t len = (size_t) (pend - pline);
      if (len > 0)
      {
        memcpy(name, pline, len);
        name[len] = 0;
      }
      return 0;
    }
    else
    {
      pend--;
      while (isspace((int) *pend)) pend--;
      size_t len = (size_t) (pend - pline + 1);
      if (len > 0)
      {
        memcpy(name, pline, len);
        name[len] = 0;
      }
    }
  }
  else
    name[0] = '\0';

  pline = strchr(pline, '|');
  pline++;
  while (isspace((int) *pline)) pline++;
  pend = strchr(pline, '|');
  if (!pend) pend = strchr(pline, 0);
  pend--;
  while (isspace((int) *pend)) pend--;
  {
    size_t len = (size_t) (pend - pline + 1);
    if (len > 0)
    {
      memcpy(longname, pline, len);
      longname[len] = 0;
    }
  }

  pline = strchr(pline, '|');
  if (pline)
  {
    pline++;
    while (isspace((int) *pline)) pline++;
    pend = strchr(pline, '|');
    if (!pend) pend = strchr(pline, 0);
    pend--;
    while (isspace((int) *pend)) pend--;
    ptrdiff_t len = pend - pline + 1;
    if (len < 0) len = 0;
    memcpy(units, pline, (size_t) len);
    units[len] = 0;
  }

  return 0;
}

int
tableRead(const char *tablefile)
{
  char line[1024], *pline;
  char name[256], longname[256], units[256];
  int tableID = CDI_UNDEFID;

  FILE *tablefp = fopen(tablefile, "r");
  if (tablefp == NULL) return tableID;

  const char *tablename = strrchr(tablefile, '/');
  if (tablename == 0)
    tablename = tablefile;
  else
    tablename++;

  tableID = tableDef(-1, 0, tablename);

  while (fgets(line, 1023, tablefp))
  {
    size_t len = strlen(line);
    if (line[len - 1] == '\n') line[len - 1] = '\0';
    name[0] = 0;
    longname[0] = 0;
    units[0] = 0;
    if (line[0] == '#') continue;
    pline = line;

    len = strlen(pline);
    if (len < 4) continue;
    while (isspace((int) *pline)) pline++;
    int id = atoi(pline);
    // if ( id > 255 ) id -= 256;
    if (id == 0) continue;

    while (isdigit((int) *pline)) pline++;

    int ltype = CDI_UNDEFID;
    if (*pline == ';' || *pline == ':')
    {
      pline++;
      ltype = atoi(pline);
      while (isdigit((int) *pline)) pline++;

      if (*pline == ';' || *pline == ':')
      {
        pline++;
        while (isdigit((int) *pline)) pline++;
      }
    }

    while (isdigit((int) *pline)) pline++;

    int err = (strchr(pline, '|')) ? decodeForm2(pline, name, longname, units) : decodeForm1(pline, name, longname, units);
    if (err) continue;

    if (name[0] == 0) snprintf(name, sizeof(name), "var%d", id);

    tableDefEntry(tableID, id, ltype, name, longname, units);
  }

  fclose(tablefp);

  return tableID;
}

static int
tableFromEnv(int modelID, int tablenum)
{
  char tablename[256] = { '\0' };
  size_t tablenameLen = 0;
  int instID;

  const char *name2Use;
  {
    const char *modelName, *instName;
    if ((modelName = modelInqNamePtr(modelID)))
      name2Use = modelName;
    else if ((instID = modelInqInstitut(modelID)) != CDI_UNDEFID && (instName = institutInqNamePtr(instID)))
      name2Use = instName;
    else
      return CDI_UNDEFID;
  }
  tablenameLen = strlen(name2Use);
  memcpy(tablename, name2Use, tablenameLen);
  if (tablenum) tablenameLen += (size_t) (snprintf(tablename + tablenameLen, 256 - tablenameLen, "_%03d", tablenum));
  size_t lenp = 0, lenf = tablenameLen;
  if (tablePath) lenp = strlen(tablePath);
  // if (tablePath) printf("tablePath = %s\n", tablePath);
  // if (tablename) printf("tableName = %s\n", tablename);
  char *tablefile = (char *) malloc(lenp + lenf + 3);
  if (tablePath)
  {
    strcpy(tablefile, tablePath);
    strcat(tablefile, "/");
  }
  else
    tablefile[0] = '\0';
  strcat(tablefile, tablename);
  // if (tablefile) printf("tableFile = %s\n", tablefile);

  int tableID = tableRead(tablefile);
  if (tableID != CDI_UNDEFID)
  {
    tableDefModelID(tableID, modelID);
    tableDefNum(tableID, tablenum);
  }
  // printf("tableID = %d %s\n", tableID, tablefile);
  free(tablefile);

  return tableID;
}

int
tableInq(int modelID, int tablenum, const char *tablename)
{
  int tableID = CDI_UNDEFID;
  int modelID2 = CDI_UNDEFID;
  char tablefile[256] = { '\0' };

  if (!ParTableInit) parTableInit();

  if (tablename)
  {
    strcpy(tablefile, tablename);
    /*
    printf("tableInq: tablefile = >%s<\n", tablefile);
    */
    /* search for internal table */
    for (tableID = 0; tableID < MAX_TABLE; tableID++)
    {
      if (parTable[tableID].used && parTable[tableID].name)
      {
        /* len = strlen(parTable[tableID].name); */
        size_t len = strlen(tablename);
        if (memcmp(parTable[tableID].name, tablename, len) == 0) break;
      }
    }
    if (tableID == MAX_TABLE) tableID = CDI_UNDEFID;
    if (CDI_Debug) Message("tableID = %d tablename = %s", tableID, tablename);
  }
  else
  {
    for (tableID = 0; tableID < MAX_TABLE; tableID++)
    {
      if (parTable[tableID].used)
      {
        if (parTable[tableID].modelID == modelID && parTable[tableID].number == tablenum) break;
      }
    }

    if (tableID == MAX_TABLE) tableID = CDI_UNDEFID;

    if (tableID == CDI_UNDEFID)
    {
      if (modelID != CDI_UNDEFID)
      {
        const char *modelName;
        if ((modelName = modelInqNamePtr(modelID)))
        {
          strcpy(tablefile, modelName);
          size_t len = strlen(tablefile);
          for (size_t i = 0; i < len; i++)
            if (tablefile[i] == '.') tablefile[i] = '\0';
          modelID2 = modelInq(-1, 0, tablefile);
        }
      }
      if (modelID2 != CDI_UNDEFID)
        for (tableID = 0; tableID < MAX_TABLE; tableID++)
        {
          if (parTable[tableID].used)
          {
            if (parTable[tableID].modelID == modelID2 && parTable[tableID].number == tablenum) break;
          }
        }
    }

    if (tableID == MAX_TABLE) tableID = CDI_UNDEFID;

    if (tableID == CDI_UNDEFID && modelID != CDI_UNDEFID) tableID = tableFromEnv(modelID, tablenum);

    if (CDI_Debug && tablename) Message("tableID = %d tablename = %s", tableID, tablename);
  }

  return tableID;
}

int
tableDef(int modelID, int tablenum, const char *tablename)
{
  int tableID = CDI_UNDEFID;

  if (!ParTableInit) parTableInit();
  // if (!(modelID == CDI_UNDEFID && tablenum == 0)) tableID = tableInq(modelID, tablenum, tablename);
  if (tableID == CDI_UNDEFID)
  {
    tableID = tableNewEntry();

    parTable[tableID].modelID = modelID;
    parTable[tableID].number = tablenum;
    if (tablename) parTable[tableID].name = strdup(tablename);

    parTable[tableID].pars = (param_type *) malloc(MAX_PARS * sizeof(param_type));
  }

  return tableID;
}

static void
tableDefModelID(int tableID, int modelID)
{
  parTable[tableID].modelID = modelID;
}

static void
tableDefNum(int tableID, int tablenum)
{
  parTable[tableID].number = tablenum;
}

int
tableInqNum(int tableID)
{
  int number = 0;

  if (tableID >= 0 && tableID < MAX_TABLE) number = parTable[tableID].number;

  return number;
}

int
tableInqModel(int tableID)
{
  int modelID = -1;

  if (tableID >= 0 && tableID < MAX_TABLE) modelID = parTable[tableID].modelID;

  return modelID;
}

static void
partabCheckID(int item)
{
  if (item < 0 || item >= parTableSize) Error("item %d undefined!", item);

  if (!parTable[item].name) Error("item %d name undefined!", item);
}

const char *
tableInqNamePtr(int tableID)
{
  const char *tablename = NULL;

  if (CDI_Debug) Message("tableID = %d", tableID);

  if (!ParTableInit) parTableInit();

  if (tableID >= 0 && tableID < parTableSize)
    if (parTable[tableID].name) tablename = parTable[tableID].name;

  return tablename;
}

static size_t
max_length(size_t maxlen, const char *cstring)
{
  if (cstring)
  {
    size_t len = strlen(cstring);
    if (len > maxlen) maxlen = len;
  }
  return maxlen;
}

void
tableWrite(const char *ptfile, int tableID)
{
  size_t maxname = 4, maxlname = 10, maxunits = 2;
  int instID = CDI_UNDEFID;
  int center = 0, subcenter = 0;
  const char *instnameptr = NULL, *modelnameptr = NULL;

  if (CDI_Debug) Message("write parameter table %d to %s", tableID, ptfile);

  if (tableID == CDI_UNDEFID)
  {
    Warning("parameter table ID undefined");
    return;
  }

  partabCheckID(tableID);

  FILE *ptfp = fopen(ptfile, "w");

  int npars = parTable[tableID].npars;
  for (int item = 0; item < npars; item++)
  {
    maxname = max_length(maxname, parTable[tableID].pars[item].name);
    maxlname = max_length(maxlname, parTable[tableID].pars[item].longname);
    maxunits = max_length(maxunits, parTable[tableID].pars[item].units);
  }

  int tablenum = tableInqNum(tableID);
  int modelID = parTable[tableID].modelID;
  if (modelID != CDI_UNDEFID)
  {
    modelnameptr = modelInqNamePtr(modelID);
    instID = modelInqInstitut(modelID);
  }
  if (instID != CDI_UNDEFID)
  {
    center = institutInqCenter(instID);
    subcenter = institutInqSubcenter(instID);
    instnameptr = institutInqNamePtr(instID);
  }

  fprintf(ptfp, "# Parameter table\n");
  fprintf(ptfp, "#\n");
  if (tablenum) fprintf(ptfp, "# TABLE_ID=%d\n", tablenum);
  fprintf(ptfp, "# TABLE_NAME=%s\n", parTable[tableID].name);
  if (modelnameptr) fprintf(ptfp, "# TABLE_MODEL=%s\n", modelnameptr);
  if (instnameptr) fprintf(ptfp, "# TABLE_INSTITUT=%s\n", instnameptr);
  if (center) fprintf(ptfp, "# TABLE_CENTER=%d\n", center);
  if (subcenter) fprintf(ptfp, "# TABLE_SUBCENTER=%d\n", subcenter);
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "# id       = parameter ID\n");
  fprintf(ptfp, "# name     = variable name\n");
  fprintf(ptfp, "# title    = long name (description)\n");
  fprintf(ptfp, "# units    = variable units\n");
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "# The format of each record is:\n");
  fprintf(ptfp, "#\n");
  fprintf(ptfp, "# id | %-*s | %-*s | %-*s\n", (int) maxname, "name", (int) maxlname, "title", (int) maxunits, "units");

  for (int item = 0; item < npars; item++)
  {
    const char *name = parTable[tableID].pars[item].name, *longname = parTable[tableID].pars[item].longname,
               *units = parTable[tableID].pars[item].units;
    if (name == NULL) name = " ";
    if (longname == NULL) longname = " ";
    if (units == NULL) units = " ";
    fprintf(ptfp, "%4d | %-*s | %-*s | %-*s\n", parTable[tableID].pars[item].id, (int) maxname, name, (int) maxlname, longname,
            (int) maxunits, units);
  }

  fclose(ptfp);
}

void
tableFWriteC(FILE *ptfp, int tableID)
{
  const char chelp[] = "";
  size_t maxname = 0, maxlname = 0, maxunits = 0;
  char tablename[256];

  if (tableID == CDI_UNDEFID)
  {
    Warning("parameter table ID undefined");
    return;
  }

  partabCheckID(tableID);

  int npars = parTable[tableID].npars;
  for (int item = 0; item < npars; item++)
  {
    maxname = max_length(maxname, parTable[tableID].pars[item].name);
    maxlname = max_length(maxlname, parTable[tableID].pars[item].longname);
    maxunits = max_length(maxunits, parTable[tableID].pars[item].units);
  }

  strncpy(tablename, parTable[tableID].name, sizeof(tablename) - 1);
  tablename[sizeof(tablename) - 1] = '\0';
  {
    size_t len = strlen(tablename);
    for (size_t i = 0; i < len; i++)
      if (tablename[i] == '.') tablename[i] = '_';
  }
  fprintf(ptfp, "static const param_type %s[] = {\n", tablename);

  for (int item = 0; item < npars; item++)
  {
    size_t len = strlen(parTable[tableID].pars[item].name),
           llen = parTable[tableID].pars[item].longname ? strlen(parTable[tableID].pars[item].longname) : 0,
           ulen = parTable[tableID].pars[item].units ? strlen(parTable[tableID].pars[item].units) : 0;
    fprintf(ptfp, "  {%4d, -1, 0, \"%s\", %-*s%c%s%s, %-*s%c%s%s %-*s},\n", parTable[tableID].pars[item].id,
            parTable[tableID].pars[item].name, (int) (maxname - len), chelp, llen ? '"' : ' ',
            llen ? parTable[tableID].pars[item].longname : "NULL", llen ? "\"" : "", (int) (maxlname - (llen ? llen : 3)), chelp,
            ulen ? '"' : ' ', ulen ? parTable[tableID].pars[item].units : "NULL", ulen ? "\"" : "",
            (int) (maxunits - (ulen ? ulen : 3)), chelp);
  }

  fprintf(ptfp, "};\n\n");
}

void
tableInqEntry(int tableID, int id, int ltype, char *name, char *longname, char *units)
{
  if (((tableID >= 0) & (tableID < MAX_TABLE)) | (tableID == CDI_UNDEFID)) {}
  else
    Error("Invalid table ID %d", tableID);

  if (tableID != CDI_UNDEFID)
  {
    int npars = parTable[tableID].npars;
    for (int item = 0; item < npars; item++)
    {
      if (parTable[tableID].pars[item].id == id
          && (parTable[tableID].pars[item].ltype == -1 || ltype == -1 || parTable[tableID].pars[item].ltype == ltype))
      {
        if (name && parTable[tableID].pars[item].name) strcpy(name, parTable[tableID].pars[item].name);
        if (longname && parTable[tableID].pars[item].longname) strcpy(longname, parTable[tableID].pars[item].longname);
        if (units && parTable[tableID].pars[item].units) strcpy(units, parTable[tableID].pars[item].units);

        break;
      }
    }
  }
}

int
tableInqParCode(int tableID, char *varname, int *code)
{
  int err = 1;

  if (tableID != CDI_UNDEFID && varname != NULL)
  {
    int npars = parTable[tableID].npars;
    for (int item = 0; item < npars; item++)
    {
      if (parTable[tableID].pars[item].name && str_is_equal(parTable[tableID].pars[item].name, varname))
      {
        *code = parTable[tableID].pars[item].id;
        err = 0;
        break;
      }
    }
  }

  return err;
}

int
tableInqNumber(void)
{
  if (!ParTableInit) parTableInit();

  return parTableNum;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef NORMALIZE_MONTH_H
#define NORMALIZE_MONTH_H

#include <stdlib.h>

struct YearMonth
{
  int year, month;
};

/* normalizes month to range [1,12] and adjusts year accordingly */
static inline struct YearMonth
normalize_month(int year, int month)
{
  div_t modres = div(month - 1, 12);
  year += modres.quot - ((month < 1) & (modres.rem != 0));
  return (struct YearMonth){ .year = year, .month = (modres.rem + 12) % 12 + 1 };
}

#endif
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <stddef.h>
#include <stdio.h>


static int DefaultTimeType = TAXIS_ABSOLUTE;
static int DefaultTimeUnit = TUNIT_HOUR;

static int taxisCompareP(void *taxisptr1, void *taxisptr2);
static void taxisDestroyP(void *taxisptr);
static void taxisPrintKernel(taxis_t *taxisptr, FILE *fp);
static int taxisGetPackSize(void *taxisptr, void *context);
static void taxisPack(void *taxisptr, void *buf, int size, int *position, void *context);
static int taxisTxCode(void *taxisptr);

const resOps taxisOps
    = { taxisCompareP, taxisDestroyP, (void (*)(void *, FILE *)) taxisPrintKernel, taxisGetPackSize, taxisPack, taxisTxCode };

#define container_of(ptr, type, member) ((type *) (void *) ((unsigned char *) ptr - offsetof(type, member)))

struct refcount_string
{
  int ref_count;
  char string[];
};

static char *
new_refcount_string(size_t len)
{
  struct refcount_string *container = (struct refcount_string *) Malloc(sizeof(*container) + len + 1);
  container->ref_count = 1;
  return container->string;
}

static void
delete_refcount_string(void *p)
{
  if (p)
  {
    struct refcount_string *container = container_of(p, struct refcount_string, string);
    if (!--(container->ref_count)) Free(container);
  }
}

static char *
dup_refcount_string(char *p)
{
  if (p)
  {
    struct refcount_string *container = container_of(p, struct refcount_string, string);
    ++(container->ref_count);
  }
  return p;
}

#undef container_of

const char *
taxisNamePtr(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->name;
}

const char *
tunitNamePtr(int unitID)
{
  static const char Timeunits[][TAXIS_MAX_UNIT_STR_LEN + 1] = {
    "undefined", "seconds", "minutes", "quarters", "30minutes", "hours", "3hours", "6hours", "12hours", "days", "months", "years",
  };
  enum
  {
    size = sizeof(Timeunits) / sizeof(Timeunits[0])
  };

  return (unitID > 0 && unitID < size) ? Timeunits[unitID] : Timeunits[0];
}

void
ptaxisInit(taxis_t *taxisptr)
{
  taxisptr->self = CDI_UNDEFID;
  taxisptr->type = DefaultTimeType;
  taxisptr->calendar = CDI_Default_Calendar;
  taxisptr->unit = DefaultTimeUnit;
  taxisptr->numavg = 0;
  taxisptr->climatology = false;
  taxisptr->hasBounds = false;
  cdiDateTime_init(&taxisptr->sDateTime);
  cdiDateTime_init(&taxisptr->vDateTime);
  cdiDateTime_init(&taxisptr->rDateTime);
  cdiDateTime_init(&taxisptr->fDateTime);
  cdiDateTime_init(&taxisptr->vDateTime_lb);
  cdiDateTime_init(&taxisptr->vDateTime_ub);
  taxisptr->fc_unit = DefaultTimeUnit;
  taxisptr->fc_period = 0;
  taxisptr->name = NULL;
  taxisptr->longname = NULL;
  taxisptr->units = NULL;

  cdiInitKeys(&taxisptr->keys);
  cdiDefVarKeyInt(&taxisptr->keys, CDI_KEY_DATATYPE, CDI_DATATYPE_FLT64);
}

static taxis_t *
taxisNewEntry(cdiResH resH)
{
  taxis_t *taxisptr = (taxis_t *) Malloc(sizeof(taxis_t));

  ptaxisInit(taxisptr);
  if (resH == CDI_UNDEFID)
    taxisptr->self = reshPut(taxisptr, &taxisOps);
  else
  {
    taxisptr->self = resH;
    reshReplace(resH, taxisptr, &taxisOps);
  }

  return taxisptr;
}

/*
@Function  taxisCreate
@Title     Create a Time axis

@Prototype int taxisCreate(int taxistype)
@Parameter
    @Item  taxistype  The type of the Time axis, one of the set of predefined CDI time axis types.
                      The valid CDI time axis types are @func{TAXIS_ABSOLUTE} and @func{TAXIS_RELATIVE}.

@Description
The function @func{taxisCreate} creates a Time axis.

@Result
@func{taxisCreate} returns an identifier to the Time axis.

@Example
Here is an example using @func{taxisCreate} to create a relative T-axis with a standard calendar.

@Source
   ...
int taxisID;
   ...
taxisID = taxisCreate(TAXIS_RELATIVE);
taxisDefCalendar(taxisID, CALENDAR_STANDARD);
taxisDefRdate(taxisID, 19850101);
taxisDefRtime(taxisID, 120000);
   ...
@EndSource
@EndFunction
*/
int
taxisCreate(int taxistype)
{
  taxis_t *taxisptr = taxisNewEntry(CDI_UNDEFID);
  taxisptr->type = taxistype;

  int taxisID = taxisptr->self;
  return taxisID;
}

void
taxisDestroyKernel(taxis_t *taxisptr)
{
  delete_refcount_string(taxisptr->name);
  delete_refcount_string(taxisptr->longname);
  delete_refcount_string(taxisptr->units);
}

/*
@Function  taxisDestroy
@Title     Destroy a Time axis

@Prototype void taxisDestroy(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @func{taxisCreate}

@EndFunction
*/
void
taxisDestroy(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  reshRemove(taxisID, &taxisOps);
  taxisDestroyKernel(taxisptr);
  Free(taxisptr);
}

void
taxisDestroyP(void *taxisptr)
{
  taxisDestroyKernel((taxis_t *) taxisptr);
  Free(taxisptr);
}

int
taxisDuplicate(int taxisID1)
{
  taxis_t *taxisptr1 = (taxis_t *) reshGetVal(taxisID1, &taxisOps);
  taxis_t *taxisptr2 = taxisNewEntry(CDI_UNDEFID);

  int taxisID2 = taxisptr2->self;

  ptaxisCopy(taxisptr2, taxisptr1);

  return taxisID2;
}

void
taxisDefType(int taxisID, int taxistype)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->type != taxistype)
  {
    taxisptr->type = taxistype;
    cdiDefVarKeyInt(&taxisptr->keys, CDI_KEY_DATATYPE, CDI_DATATYPE_FLT64);
    delete_refcount_string(taxisptr->units);
    taxisptr->units = NULL;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisDefVdate
@Title     Define the verification date

@Prototype void taxisDefVdate(int taxisID, int vdate)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  vdate    Verification date (YYYYMMDD)

@Description
The function @func{taxisDefVdate} defines the verification date of a Time axis.

@EndFunction
*/
void
taxisDefVdate(int taxisID, int vdate)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if ((int) cdiDate_get(taxisptr->vDateTime.date) != vdate)
  {
    taxisptr->vDateTime.date = cdiDate_set(vdate);
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisDefVtime
@Title     Define the verification time

@Prototype void taxisDefVtime(int taxisID, int vtime)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  vtime    Verification time (hhmmss)

@Description
The function @func{taxisDefVtime} defines the verification time of a Time axis.

@EndFunction
*/
void
taxisDefVtime(int taxisID, int vtime)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiTime_get(taxisptr->vDateTime.time) != vtime)
  {
    taxisptr->vDateTime.time = cdiTime_set(vtime);
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefVdatetime(int taxisID, CdiDateTime vDateTime)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNE(taxisptr->vDateTime, vDateTime))
  {
    taxisptr->vDateTime = vDateTime;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefRdatetime(int taxisID, CdiDateTime rDateTime)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNE(taxisptr->rDateTime, rDateTime))
  {
    taxisptr->rDateTime = rDateTime;
    delete_refcount_string(taxisptr->units);
    taxisptr->units = NULL;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisDefRdate
@Title     Define the reference date

@Prototype void taxisDefRdate(int taxisID, int rdate)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  rdate    Reference date (YYYYMMDD)

@Description
The function @func{taxisDefRdate} defines the reference date of a Time axis.

@EndFunction
*/
void
taxisDefRdate(int taxisID, int rdate)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if ((int) cdiDate_get(taxisptr->rDateTime.date) != rdate)
  {
    taxisptr->rDateTime.date = cdiDate_set(rdate);
    delete_refcount_string(taxisptr->units);
    taxisptr->units = NULL;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisDefRtime
@Title     Define the reference time

@Prototype void taxisDefRtime(int taxisID, int rtime)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  rtime    Reference time (hhmmss)

@Description
The function @func{taxisDefRtime} defines the reference time of a Time axis.

@EndFunction
*/
void
taxisDefRtime(int taxisID, int rtime)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiTime_get(taxisptr->rDateTime.time) != rtime)
  {
    taxisptr->rDateTime.time = cdiTime_set(rtime);
    if (taxisptr->units)
    {
      delete_refcount_string(taxisptr->units);
      taxisptr->units = NULL;
    }
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisDefFdatetime
@Title     Define the forecast reference date/time

@Prototype void taxisDefFdatetime(int taxisID, CdiDateTime fDateTime)
@Parameter
    @Item  taxisID    Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  fDateTime  Forecast reference date/time

@Description
The function @func{taxisDefFdatetime} defines the forecast reference date/time of a Time axis.

@EndFunction
*/
void
taxisDefFdatetime(int taxisID, CdiDateTime fDateTime)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNE(taxisptr->fDateTime, fDateTime))
  {
    taxisptr->fDateTime = fDateTime;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisDefCalendar
@Title     Define the calendar

@Prototype void taxisDefCalendar(int taxisID, int calendar)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  calendar The type of the calendar, one of the set of predefined CDI calendar types.
                    The valid CDI calendar types are @func{CALENDAR_STANDARD}, @func{CALENDAR_PROLEPTIC},
                    @func{CALENDAR_360DAYS}, @func{CALENDAR_365DAYS} and @func{CALENDAR_366DAYS}.

@Description
The function @func{taxisDefCalendar} defines the calendar of a Time axis.

@EndFunction
*/
void
taxisDefCalendar(int taxisID, int calendar)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->calendar != calendar)
  {
    taxisptr->calendar = calendar;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefTunit(int taxisID, int unit)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->unit != unit)
  {
    taxisptr->unit = unit;
    delete_refcount_string(taxisptr->units);
    taxisptr->units = NULL;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefForecastTunit(int taxisID, int unit)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->fc_unit != unit)
  {
    taxisptr->fc_unit = unit;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefForecastPeriod(int taxisID, double fc_period)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (IS_NOT_EQUAL(taxisptr->fc_period, fc_period))
  {
    taxisptr->fc_period = fc_period;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefNumavg(int taxisID, int numavg)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->numavg != numavg)
  {
    taxisptr->numavg = numavg;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
  The type of the time axis, one of the set of predefined CDI time types.
  The valid CDI time types are TAXIS_ABSOLUTE and TAXIS_RELATIVE.
*/
int
taxisInqType(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->type;
}

int
taxisHasBounds(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return (int) taxisptr->hasBounds;
}

void
taxisWithBounds(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->hasBounds == false)
  {
    taxisptr->hasBounds = true;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDeleteBounds(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->hasBounds)
  {
    taxisptr->hasBounds = false;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisCopyTimestep(int taxisID2, int taxisID1)
{
  taxis_t *taxisptr1 = (taxis_t *) reshGetVal(taxisID1, &taxisOps), *taxisptr2 = (taxis_t *) reshGetVal(taxisID2, &taxisOps);

  reshLock();

  // reference date/time and units can't be changed after streamDefVlist()!

  taxisptr2->sDateTime = taxisptr1->sDateTime;
  taxisptr2->vDateTime = taxisptr1->vDateTime;
  taxisptr2->fDateTime = taxisptr1->fDateTime;

  if (taxisptr2->hasBounds)
  {
    taxisptr2->vDateTime_lb = taxisptr1->vDateTime_lb;
    taxisptr2->vDateTime_ub = taxisptr1->vDateTime_ub;
  }

  taxisptr2->fc_unit = taxisptr1->fc_unit;
  taxisptr2->fc_period = taxisptr1->fc_period;

  reshSetStatus(taxisID2, &taxisOps, RESH_DESYNC_IN_USE);
  reshUnlock();
}

CdiDateTime
taxisInqVdatetime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->vDateTime;
}

CdiDateTime
taxisInqRdatetime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNull(taxisptr->rDateTime))
  {
    taxisptr->rDateTime = taxisptr->vDateTime;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }

  return taxisptr->rDateTime;
}

/*
@Function  taxisInqVdate
@Title     Get the verification date

@Prototype int taxisInqVdate(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqVdate} returns the verification date of a Time axis.

@Result
@func{taxisInqVdate} returns the verification date.

@EndFunction
*/
int
taxisInqVdate(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return (int) cdiDate_get(taxisptr->vDateTime.date);
}

CdiDateTime
taxisInqSdatetime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->sDateTime;
}

void
taxisInqVdateBounds(int taxisID, int *vdate_lb, int *vdate_ub)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  *vdate_lb = (int) cdiDate_get(taxisptr->vDateTime_lb.date);
  *vdate_ub = (int) cdiDate_get(taxisptr->vDateTime_ub.date);
}

void
taxisInqVdatetimeBounds(int taxisID, CdiDateTime *vDateTime_lb, CdiDateTime *vDateTime_ub)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  *vDateTime_lb = taxisptr->vDateTime_lb;
  *vDateTime_ub = taxisptr->vDateTime_ub;
}

void
taxisDefVdateBounds(int taxisID, int vdate_lb, int vdate_ub)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->hasBounds == false || (int) cdiDate_get(taxisptr->vDateTime_lb.date) != vdate_lb
      || (int) cdiDate_get(taxisptr->vDateTime_ub.date) != vdate_ub)
  {
    taxisptr->vDateTime_lb.date = cdiDate_set(vdate_lb);
    taxisptr->vDateTime_ub.date = cdiDate_set(vdate_ub);
    taxisptr->hasBounds = true;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

void
taxisDefVdatetimeBounds(int taxisID, CdiDateTime vDateTime_lb, CdiDateTime vDateTime_ub)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->hasBounds == false || cdiDateTime_isNE(taxisptr->vDateTime_lb, vDateTime_lb)
      || cdiDateTime_isNE(taxisptr->vDateTime_ub, vDateTime_ub))
  {
    taxisptr->vDateTime_lb = vDateTime_lb;
    taxisptr->vDateTime_ub = vDateTime_ub;
    taxisptr->hasBounds = true;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisInqVtime
@Title     Get the verification time

@Prototype int taxisInqVtime(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqVtime} returns the verification time of a Time axis.

@Result
@func{taxisInqVtime} returns the verification time.

@EndFunction
*/
int
taxisInqVtime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return cdiTime_get(taxisptr->vDateTime.time);
}

void
taxisInqVtimeBounds(int taxisID, int *vtime_lb, int *vtime_ub)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  *vtime_lb = cdiTime_get(taxisptr->vDateTime_lb.time);
  *vtime_ub = cdiTime_get(taxisptr->vDateTime_ub.time);
}

void
taxisDefVtimeBounds(int taxisID, int vtime_lb, int vtime_ub)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (taxisptr->hasBounds == false || cdiTime_get(taxisptr->vDateTime_lb.time) != vtime_lb
      || cdiTime_get(taxisptr->vDateTime_ub.time) != vtime_ub)
  {
    taxisptr->vDateTime_lb.time = cdiTime_set(vtime_lb);
    taxisptr->vDateTime_ub.time = cdiTime_set(vtime_ub);
    taxisptr->hasBounds = true;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  taxisInqRdate
@Title     Get the reference date

@Prototype int taxisInqRdate(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqRdate} returns the reference date of a Time axis.

@Result
@func{taxisInqRdate} returns the reference date.

@EndFunction
*/
int
taxisInqRdate(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNull(taxisptr->rDateTime))
  {
    taxisptr->rDateTime = taxisptr->vDateTime;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }

  return (int) cdiDate_get(taxisptr->rDateTime.date);
}

/*
@Function  taxisInqRtime
@Title     Get the reference time

@Prototype int taxisInqRtime(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqRtime} returns the reference time of a Time axis.

@Result
@func{taxisInqRtime} returns the reference time.

@EndFunction
*/
int
taxisInqRtime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNull(taxisptr->rDateTime))
  {
    taxisptr->rDateTime = taxisptr->vDateTime;
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }

  return cdiTime_get(taxisptr->rDateTime.time);
}

/*
@Function  taxisInqFdatetime
@Title     Get the forecast reference date/time

@Prototype int taxisInqFdatetime(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqFdatetime} returns the forecast reference date/time of a Time axis.

@Result
@func{taxisInqFdate} returns the forecast reference date/time.

@EndFunction
*/
CdiDateTime
taxisInqFdatetime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);

  if (cdiDateTime_isNull(taxisptr->fDateTime))
  {
    // rDateTime is initialized from vDateTime if empty!
    taxisptr->fDateTime = taxisInqRdatetime(taxisID);
    reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
  }

  return taxisptr->fDateTime;
}

/*
@Function  taxisInqCalendar
@Title     Get the calendar

@Prototype int taxisInqCalendar(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqCalendar} returns the calendar of a Time axis.

@Result
@func{taxisInqCalendar} returns the type of the calendar,
one of the set of predefined CDI calendar types.
The valid CDI calendar types are @func{CALENDAR_STANDARD}, @func{CALENDAR_PROLEPTIC},
@func{CALENDAR_360DAYS}, @func{CALENDAR_365DAYS} and @func{CALENDAR_366DAYS}.

@EndFunction
*/
int
taxisInqCalendar(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->calendar;
}

int
taxisInqTunit(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->unit;
}

int
taxisInqForecastTunit(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->fc_unit;
}

double
taxisInqForecastPeriod(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->fc_period;
}

int
taxisInqNumavg(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr->numavg;
}

taxis_t *
taxis_to_pointer(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *) reshGetVal(taxisID, &taxisOps);
  return taxisptr;
}

void
ptaxisDefDatatype(taxis_t *taxisptr, int datatype)
{
  cdiDefVarKeyInt(&taxisptr->keys, CDI_KEY_DATATYPE, datatype);
}

void
ptaxisDefName(taxis_t *taxisptr, const char *name)
{
  if (name)
  {
    size_t len = strlen(name);
    delete_refcount_string(taxisptr->name);
    char *taxisname = taxisptr->name = new_refcount_string(len);
    strcpy(taxisname, name);
  }
}

void
ptaxisDefLongname(taxis_t *taxisptr, const char *longname)
{
  if (longname)
  {
    size_t len = strlen(longname);
    delete_refcount_string(taxisptr->longname);
    char *taxislongname = taxisptr->longname = new_refcount_string(len);
    strcpy(taxislongname, longname);
  }
}

char *
ptaxisAllocUnits(taxis_t *taxisptr, size_t len)
{
  delete_refcount_string(taxisptr->units);
  return taxisptr->units = new_refcount_string(len);
}

void
ptaxisDefUnits(taxis_t *taxisptr, const char *units)
{
  if (units)
  {
    size_t len = strlen(units);
    char *taxisunits = ptaxisAllocUnits(taxisptr, len);
    strcpy(taxisunits, units);
  }
}

static JulianDate
timevalue_decode(int timeunits, double timevalue)
{
  JulianDate julianDate;
  julianDate.julianDay = 0;
  julianDate.secondOfDay = 0.0;

  if (timeunits == TUNIT_MINUTE)
  {
    timevalue *= 60;
    timeunits = TUNIT_SECOND;
  }
  else if (timeunits == TUNIT_HOUR)
  {
    timevalue /= 24;
    timeunits = TUNIT_DAY;
  }

  if (timeunits == TUNIT_SECOND)
  {
    julianDate.julianDay = (int64_t) (timevalue / 86400.0);
    double seconds = fmod(timevalue, 86400.0);
    /* FIXME: does the following line serve a definable purpose? */
    julianDate.secondOfDay = round(seconds * 1000.0) / 1000.0;
    if (julianDate.secondOfDay < 0)
    {
      julianDate.julianDay -= 1;
      julianDate.secondOfDay += 86400.0;
    };
    /*
    {
      double cval = julianDate.julianDay * 86400.0 + julianDate.secondOfDay;
      if (cval != timevalue) printf("TUNIT_SECOND error: %g %g %d %d\n", timevalue, cval, julianDate.julianDay,
    julianDate.secondOfDay);
    }
    */
  }
  else if (timeunits == TUNIT_DAY)
  {
    julianDate.julianDay = (int64_t) timevalue;
    double days, seconds = modf(timevalue, &days) * 86400.0;
    /* FIXME: why does the seconds value have to be integral? If that's
     * needed why not use modf? */
    julianDate.secondOfDay = (int) lround(seconds);
    if (julianDate.secondOfDay < 0)
    {
      julianDate.julianDay -= 1;
      julianDate.secondOfDay += 86400.0;
    };
    /*
    {
      double cval = julianDate.julianDay + julianDate.secondOfDay / 86400.0;
      if (cval != timevalue) printf("TUNIT_DAY error: %g %g %d %d\n", timevalue, cval, julianDate.julianDay,
    julianDate.secondOfDay);
    }
    */
  }
  else
  {
    static bool lwarn = true;
    if (lwarn)
    {
      Warning("timeunit %s unsupported!", tunitNamePtr(timeunits));
      lwarn = false;
    }
  }

  return julianDate;
}

static double
cdi_encode_timevalue(int64_t days, double secs, int timeunit)
{
  double timevalue = 0.0;

  if (timeunit == TUNIT_SECOND) { timevalue = (double) days * 86400.0 + secs; }
  else if (timeunit == TUNIT_MINUTE || timeunit == TUNIT_QUARTER || timeunit == TUNIT_30MINUTES)
  {
    timevalue = (double) days * 1440. + secs / 60.;
  }
  else if (timeunit == TUNIT_HOUR || timeunit == TUNIT_3HOURS || timeunit == TUNIT_6HOURS || timeunit == TUNIT_12HOURS)
  {
    timevalue = (double) days * 24. + secs / 3600.;
  }
  else if (timeunit == TUNIT_DAY) { timevalue = (double) days + secs / 86400.; }
  else
  {
    static bool lwarn = true;
    if (lwarn)
    {
      Warning("timeunit %s unsupported!", tunitNamePtr(timeunit));
      lwarn = false;
    }
  }

  return timevalue;
}

// convert relative time value to CdiDateTime
static CdiDateTime
rtimeval2datetime(double timevalue, const taxis_t *taxis)
{
  if (IS_EQUAL(timevalue, 0.0)) return taxis->rDateTime;

  int timeunits = taxis->unit;
  int calendar = taxis->calendar;

  if (timeunits == TUNIT_MONTH && calendar == CALENDAR_360DAYS)
  {
    timeunits = TUNIT_DAY;
    timevalue *= 30;
  }

  CdiDateTime rDateTime = taxis->rDateTime;

  if (timeunits == TUNIT_MONTH || timeunits == TUNIT_YEAR)
  {
    int year = rDateTime.date.year;
    int month = rDateTime.date.month;

    if (timeunits == TUNIT_YEAR) timevalue *= 12;

    double fmon = fmod(timevalue, 1.0);

    month += (int) timevalue;

    struct YearMonth ym = normalize_month(year, month);
    year = ym.year;
    month = ym.month;

    timeunits = TUNIT_DAY;
    timevalue = fmon * days_per_month(calendar, year, month);

    rDateTime.date.year = year;
    rDateTime.date.month = (short) month;
  }

  JulianDate julianDate = julianDate_encode(calendar, rDateTime);
  JulianDate julianDate2 = timevalue_decode(timeunits, timevalue);

  return julianDate_decode(calendar, julianDate_add(julianDate2, julianDate));
}

// convert CdiDateTime to relative time value
static double
datetime2rtimeval(CdiDateTime vDateTime, const taxis_t *taxis)
{
  double value = 0.0;

  int calendar = taxis->calendar;
  int timeunits = taxis->unit;
  int timeunits0 = timeunits;

  CdiDateTime rDateTime = taxis->rDateTime;

  if (cdiDateTime_isNull(rDateTime)) rDateTime = (*taxis).vDateTime;

  if (cdiDateTime_isNull(rDateTime) && cdiDateTime_isNull(vDateTime)) return value;

  JulianDate julianDate1 = julianDate_encode(calendar, rDateTime);

  if (timeunits == TUNIT_MONTH && calendar == CALENDAR_360DAYS) timeunits = TUNIT_DAY;

  if (timeunits == TUNIT_MONTH || timeunits == TUNIT_YEAR)
  {
    int ryear = rDateTime.date.year;
    int rmonth = rDateTime.date.month;
    int year = vDateTime.date.year;
    int month = vDateTime.date.month;
    value = (year - ryear) * 12 - rmonth + month;

    int nmonth = (int) value;
    month -= nmonth;

    struct YearMonth ym = normalize_month(year, month);
    year = ym.year;
    month = ym.month;

    int dpm = days_per_month(calendar, year, month);

    vDateTime.date.year = year;
    vDateTime.date.month = (short) month;
    JulianDate julianDate2 = julianDate_encode(calendar, vDateTime);
    JulianDate dateDifference = julianDate_sub(julianDate2, julianDate1);

    value += ((double) dateDifference.julianDay + dateDifference.secondOfDay / 86400.0) / dpm;
    if (timeunits == TUNIT_YEAR) value = value / 12;
  }
  else
  {
    JulianDate julianDate2 = julianDate_encode(calendar, vDateTime);
    JulianDate dateDifference = julianDate_sub(julianDate2, julianDate1);

    value = cdi_encode_timevalue(dateDifference.julianDay, dateDifference.secondOfDay, timeunits);
  }

  if (timeunits0 == TUNIT_MONTH && calendar == CALENDAR_360DAYS) value /= 30.0;

  return value;
}

// convert absolute seconds to CdiDateTime
static CdiDateTime
seconds2datetime(double timevalue)
{
  int calendar = CALENDAR_STANDARD;
  int64_t seconds = (int64_t) timevalue;

  CdiDateTime datetime0;
  datetime0.date = cdiDate_encode(1, 1, 1);
  datetime0.time = cdiTime_encode(0, 0, 0, 0);
  JulianDate julianDate = julianDate_encode(calendar, datetime0);

  return julianDate_decode(calendar, julianDate_add_seconds(julianDate, seconds));
}

// convert absolute time value to CdiDateTime
static CdiDateTime
absTimeval2datetime(double timevalue)
{
  int64_t vdate = (int64_t) timevalue;
  double days, tmpval = modf(timevalue, &days) * 86400.0;
  /* FIXME: why +0.01? */
  int daysec = (vdate < 0) ? (int) (-tmpval + 0.01) : (int) (tmpval + 0.01);

  CdiDateTime datetime;
  datetime.date = cdiDate_set(vdate);
  int hour = daysec / 3600;
  int minute = (daysec - hour * 3600) / 60;
  int second = daysec - hour * 3600 - minute * 60;
  int ms = 0;
  datetime.time = cdiTime_encode(hour, minute, second, ms);

  return datetime;
}

static CdiDateTime
split_timevalue(double timevalue, int timeunit)
{
  CdiDateTime datetime;
  cdiDateTime_init(&datetime);

  if (timeunit == TUNIT_SECOND) { datetime = seconds2datetime(timevalue); }
  else if (timeunit == TUNIT_HOUR)
  {
    timevalue /= 24;
    datetime = absTimeval2datetime(timevalue);
  }
  else if (timeunit == TUNIT_DAY) { datetime = absTimeval2datetime(timevalue); }
  else if (timeunit == TUNIT_MONTH)
  {
    int64_t vdate = (int64_t) timevalue * 100 - ((timevalue < 0) * 2 - 1);
    datetime.date = cdiDate_set(vdate);
  }
  else if (timeunit == TUNIT_YEAR)
  {
    {
      static bool lwarn = true;
      if (lwarn && (fabs(timevalue - trunc(timevalue)) > 0))
      {
        Warning("Fraction of a year is not supported!!");
        lwarn = false;
      }
    }

    {
      static bool lwarn = true;
      if (timevalue < -214700)
      {
        if (lwarn)
        {
          Warning("Year %g out of range, set to -214700", timevalue);
          lwarn = false;
        }
        timevalue = -214700;
      }
      else if (timevalue > 214700)
      {
        if (lwarn)
        {
          Warning("Year %g out of range, set to 214700", timevalue);
          lwarn = false;
        }
        timevalue = 214700;
      }
    }

    int64_t vdate = (int64_t) timevalue * 10000;
    vdate += (timevalue < 0) ? -101 : 101;
    datetime.date = cdiDate_set(vdate);
  }
  else
  {
    static bool lwarn = true;
    if (lwarn)
    {
      Warning("timeunit %s unsupported!", tunitNamePtr(timeunit));
      lwarn = false;
    }
  }

  // verify date and time

  int year, month, day;
  cdiDate_decode(datetime.date, &year, &month, &day);
  int hour, minute, second, ms;
  cdiTime_decode(datetime.time, &hour, &minute, &second, &ms);

  if (month > 17 || day > 31 || hour > 23 || minute > 59 || second > 59)
  {
    if ((month > 17 || day > 31) && (year < -9999 || year > 9999)) year = 1;
    if (month > 17) month = 1;
    if (day > 31) day = 1;
    if (hour > 23) hour = 0;
    if (minute > 59) minute = 0;
    if (second > 59) second = 0;

    datetime.date = cdiDate_encode(year, month, day);
    datetime.time = cdiTime_encode(hour, minute, second, ms);

    static bool lwarn = true;
    if (lwarn)
    {
      lwarn = false;
      Warning("Reset wrong date/time to %4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d!", year, month, day, hour, minute, second);
    }
  }

  return datetime;
}

void
cdi_set_forecast_period(double timevalue, taxis_t *taxis)
{
  taxis->fc_period = timevalue;

  int timeunits = taxis->fc_unit;
  int calendar = taxis->calendar;

  if (cdiDateTime_isNull(taxis->vDateTime) && DBL_IS_EQUAL(timevalue, 0.0)) return;

  if (timeunits == TUNIT_MONTH && calendar == CALENDAR_360DAYS)
  {
    timeunits = TUNIT_DAY;
    timevalue *= 30;
  }

  CdiDateTime vDateTime = taxis->vDateTime;

  if (timeunits == TUNIT_MONTH || timeunits == TUNIT_YEAR)
  {
    int year = vDateTime.date.year;
    int month = vDateTime.date.month;

    if (timeunits == TUNIT_YEAR) timevalue *= 12;

    double fmon = fmod(timevalue, 1.0);

    month -= (int) timevalue;

    struct YearMonth ym = normalize_month(year, month);
    year = ym.year;
    month = ym.month;

    timeunits = TUNIT_DAY;
    timevalue = fmon * days_per_month(calendar, year, month);

    vDateTime.date.year = year;
    vDateTime.date.month = (short) month;
  }

  JulianDate julianDate = julianDate_encode(calendar, vDateTime);
  JulianDate julianDate2 = timevalue_decode(timeunits, timevalue);

  taxis->fDateTime = julianDate_decode(calendar, julianDate_sub(julianDate, julianDate2));
}

CdiDateTime
cdi_decode_timeval(double timevalue, const taxis_t *taxis)
{
  return (taxis->type == TAXIS_ABSOLUTE) ? split_timevalue(timevalue, taxis->unit) : rtimeval2datetime(timevalue, taxis);
}

/*
static int64_t
datetime2seconds(CdiDateTime datetime)
{
  int calendar = CALENDAR_STANDARD;

  CdiDateTime datetime0;
  datetime0.date = cdiDate_encode(1, 1, 1);
  datetime0.time = cdiTime_encode(0, 0, 0, 0);
  JulianDate julianDate0 = julianDate_encode(calendar, datetime0);
  JulianDate julianDate = julianDate_encode(calendar, datetime);

  int64_t days = julianDate.julianDay - julianDate0.julianDay;
  int64_t seconds = days * 86400 + (int64_t) julianDate.secondOfDay;

  return seconds;
}
*/
double
cdi_encode_timeval(CdiDateTime datetime, taxis_t *taxis)
{
  double timeValue = 0.0;

  if (taxis->type == TAXIS_ABSOLUTE)
  {
    if (taxis->unit == TUNIT_YEAR) { timeValue = datetime.date.year; }
    else if (taxis->unit == TUNIT_MONTH)
    {
      int64_t xdate = cdiDate_get(datetime.date);
      timeValue = (double) xdate / 100 + copysign((double) (datetime.date.day != 0) * 0.5, (double) xdate);
    }
    /*
    else if (taxis->unit == TUNIT_SECOND)
      {
        timeValue = (double) datetime2seconds(datetime);
      }
    */
    else
    {
      int hour, minute, second, ms;
      cdiTime_decode(datetime.time, &hour, &minute, &second, &ms);
      int64_t xdate = cdiDate_get(datetime.date);
      timeValue = copysign(1.0, (double) xdate) * (fabs((double) xdate) + (hour * 3600 + minute * 60 + second) / 86400.0);
    }
  }
  else
    timeValue = datetime2rtimeval(datetime, taxis);

  return timeValue;
}

void
ptaxisCopy(taxis_t *dest, taxis_t *source)
{
  reshLock();

  // memcpy(dest, source, sizeof(taxis_t));
  dest->type = source->type;
  dest->calendar = source->calendar;
  dest->unit = source->unit;
  dest->numavg = source->numavg;
  dest->climatology = source->climatology;
  dest->hasBounds = source->hasBounds;
  dest->sDateTime = source->sDateTime;
  dest->vDateTime = source->vDateTime;
  dest->rDateTime = source->rDateTime;
  dest->fDateTime = source->fDateTime;
  dest->vDateTime_lb = source->vDateTime_lb;
  dest->vDateTime_ub = source->vDateTime_ub;
  dest->fc_unit = source->fc_unit;
  dest->fc_period = source->fc_period;

  dest->climatology = source->climatology;
  delete_refcount_string(dest->name);
  delete_refcount_string(dest->longname);
  delete_refcount_string(dest->units);
  dest->name = dup_refcount_string(source->name);
  dest->longname = dup_refcount_string(source->longname);
  dest->units = dup_refcount_string(source->units);
  if (dest->self != CDI_UNDEFID) reshSetStatus(dest->self, &taxisOps, RESH_DESYNC_IN_USE);

  cdiInitKeys(&dest->keys);
  cdiCopyVarKeys(&source->keys, &dest->keys);

  reshUnlock();
}

static void
taxisPrintKernel(taxis_t *taxisptr, FILE *fp)
{
  int vdate_lb, vdate_ub;
  int vtime_lb, vtime_ub;

  taxisInqVdateBounds(taxisptr->self, &vdate_lb, &vdate_ub);
  taxisInqVtimeBounds(taxisptr->self, &vtime_lb, &vtime_ub);

  fprintf(fp,
          "#\n"
          "# taxisID %d\n"
          "#\n"
          "self        = %d\n"
          "type        = %d\n"
          "vdate       = %d\n"
          "vtime       = %d\n"
          "rdate       = %d\n"
          "rtime       = %d\n"
          "fdate       = %d\n"
          "ftime       = %d\n"
          "calendar    = %d\n"
          "unit        = %d\n"
          "numavg      = %d\n"
          "climatology = %d\n"
          "hasBounds   = %d\n"
          "vdate_lb    = %d\n"
          "vtime_lb    = %d\n"
          "vdate_ub    = %d\n"
          "vtime_ub    = %d\n"
          "fc_unit     = %d\n"
          "fc_period   = %g\n"
          "\n",
          taxisptr->self, taxisptr->self, taxisptr->type, (int) cdiDate_get(taxisptr->vDateTime.date),
          cdiTime_get(taxisptr->vDateTime.time), (int) cdiDate_get(taxisptr->rDateTime.date), cdiTime_get(taxisptr->rDateTime.time),
          (int) cdiDate_get(taxisptr->fDateTime.date), cdiTime_get(taxisptr->fDateTime.time), taxisptr->calendar, taxisptr->unit,
          taxisptr->numavg, (int) taxisptr->climatology, (int) taxisptr->hasBounds, vdate_lb, vtime_lb, vdate_ub, vtime_ub,
          taxisptr->fc_unit, taxisptr->fc_period);
}

static int
taxisCompareP(void *taxisptr1, void *taxisptr2)
{
  const taxis_t *t1 = (const taxis_t *) taxisptr1, *t2 = (const taxis_t *) taxisptr2;

  xassert(t1 && t2);

  return !(t1->type == t2->type && cdiDateTime_isEQ(t1->vDateTime, t2->vDateTime) && cdiDateTime_isEQ(t1->rDateTime, t2->rDateTime)
           && cdiDateTime_isEQ(t1->fDateTime, t2->fDateTime) && t1->calendar == t2->calendar && t1->unit == t2->unit
           && t1->fc_unit == t2->fc_unit && IS_EQUAL(t1->fc_period, t2->fc_period) && t1->numavg == t2->numavg
           && t1->climatology == t2->climatology && t1->hasBounds == t2->hasBounds
           && cdiDateTime_isEQ(t1->vDateTime_lb, t2->vDateTime_lb) && cdiDateTime_isEQ(t1->vDateTime_ub, t2->vDateTime_ub));
}

static int
taxisTxCode(void *taxisptr)
{
  (void) taxisptr;
  return TAXIS;
}

enum
{
  TAXIS_PACK_INT_SELF,
  TAXIS_PACK_INT_TYPE,
  TAXIS_PACK_INT_VDATE,
  TAXIS_PACK_INT_VTIME,
  TAXIS_PACK_INT_RDATE,
  TAXIS_PACK_INT_RTIME,
  TAXIS_PACK_INT_FDATE,
  TAXIS_PACK_INT_FTIME,
  TAXIS_PACK_INT_CALENDAR,
  TAXIS_PACK_INT_UNIT,
  TAXIS_PACK_INT_FC_UNIT,
  TAXIS_PACK_INT_NUMAVG,
  TAXIS_PACK_INT_CLIMATOLOGY,
  TAXIS_PACK_INT_HAS_BOUNDS,
  TAXIS_PACK_INT_VDATE_LB,
  TAXIS_PACK_INT_VDATE_UB,
  TAXIS_PACK_INT_VTIME_LB,
  TAXIS_PACK_INT_VTIME_UB,
  TAXIS_PACK_INT_NAMELEN,
  TAXIS_PACK_INT_LNAMELEN,
  TAXIS_PACK_INT_UNITSLEN,
  taxisNint
};

enum
{
  TAXIS_PACK_FC_PERIOD,
  taxisNdouble
};

static int
taxisGetPackSize(void *p, void *context)
{
  taxis_t *taxisptr = (taxis_t *) p;
  int packBufferSize = serializeGetSize(taxisNint, CDI_DATATYPE_INT, context)
                       + serializeGetSize(taxisNdouble, CDI_DATATYPE_FLT64, context)
                       + (taxisptr->name ? serializeGetSize((int) strlen(taxisptr->name), CDI_DATATYPE_TXT, context) : 0)
                       + (taxisptr->longname ? serializeGetSize((int) strlen(taxisptr->longname), CDI_DATATYPE_TXT, context) : 0)
                       + (taxisptr->units ? serializeGetSize((int) strlen(taxisptr->units), CDI_DATATYPE_TXT, context) : 0)
                       + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
  packBufferSize += serializeKeysGetPackSize(&taxisptr->keys, context);
  return packBufferSize;
}

int
taxisUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id)
{
#define adaptKey(key) (namespaceAdaptKey((key), originNamespace))
  taxis_t *taxisP;
  int intBuffer[taxisNint];
  double dblBuffer[taxisNdouble];
  uint32_t d;

  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, intBuffer, taxisNint, CDI_DATATYPE_INT, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, dblBuffer, taxisNdouble, CDI_DATATYPE_FLT64, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);

  xassert(cdiCheckSum(CDI_DATATYPE_INT, taxisNint, intBuffer) == d);

  cdiResH targetID = force_id ? adaptKey(intBuffer[TAXIS_PACK_INT_SELF]) : CDI_UNDEFID;
  taxisP = taxisNewEntry(targetID);

  xassert(!force_id || targetID == taxisP->self);

  taxisP->type = intBuffer[TAXIS_PACK_INT_TYPE];
  taxisP->vDateTime.date = cdiDate_set(intBuffer[TAXIS_PACK_INT_VDATE]);
  taxisP->vDateTime.time = cdiTime_set(intBuffer[TAXIS_PACK_INT_VTIME]);
  taxisP->rDateTime.date = cdiDate_set(intBuffer[TAXIS_PACK_INT_RDATE]);
  taxisP->rDateTime.time = cdiTime_set(intBuffer[TAXIS_PACK_INT_RTIME]);
  taxisP->fDateTime.date = cdiDate_set(intBuffer[TAXIS_PACK_INT_FDATE]);
  taxisP->fDateTime.time = cdiTime_set(intBuffer[TAXIS_PACK_INT_FTIME]);
  taxisP->calendar = intBuffer[TAXIS_PACK_INT_CALENDAR];
  taxisP->unit = intBuffer[TAXIS_PACK_INT_UNIT];
  taxisP->fc_unit = intBuffer[TAXIS_PACK_INT_FC_UNIT];
  taxisP->numavg = intBuffer[TAXIS_PACK_INT_NUMAVG];
  taxisP->climatology = intBuffer[TAXIS_PACK_INT_CLIMATOLOGY];
  taxisP->hasBounds = (bool) intBuffer[TAXIS_PACK_INT_HAS_BOUNDS];
  taxisP->vDateTime_lb.date = cdiDate_set(intBuffer[TAXIS_PACK_INT_VDATE_LB]);
  taxisP->vDateTime_lb.time = cdiTime_set(intBuffer[TAXIS_PACK_INT_VDATE_UB]);
  taxisP->vDateTime_ub.date = cdiDate_set(intBuffer[TAXIS_PACK_INT_VTIME_LB]);
  taxisP->vDateTime_ub.time = cdiTime_set(intBuffer[TAXIS_PACK_INT_VTIME_UB]);
  taxisP->fc_period = dblBuffer[TAXIS_PACK_FC_PERIOD];
  if (intBuffer[TAXIS_PACK_INT_NAMELEN])
  {
    int len = intBuffer[TAXIS_PACK_INT_NAMELEN];
    char *name = new_refcount_string((size_t) len);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, name, len, CDI_DATATYPE_TXT, context);
    name[len] = '\0';
    taxisP->name = name;
  }
  if (intBuffer[TAXIS_PACK_INT_LNAMELEN])
  {
    int len = intBuffer[TAXIS_PACK_INT_LNAMELEN];
    char *longname = new_refcount_string((size_t) len);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, longname, len, CDI_DATATYPE_TXT, context);
    longname[len] = '\0';
    taxisP->longname = longname;
  }
  if (intBuffer[TAXIS_PACK_INT_UNITSLEN])
  {
    int len = intBuffer[TAXIS_PACK_INT_UNITSLEN];
    char *units = new_refcount_string((size_t) len);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, units, len, CDI_DATATYPE_TXT, context);
    units[len] = '\0';
    taxisP->units = units;
  }

  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &taxisP->keys, context);

  reshSetStatus(taxisP->self, &taxisOps, reshGetStatus(taxisP->self, &taxisOps) & ~RESH_SYNC_BIT);
#undef adaptKey

  return taxisP->self;
}

static void
taxisPack(void *voidP, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  taxis_t *taxisP = (taxis_t *) voidP;
  int nameLen, lnameLen, unitsLen;
  uint32_t d;

  {
    int intBuffer[taxisNint];
    intBuffer[TAXIS_PACK_INT_SELF] = taxisP->self;
    intBuffer[TAXIS_PACK_INT_TYPE] = taxisP->type;
    intBuffer[TAXIS_PACK_INT_VDATE] = (int) cdiDate_get(taxisP->vDateTime.date);
    intBuffer[TAXIS_PACK_INT_VTIME] = cdiTime_get(taxisP->vDateTime.time);
    intBuffer[TAXIS_PACK_INT_RDATE] = (int) cdiDate_get(taxisP->rDateTime.date);
    intBuffer[TAXIS_PACK_INT_RTIME] = cdiTime_get(taxisP->rDateTime.time);
    intBuffer[TAXIS_PACK_INT_FDATE] = (int) cdiDate_get(taxisP->fDateTime.date);
    intBuffer[TAXIS_PACK_INT_FTIME] = cdiTime_get(taxisP->fDateTime.time);
    intBuffer[TAXIS_PACK_INT_CALENDAR] = taxisP->calendar;
    intBuffer[TAXIS_PACK_INT_UNIT] = taxisP->unit;
    intBuffer[TAXIS_PACK_INT_FC_UNIT] = taxisP->fc_unit;
    intBuffer[TAXIS_PACK_INT_NUMAVG] = taxisP->numavg;
    intBuffer[TAXIS_PACK_INT_CLIMATOLOGY] = taxisP->climatology;
    intBuffer[TAXIS_PACK_INT_HAS_BOUNDS] = taxisP->hasBounds;
    intBuffer[TAXIS_PACK_INT_VDATE_LB] = (int) cdiDate_get(taxisP->vDateTime_lb.date);
    intBuffer[TAXIS_PACK_INT_VDATE_UB] = cdiTime_get(taxisP->vDateTime_lb.time);
    intBuffer[TAXIS_PACK_INT_VTIME_LB] = (int) cdiDate_get(taxisP->vDateTime_ub.date);
    intBuffer[TAXIS_PACK_INT_VTIME_UB] = cdiTime_get(taxisP->vDateTime_ub.time);
    intBuffer[TAXIS_PACK_INT_NAMELEN] = nameLen = taxisP->name ? (int) strlen(taxisP->name) : 0;
    intBuffer[TAXIS_PACK_INT_LNAMELEN] = lnameLen = taxisP->longname ? (int) strlen(taxisP->longname) : 0;
    intBuffer[TAXIS_PACK_INT_UNITSLEN] = unitsLen = taxisP->units ? (int) strlen(taxisP->units) : 0;
    serializePack(intBuffer, taxisNint, CDI_DATATYPE_INT, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_INT, taxisNint, intBuffer);
  }

  {
    double dblBuffer[taxisNdouble];
    dblBuffer[TAXIS_PACK_FC_PERIOD] = taxisP->fc_period;
    serializePack(dblBuffer, taxisNdouble, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
  }
  serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  if (taxisP->name) serializePack(taxisP->name, nameLen, CDI_DATATYPE_TXT, packBuffer, packBufferSize, packBufferPos, context);
  if (taxisP->longname)
    serializePack(taxisP->longname, lnameLen, CDI_DATATYPE_TXT, packBuffer, packBufferSize, packBufferPos, context);
  if (taxisP->units) serializePack(taxisP->units, unitsLen, CDI_DATATYPE_TXT, packBuffer, packBufferSize, packBufferPos, context);

  serializeKeysPack(&taxisP->keys, packBuffer, packBufferSize, packBufferPos, context);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <limits.h>


static void
tstepsInitEntry(tsteps_t *tstep)
{
  tstep->recIDs = NULL;
  tstep->recinfo = NULL;
  tstep->records = NULL;
  tstep->recordSize = 0;
  tstep->nrecs = 0;
  tstep->curRecID = CDI_UNDEFID;
  tstep->ncStepIndex = 0;
  tstep->position = 0;
  tstep->nallrecs = 0;
  tstep->next = 0;

  ptaxisInit(&(tstep->taxis));
}

int
tstepsNewEntry(stream_t *streamptr)
{
  int tsID = streamptr->tstepsNextID++;
  int tstepsTableSize = streamptr->tstepsTableSize;
  tsteps_t *tstepsTable = streamptr->tsteps;

  // If the table overflows, double its size.
  if (tsID == tstepsTableSize)
  {
    if (tstepsTableSize == 0) tstepsTableSize = 1;
    if (tstepsTableSize <= INT_MAX / 2)
      tstepsTableSize *= 2;
    else if (tstepsTableSize < INT_MAX)
      tstepsTableSize = INT_MAX;
    else
      Error("Resizing of tstep table failed!");

    tstepsTable = (tsteps_t *) Realloc(tstepsTable, (size_t) tstepsTableSize * sizeof(tsteps_t));
  }

  streamptr->tstepsTableSize = tstepsTableSize;
  streamptr->tsteps = tstepsTable;

  tsteps_t *curTstep = &streamptr->tsteps[tsID];
  tstepsInitEntry(curTstep);

  return tsID;
}

void
cdi_create_timesteps(size_t numTimesteps, stream_t *streamptr)
{
  streamptr->ntsteps = (long) numTimesteps;
  if (streamptr->tstepsTableSize > 0) return;

  size_t ntsteps = (numTimesteps == 0) ? 1 : numTimesteps;

  streamptr->tsteps = (tsteps_t *) Malloc(ntsteps * sizeof(tsteps_t));

  streamptr->tstepsTableSize = (int) ntsteps;
  streamptr->tstepsNextID = (int) ntsteps;

  for (size_t tsID = 0; tsID < ntsteps; tsID++) { tstepsInitEntry(&streamptr->tsteps[tsID]); }
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#define _XOPEN_SOURCE 600

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static const char uuidFmt[] = "%02hhx%02hhx%02hhx%02hhx-"
                              "%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx-"
                              "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx";

int
cdiUUID2Str(const unsigned char *uuid, char *uuidstr)
{
  if (uuid == NULL || uuidstr == NULL) return 0;

  int iret = snprintf(uuidstr, uuidNumHexChars + 1, uuidFmt, uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
                      uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]);

  if (iret != uuidNumHexChars)
  {
    uuidstr[0] = 0;
    iret = -1;
  }

  return iret;
}

int
cdiStr2UUID(const char *uuidstr, unsigned char *uuid)
{
  if (uuid == NULL || uuidstr == NULL || strlen(uuidstr) != uuidNumHexChars) return -1;

  int iret = sscanf(uuidstr, uuidFmt, &uuid[0], &uuid[1], &uuid[2], &uuid[3], &uuid[4], &uuid[5], &uuid[6], &uuid[7], &uuid[8],
                    &uuid[9], &uuid[10], &uuid[11], &uuid[12], &uuid[13], &uuid[14], &uuid[15]);
  if (iret != CDI_UUID_SIZE) return -1;

  return iret;
}

// Returns a malloc'ed string that escapes all spaces and backslashes with backslashes.
char *
cdiEscapeSpaces(const char *string)
{
  // How much memory do we need?
  size_t escapeCount = 0, length = 0;
  for (; string[length]; ++length) escapeCount += string[length] == ' ' || string[length] == '\\';

  char *result = (char *) Malloc(length + escapeCount + 1);
  if (!result) return NULL;

  // Do the escaping.
  for (size_t in = 0, out = 0; in < length; ++out, ++in)
  {
    if (string[in] == ' ' || string[in] == '\\') result[out++] = '\\';
    result[out] = string[in];
  }
  result[length + escapeCount] = 0;  // termination!
  return result;
}

// input: a space terminated string that may contain escaped characters
// output: a new zero terminated string with the escape characters removed
//*outStringEnd points to the terminating character upon return.
char *
cdiUnescapeSpaces(const char *string, const char **outStringEnd)
{
  // How much memory do we need?
  size_t escapeCount = 0, length = 0;
  for (const char *current = string; *current && *current != ' '; current++)
  {
    if (*current == '\\')
    {
      current++, escapeCount++;
      if (!current) return NULL;
    }
    length++;
  }

  char *result = (char *) Malloc(length + 1);
  if (!result) return NULL;

  // Do the unescaping.
  for (size_t in = 0, out = 0; out < length;)
  {
    if (string[in] == '\\') in++;
    result[out++] = string[in++];
  }
  result[length] = 0;  // termination!
  if (outStringEnd) *outStringEnd = &string[length + escapeCount];
  return result;
}

#ifndef _WIN32
#if defined(HAVE_DECL_UUID_GENERATE) && defined(HAVE_UUID_UUID_H)
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <uuid/uuid.h>
void
cdiCreateUUID(unsigned char uuid[CDI_UUID_SIZE])
{
  static int uuid_seeded = 0;
  static char uuid_rand_state[31 * sizeof(long)];
  char *caller_rand_state;
  if (uuid_seeded)
    caller_rand_state = setstate(uuid_rand_state);
  else
  {
    struct timeval tv;
    int status = gettimeofday(&tv, NULL);
    if (status != 0)
    {
      perror("uuid random seed generation failed!");
      exit(1);
    }
    unsigned seed = (unsigned) (tv.tv_sec ^ tv.tv_usec);
    caller_rand_state = initstate(seed, uuid_rand_state, sizeof(uuid_rand_state));
    uuid_seeded = 1;
  }
  uuid_generate(uuid);
  setstate(caller_rand_state);
}
#elif defined(HAVE_DECL_UUID_CREATE) && defined(HAVE_UUID_H)
#ifdef HAVE_DECL_UUID_MAKE_V5
#include <uuid.h>
void
cdiCreateUUID(unsigned char *uuid)
{
  static const char error_stage[][16]
      = { "uuid_create", "uuid_create", "uuid_load", "uuid_make", "uuid_export", "uuid_destroy1", "uuid_destroy2" };
  uuid_t *objuuid = NULL, *nsuuid = NULL;
  int stage = 0;
  uuid_rc_t status;
  if ((status = uuid_create(&objuuid)) == UUID_RC_OK)
  {
    ++stage;
    if ((status = uuid_create(&nsuuid)) == UUID_RC_OK)
    {
      ++stage;
      if ((status = uuid_load(nsuuid, "ns:OID")) == UUID_RC_OK)
      {
        ++stage;
        if ((status = uuid_make(objuuid, UUID_MAKE_V5, nsuuid, cdiLibraryVersion())) == UUID_RC_OK)
        {
          ++stage;
          size_t datalen = CDI_UUID_SIZE;
          status = uuid_export(objuuid, UUID_FMT_BIN, &uuid, &datalen);
        }
      }
    }
  }
  if (status != UUID_RC_OK) Error("failed to generate UUID at stage %s\n", error_stage[stage]);
  stage = 5;
  if ((status = uuid_destroy(nsuuid)) != UUID_RC_OK) Error("failed to generate UUID at stage %s\n", error_stage[stage]);
  ++stage;
  if ((status = uuid_destroy(objuuid)) != UUID_RC_OK) Error("failed to generate UUID at stage %s\n", error_stage[stage]);
}
#else
#include <inttypes.h>
typedef uint8_t u_int8_t;
typedef uint16_t u_int16_t;
typedef uint32_t u_int32_t;
#include <uuid.h>
void
cdiCreateUUID(unsigned char *uuid)
{
  uint32_t status;
  uuid_create((uuid_t *) (void *) uuid, &status);
  if (status != uuid_s_ok)
  {
    perror("uuid generation failed!");
    exit(1);
  }
}
#endif
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
void
cdiCreateUUID(unsigned char *uuid)
{
  static int uuid_seeded = 0;
#ifndef _SX
  static char uuid_rand_state[31 * sizeof(long)];
  char *caller_rand_state;
  if (uuid_seeded)
    caller_rand_state = setstate(uuid_rand_state);
  else
  {
#ifdef HAVE_SYS_TIME_H
    struct timeval tv;
    int status = gettimeofday(&tv, NULL);
    if (status != 0)
    {
      perror("failed seed generation!");
      exit(1);
    }
    unsigned seed = tv.tv_sec ^ tv.tv_usec;
#else
    unsigned seed = 0;
#endif
    caller_rand_state = initstate(seed, uuid_rand_state, sizeof(uuid_rand_state));
    uuid_seeded = 1;
  }
  for (size_t i = 0; i < CDI_UUID_SIZE; ++i) uuid[i] = (unsigned char) random();
#else
  unsigned short caller_rand_state[3];
  {
    static unsigned short our_rand_state[3];
    if (!uuid_seeded)
    {
#ifdef HAVE_SYS_TIME_H
      struct timeval tv;
      int status = gettimeofday(&tv, NULL);
      if (status != 0)
      {
        perror("failed seed generation!");
        exit(1);
      }
      unsigned seed = tv.tv_sec ^ tv.tv_usec;
#else
      unsigned seed = 0;
#endif
      our_rand_state[0] = 0x330E;
      our_rand_state[1] = (unsigned short) (seed & 0xFFFFU);
      our_rand_state[2] = (unsigned short) ((seed >> 16) & 0xFFFFU);
    }
    unsigned short *p = seed48(our_rand_state);
    uuid_seeded = 1;
    memcpy(caller_rand_state, p, sizeof(caller_rand_state));
  }
  for (size_t i = 0; i < CDI_UUID_SIZE; ++i) uuid[i] = (unsigned char) lrand48();
#endif
  /* encode variant into msb of octet 8 */
  uuid[8] = (unsigned char) ((uuid[8] & 0x3f) | (1 << 7));
  /* encode version 4 ((pseudo-)random uuid) into msb of octet 7 */
  uuid[7] = (unsigned char) ((uuid[7] & 0x0f) | (4 << 4));
#ifndef _SX
  setstate(caller_rand_state);
#else
  seed48(caller_rand_state);
#endif
}
#endif
#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif


static size_t Vctsize = 0;
static double *Vct = NULL;

static int numberOfVerticalLevels = 0;
static int numberOfVerticalGrid = 0;
static unsigned char uuidVGrid[CDI_UUID_SIZE];

typedef struct
{
  int level1;
  int level2;
  int recID;
  int lindex;
} leveltable_t;

typedef struct
{
  int subtypeIndex;  //  corresponding tile in subtype_t structure (subtype->self)
  int nlevels;
  int levelTableSize;
  leveltable_t *levelTable;
} subtypetable_t;

typedef struct
{
  int varID;
  int param;
  int prec;
  int tsteptype;
  VarScanKeys scanKeys;
  int gridID;
  int zaxistype;
  int ltype1;  // GRIB first level type
  int ltype2;  // GRIB second level type
  int hasBounds;
  int level_sf;
  int level_unit;
  int zaxisID;

  int nsubtypes_alloc;
  int nsubtypes;
  subtypetable_t *recordTable;  // ~ two-dimensional record list [nsubtypes_alloc][levelTableSize]

  int instID;
  int modelID;
  int tableID;
  int comptype;   // compression type
  int complevel;  // compression level
  bool lmissval;
  double missval;
  char *name;

  // meta-data for specification of tiles (currently only GRIB-API:
  subtype_t *tiles;

  cdi_keys_t keys;

  int opt_grib_nentries;                // current no. key-value pairs
  int opt_grib_kvpair_size;             // current allocated size
  opt_key_val_pair_t *opt_grib_kvpair;  // (optional) list of keyword/value pairs
} VarInfo;

static VarInfo *varInfoList = NULL;
static int varInfoListSize = 0;
static int varInfoListUsed = 0;

static void
paramInitEntry(int varID, int param)
{
  VarInfo *var = &varInfoList[varID];
  var->varID = varID;
  var->param = param;
  var->prec = 0;
  var->tsteptype = TSTEP_INSTANT;
  varScanKeysInit(&(var->scanKeys));
  var->gridID = CDI_UNDEFID;
  var->zaxistype = 0;
  var->ltype1 = 0;
  var->ltype2 = -1;
  var->hasBounds = 0;
  var->level_sf = 0;
  var->level_unit = 0;
  var->recordTable = NULL;
  var->nsubtypes_alloc = 0;
  var->nsubtypes = 0;
  var->instID = CDI_UNDEFID;
  var->modelID = CDI_UNDEFID;
  var->tableID = CDI_UNDEFID;
  cdiInitKeys(&(var->keys));
  var->comptype = CDI_COMPRESS_NONE;
  var->complevel = 1;
  var->lmissval = false;
  var->missval = 0;
  var->name = NULL;
  var->tiles = NULL;
}

// Test if a variable specified by the given meta-data has already been registered in "vartable".
static int
varGetEntry(int param, int gridID, int zaxistype, int ltype1, int tsteptype, const char *name, const VarScanKeys *scanKeys,
            const var_tile_t *tiles)
{
  for (int varID = 0; varID < varInfoListSize; ++varID)
  {
    VarInfo *var = &varInfoList[varID];
    // testing for "param" implicitly checks if we are beyond the current vartable size:
    if (var->param == param)
    {
      int no_of_tiles = tiles ? tiles->numberOfTiles : -1;
      int vt_no_of_tiles = var->tiles ? subtypeGetGlobalDataP(var->tiles, SUBTYPE_ATT_NUMBER_OF_TILES) : -1;
      if ((var->zaxistype == zaxistype) && (var->ltype1 == ltype1) && (var->tsteptype == tsteptype)
          && (scanKeys == NULL || varScanKeysIsEqual(&var->scanKeys, scanKeys)) && (var->gridID == gridID)
          && (vt_no_of_tiles == no_of_tiles))
      {
        if (name && name[0] && var->name && var->name[0])
        {
          if (str_is_equal(name, var->name)) return varID;
        }
        else { return varID; }
      }
    }
  }

  return -1;
}

static void
varFree(void)
{
  if (CDI_Debug) Message("call to varFree");

  for (int varID = 0; varID < varInfoListUsed; ++varID)
  {
    VarInfo *var = &varInfoList[varID];
    if (var->recordTable)
    {
      for (int isub = 0; isub < var->nsubtypes_alloc; isub++) Free(var->recordTable[isub].levelTable);
      Free(var->recordTable);
    }

    if (var->name) Free(var->name);
    if (var->tiles) subtypeDestroyPtr(var->tiles);

    cdi_keys_t *keysp = &(var->keys);
    cdiDeleteVarKeys(keysp);

    if (var->opt_grib_kvpair)
    {
      for (int i = 0; i < var->opt_grib_nentries; i++)
      {
        if (var->opt_grib_kvpair[i].keyword) Free(var->opt_grib_kvpair[i].keyword);
      }
      Free(var->opt_grib_kvpair);
    }
    var->opt_grib_nentries = 0;
    var->opt_grib_kvpair_size = 0;
    var->opt_grib_kvpair = NULL;
  }

  if (varInfoList) Free(varInfoList);
  varInfoList = NULL;
  varInfoListSize = 0;
  varInfoListUsed = 0;

  if (Vct) Free(Vct);
  Vct = NULL;
  Vctsize = 0;
}

// Search for a tile subtype with subtypeIndex == tile_index.
static int
tileGetEntry(int varID, int tile_index)
{
  VarInfo *var = &varInfoList[varID];
  for (int isub = 0; isub < var->nsubtypes; isub++)
    if (var->recordTable[isub].subtypeIndex == tile_index) return isub;
  return CDI_UNDEFID;
}

/* Resizes vartable:recordTable data structure, if necessary. */
static int
tileNewEntry(int varID)
{
  VarInfo *var = &varInfoList[varID];
  int tileID = 0;
  if (var->nsubtypes_alloc == 0)
  {
    /* create table for the first time. */
    var->nsubtypes_alloc = 2;
    var->nsubtypes = 0;
    var->recordTable = (subtypetable_t *) Malloc((size_t) var->nsubtypes_alloc * sizeof(subtypetable_t));
    if (var->recordTable == NULL) SysError("Allocation of leveltable failed!");

    for (int isub = 0; isub < var->nsubtypes_alloc; isub++)
    {
      var->recordTable[isub].levelTable = NULL;
      var->recordTable[isub].levelTableSize = 0;
      var->recordTable[isub].nlevels = 0;
      var->recordTable[isub].subtypeIndex = CDI_UNDEFID;
    }
  }
  else
  {
    /* data structure large enough; find a free entry. */
    while (tileID < var->nsubtypes_alloc)
    {
      if (var->recordTable[tileID].levelTable == NULL) break;
      tileID++;
    }
  }

  /* If the table overflows, double its size. */
  if (tileID == var->nsubtypes_alloc)
  {
    tileID = var->nsubtypes_alloc;
    var->nsubtypes_alloc *= 2;
    var->recordTable = (subtypetable_t *) Realloc(var->recordTable, (size_t) var->nsubtypes_alloc * sizeof(subtypetable_t));
    if (var->recordTable == NULL) SysError("Reallocation of leveltable failed");
    for (int isub = tileID; isub < var->nsubtypes_alloc; isub++)
    {
      var->recordTable[isub].levelTable = NULL;
      var->recordTable[isub].levelTableSize = 0;
      var->recordTable[isub].nlevels = 0;
      var->recordTable[isub].subtypeIndex = CDI_UNDEFID;
    }
  }

  return tileID;
}

static int
levelNewEntry(int varID, int level1, int level2, int tileID)
{
  VarInfo *var = &varInfoList[varID];
  int levelID = 0;
  int levelTableSize = var->recordTable[tileID].levelTableSize;
  leveltable_t *levelTable = var->recordTable[tileID].levelTable;

  // Look for a free slot in levelTable. (Create the table the first time through).
  if (!levelTableSize)
  {
    levelTableSize = 2;
    levelTable = (leveltable_t *) Malloc((size_t) levelTableSize * sizeof(leveltable_t));
    for (int i = 0; i < levelTableSize; i++) levelTable[i].recID = CDI_UNDEFID;
  }
  else
  {
    while (levelID < levelTableSize && levelTable[levelID].recID != CDI_UNDEFID) ++levelID;
  }

  // If the table overflows, double its size.
  if (levelID == levelTableSize)
  {
    levelTable = (leveltable_t *) Realloc(levelTable, (size_t) (levelTableSize *= 2) * sizeof(leveltable_t));
    for (int i = levelID; i < levelTableSize; i++) levelTable[i].recID = CDI_UNDEFID;
  }

  levelTable[levelID].level1 = level1;
  levelTable[levelID].level2 = level2;
  levelTable[levelID].lindex = levelID;

  var->recordTable[tileID].nlevels = levelID + 1;
  var->recordTable[tileID].levelTableSize = levelTableSize;
  var->recordTable[tileID].levelTable = levelTable;

  return levelID;
}

#define UNDEF_PARAM -4711

static int
paramNewEntry(int param)
{
  int varID = 0;

  // Look for a free slot in vartable. (Create the table the first time through).
  if (!varInfoListSize)
  {
    varInfoListSize = 2;
    varInfoList = (VarInfo *) Malloc((size_t) varInfoListSize * sizeof(VarInfo));
    if (varInfoList == NULL)
    {
      Message("varTableSize = %d", varInfoListSize);
      SysError("Allocation of vartable failed");
    }

    for (int i = 0; i < varInfoListSize; i++)
    {
      varInfoList[i].param = UNDEF_PARAM;
      varInfoList[i].opt_grib_kvpair = NULL;
      varInfoList[i].opt_grib_kvpair_size = 0;
      varInfoList[i].opt_grib_nentries = 0;
    }
  }
  else
  {
    while (varID < varInfoListSize)
    {
      if (varInfoList[varID].param == UNDEF_PARAM) break;
      varID++;
    }
  }

  // If the table overflows, double its size.
  if (varID == varInfoListSize)
  {
    varInfoList = (VarInfo *) Realloc(varInfoList, (size_t) (varInfoListSize *= 2) * sizeof(VarInfo));
    for (int i = varID; i < varInfoListSize; i++)
    {
      varInfoList[i].param = UNDEF_PARAM;
      varInfoList[i].opt_grib_kvpair = NULL;
      varInfoList[i].opt_grib_kvpair_size = 0;
      varInfoList[i].opt_grib_nentries = 0;
    }
  }

  paramInitEntry(varID, param);

  return varID;
}

// Append tile set to a subtype. Return index of the new tile (i.e. the "entry->self" value).
static int
varInsertTileSubtype(VarInfo *vptr, const var_tile_t *tiles)
{
  if (tiles == NULL) return 0;

  // first, generate a subtype based on the info in "tiles".
  subtype_t *subtype_ptr;
  subtypeAllocate(&subtype_ptr, SUBTYPE_TILES);
  subtypeDefGlobalDataP(subtype_ptr, SUBTYPE_ATT_TOTALNO_OF_TILEATTR_PAIRS, tiles->totalno_of_tileattr_pairs);
  subtypeDefGlobalDataP(subtype_ptr, SUBTYPE_ATT_TILE_CLASSIFICATION, tiles->tileClassification);
  subtypeDefGlobalDataP(subtype_ptr, SUBTYPE_ATT_NUMBER_OF_TILES, tiles->numberOfTiles);

  // Here, we create a tile set for comparison that contains only one tile/attribute pair (based on "tiles").
  struct subtype_entry_t *entry = subtypeEntryInsert(subtype_ptr);
  subtypeDefEntryDataP(entry, SUBTYPE_ATT_NUMBER_OF_ATTR, tiles->numberOfAttributes);
  subtypeDefEntryDataP(entry, SUBTYPE_ATT_TILEINDEX, tiles->tileindex);
  subtypeDefEntryDataP(entry, SUBTYPE_ATT_TILEATTRIBUTE, tiles->attribute);

  if (vptr->tiles == NULL)
  {
    vptr->tiles = subtype_ptr;
    return 0;
  }
  else
  {
    tilesetInsertP(vptr->tiles, subtype_ptr);
    subtypeDestroyPtr(subtype_ptr);
    return vptr->tiles->nentries - 1;
  }
}

void
varAddRecord(int recID, int param, int gridID, int zaxistype, int hasBounds, int level1, int level2, int level_sf, int level_unit,
             int prec, int *pvarID, int *plevelID, int tsteptype, int ltype1, int ltype2, const char *name,
             const VarScanKeys *scanKeys, const var_tile_t *tiles, int *tile_index)
{
  int varID = (CDI_Split_Ltype105 != 1 || zaxistype != ZAXIS_HEIGHT)
                  ? varGetEntry(param, gridID, zaxistype, ltype1, tsteptype, name, scanKeys, tiles)
                  : CDI_UNDEFID;

  if (varID == CDI_UNDEFID)
  {
    varInfoListUsed++;
    varID = paramNewEntry(param);
    VarInfo *var = &varInfoList[varID];
    var->gridID = gridID;
    var->zaxistype = zaxistype;
    var->ltype1 = ltype1;
    var->ltype2 = ltype2;
    var->hasBounds = hasBounds;
    var->level_sf = level_sf;
    var->level_unit = level_unit;
    var->tsteptype = tsteptype;
    if (scanKeys) var->scanKeys = *scanKeys;

    if (name && name[0]) var->name = strdup(name);
  }
  else
  {
    char paramstr[32];
    cdiParamToString(param, paramstr, sizeof(paramstr));

    VarInfo *var = &varInfoList[varID];
    if (var->gridID != gridID)
    {
      Message("param = %s gridID = %d", paramstr, gridID);
      Error("horizontal grid must not change for same parameter!");
    }
    if (var->zaxistype != zaxistype)
    {
      Message("param = %s zaxistype = %d", paramstr, zaxistype);
      Error("zaxistype must not change for same parameter!");
    }
  }

  VarInfo *var = &varInfoList[varID];
  if (prec > var->prec) var->prec = prec;

  // append current tile to tile subtype info.
  int this_tile = varInsertTileSubtype(&varInfoList[varID], tiles);
  int tileID = tileGetEntry(varID, this_tile);
  if (tile_index) (*tile_index) = this_tile;
  if (tileID == CDI_UNDEFID)
  {
    tileID = tileNewEntry((int) varID);
    var->recordTable[tileID].subtypeIndex = this_tile;
    var->nsubtypes++;
  }

  // append current level to level table info
  int levelID = levelNewEntry(varID, level1, level2, tileID);
  if (CDI_Debug)
    Message("vartable[%d].recordTable[%d].levelTable[%d].recID = %d; level1,2=%d,%d", varID, tileID, levelID, recID, level1,
            level2);
  var->recordTable[tileID].levelTable[levelID].recID = recID;

  *pvarID = (int) varID;
  *plevelID = levelID;
}

/*
static
int dblcmp(const void *s1, const void *s2)
{
  int cmp = 0;

  if      ( *((double *) s1) < *((double *) s2) ) cmp = -1;
  else if ( *((double *) s1) > *((double *) s2) ) cmp =  1;

  return cmp;
}
*/
static int
cmpLevelTable(const void *s1, const void *s2)
{
  int cmp = 0;
  const leveltable_t *x = (const leveltable_t *) s1;
  const leveltable_t *y = (const leveltable_t *) s2;
  // printf("%g %g  %d %d\n", x->leve11, y->level1, x, y);
  if (x->level1 < y->level1)
    cmp = -1;
  else if (x->level1 > y->level1)
    cmp = 1;

  return cmp;
}

static int
cmpLevelTableInv(const void *s1, const void *s2)
{
  int cmp = 0;
  const leveltable_t *x = (const leveltable_t *) s1;
  const leveltable_t *y = (const leveltable_t *) s2;
  // printf("%g %g  %d %d\n", x->leve11, y->level1, x, y);
  if (x->level1 < y->level1)
    cmp = 1;
  else if (x->level1 > y->level1)
    cmp = -1;

  return cmp;
}

void
varCopyKeys(int vlistID, int varID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  cdiInitKeys(&vlistptr->vars[varID].keys);
  cdiCopyVarKeys(&(varInfoList[varID].keys), &(vlistptr->vars[varID].keys));
}
/*
struct cdi_generate_varinfo
{
  int varid;
  const char *name;
};

static int
cdi_generate_cmp_varname(const void *s1, const void *s2)
{
  const struct cdi_generate_varinfo *x = (const struct cdi_generate_varinfo *) s1, *y = (const struct cdi_generate_varinfo *) s2;
  return strcmp(x->name, y->name);
}
*/
void
cdi_generate_vars(stream_t *streamptr)
{
  int vlistID = streamptr->vlistID;

  int *varids = (int *) Malloc((size_t) varInfoListUsed * sizeof(int));
  for (int varID = 0; varID < varInfoListUsed; varID++) varids[varID] = (int) varID;
  /*
    if (streamptr->sortname)
      {
        bool hasName = true;
        for (int varID = 0; varID < varTableUsed; varID++)
          if (!vartable[varID].name) hasName = false;

        if (hasName)
          {
            struct cdi_generate_varinfo *varInfo
                = (struct cdi_generate_varinfo *) Malloc((size_t) varTableUsed * sizeof(struct cdi_generate_varinfo));

            for (int varID = 0; varID < varTableUsed; varID++)
              {
                varInfo[varID].varid = varids[varID];
                varInfo[varID].name = vartable[varids[varID]].name;
              }
            qsort(varInfo, varTableUsed, sizeof(varInfo[0]), cdi_generate_cmp_varname);
            for (int varID = 0; varID < varTableUsed; varID++)
              {
                varids[varID] = varInfo[varID].varid;
              }
            Free(varInfo);
          }
      }
  */
  for (int index = 0; index < varInfoListUsed; index++)
  {
    int varid = varids[index];
    VarInfo *var = &varInfoList[varid];

    int gridID = var->gridID;
    int param = var->param;
    int ltype1 = var->ltype1;
    int ltype2 = var->ltype2;
    int zaxistype = var->zaxistype;
    if (ltype1 == 0 && zaxistype == ZAXIS_GENERIC && cdiDefaultLeveltype != -1) zaxistype = cdiDefaultLeveltype;
    int hasBounds = var->hasBounds;
    int prec = var->prec;
    int instID = var->instID;
    int modelID = var->modelID;
    int tableID = var->tableID;
    int tsteptype = var->tsteptype;
    int comptype = var->comptype;

    double level_sf = (var->level_sf != 0) ? (1.0 / var->level_sf) : 1;

    /* consistency check: test if all subtypes have the same levels: */
    int nlevels = var->recordTable[0].nlevels;
    for (int isub = 1; isub < var->nsubtypes; isub++)
    {
      if (var->recordTable[isub].nlevels != nlevels)
      {
        fprintf(stderr,
                "var \"%s\": isub = %d / %d :: "
                "nlevels = %d, vartable[varid].recordTable[isub].nlevels = %d\n",
                var->name, isub, var->nsubtypes, nlevels, var->recordTable[isub].nlevels);
        Error("zaxis size must not change for same parameter!");
      }

      const leveltable_t *t1 = var->recordTable[isub - 1].levelTable;
      const leveltable_t *t2 = var->recordTable[isub].levelTable;
      for (int ilev = 0; ilev < nlevels; ilev++)
        if ((t1[ilev].level1 != t2[ilev].level1) || (t1[ilev].level2 != t2[ilev].level2) || (t1[ilev].lindex != t2[ilev].lindex))
        {
          fprintf(stderr,
                  "var \"%s\", varID=%d: isub = %d / %d :: "
                  "nlevels = %d, vartable[varid].recordTable[isub].nlevels = %d\n",
                  var->name, varid, isub, var->nsubtypes, nlevels, var->recordTable[isub].nlevels);
          Message("t1[ilev].level1=%d / t2[ilev].level1=%d", t1[ilev].level1, t2[ilev].level1);
          Message("t1[ilev].level2=%d / t2[ilev].level2=%d", t1[ilev].level2, t2[ilev].level2);
          Message("t1[ilev].lindex=%d / t2[ilev].lindex=%d", t1[ilev].lindex, t2[ilev].lindex);
          Error("zaxis type must not change for same parameter!");
        }
    }
    leveltable_t *levelTable = var->recordTable[0].levelTable;

    if (ltype1 == 0 && zaxistype == ZAXIS_GENERIC && nlevels == 1 && levelTable[0].level1 == 0) zaxistype = ZAXIS_SURFACE;

    double *dlevels = (double *) Malloc((size_t) nlevels * sizeof(double));

    /*
    if ( hasBounds && zaxistype != ZAXIS_HYBRID && zaxistype != ZAXIS_HYBRID_HALF )
      for (int levelID = 0; levelID < nlevels; levelID++)
        dlevels[levelID] = (level_sf*levelTable[levelID].level1 + level_sf*levelTable[levelID].level2) / 2.0;
    else
    */
    for (int levelID = 0; levelID < nlevels; levelID++) dlevels[levelID] = level_sf * levelTable[levelID].level1;

    if (nlevels > 1)
    {
      bool linc = true, ldec = true, lsort = false;
      for (int levelID = 1; levelID < nlevels; levelID++)
      {
        // check increasing of levels
        linc &= (dlevels[levelID] > dlevels[levelID - 1]);
        // check decreasing of levels
        ldec &= (dlevels[levelID] < dlevels[levelID - 1]);
      }
      /*
       * always sort pressure z-axis to ensure
       * levelTable[levelID1].level1 < levelTable[levelID2].level1 <=> levelID1 > levelID2
       * unless already sorted in decreasing order
       */
      if ((!linc && !ldec) && zaxistype == ZAXIS_PRESSURE)
      {
        qsort(levelTable, (size_t) nlevels, sizeof(leveltable_t), cmpLevelTableInv);
        lsort = true;
      }
      /*
       * always sort hybrid and depth-below-land z-axis to ensure
       * levelTable[levelID1].level1 < levelTable[levelID2].level1 <=> levelID1 < levelID2
       * unless already sorted in increasing order
       */
      else if ((!linc && !ldec) || zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_DEPTH_BELOW_LAND)
      {
        qsort(levelTable, (size_t) nlevels, sizeof(leveltable_t), cmpLevelTable);
        lsort = true;
      }

      if (lsort)
      {
        /*
        if ( hasBounds && zaxistype != ZAXIS_HYBRID && zaxistype != ZAXIS_HYBRID_HALF )
          for (int levelID = 0; levelID < nlevels; levelID++)
            dlevels[levelID] = (level_sf*levelTable[levelID].level1 + level_sf*levelTable[levelID].level2) / 2.0;
        else
        */
        for (int levelID = 0; levelID < nlevels; levelID++) dlevels[levelID] = level_sf * levelTable[levelID].level1;
      }
    }

    double *dlevels1 = NULL, *dlevels2 = NULL;
    if (hasBounds)
    {
      dlevels1 = (double *) Malloc(2 * (size_t) nlevels * sizeof(double));
      dlevels2 = dlevels1 + nlevels;
      for (int levelID = 0; levelID < nlevels; levelID++) dlevels1[levelID] = level_sf * levelTable[levelID].level1;
      for (int levelID = 0; levelID < nlevels; levelID++) dlevels2[levelID] = level_sf * levelTable[levelID].level2;
    }

    const char **cvals = NULL;
    const char *unitptr = cdiUnitNamePtr(var->level_unit);
    int zaxisID = varDefZaxis(vlistID, zaxistype, nlevels, dlevels, cvals, 0, dlevels1, dlevels2, (int) Vctsize, Vct, NULL, NULL,
                              unitptr, 0, 0, ltype1, ltype2);

    if (CDI_CMOR_Mode && nlevels == 1 && zaxistype != ZAXIS_HYBRID) zaxisDefScalar(zaxisID);

    if (zaxisInqType(zaxisID) == ZAXIS_REFERENCE)
    {
      if (numberOfVerticalLevels > 0) cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NLEV, numberOfVerticalLevels);
      if (numberOfVerticalGrid > 0) cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NUMBEROFVGRIDUSED, numberOfVerticalGrid);
      if (!cdiUUIDIsNull(uuidVGrid)) cdiDefKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuidVGrid, CDI_UUID_SIZE);
    }

    if (hasBounds) Free(dlevels1);
    Free(dlevels);

    // define new subtype for tile set
    int tilesetID = CDI_UNDEFID;
    if (var->tiles) tilesetID = vlistDefTileSubtype(vlistID, var->tiles);

    // generate new variable
    (void) stream_new_var(streamptr, gridID, zaxisID, tilesetID);
    int varID = vlistDefVarTiles(vlistID, gridID, zaxisID, TIME_VARYING, tilesetID);

    vlistDefVarTsteptype(vlistID, varID, tsteptype);
    vlistDefVarParam(vlistID, varID, param);
    vlistDefVarDatatype(vlistID, varID, prec);
    vlistDefVarCompType(vlistID, varID, comptype);

    varCopyKeys(vlistID, varID);

    if (var->lmissval) vlistDefVarMissval(vlistID, varID, var->missval);
    if (var->name) cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, var->name);

    vlist_t *vlistptr = vlist_to_pointer(vlistID);
    for (int i = 0; i < var->opt_grib_nentries; i++)
    {
      resize_opt_grib_entries(&vlistptr->vars[varID], vlistptr->vars[varID].opt_grib_nentries + 1);
      vlistptr->vars[varID].opt_grib_nentries += 1;
      int idx = vlistptr->vars[varID].opt_grib_nentries - 1;

      vlistptr->vars[varID].opt_grib_kvpair[idx] = var->opt_grib_kvpair[i];
      vlistptr->vars[varID].opt_grib_kvpair[idx].keyword = NULL;
      if (var->opt_grib_kvpair[i].keyword)
        vlistptr->vars[varID].opt_grib_kvpair[idx].keyword = strdup(var->opt_grib_kvpair[i].keyword);
      vlistptr->vars[varID].opt_grib_kvpair[i].update = true;
    }
    // note: if the key is not defined, we do not throw an error!

    if (CDI_Default_TableID != CDI_UNDEFID)
    {
      int pdis, pcat, pnum;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      char name[CDI_MAX_NAME];
      name[0] = 0;
      char longname[CDI_MAX_NAME];
      longname[0] = 0;
      char units[CDI_MAX_NAME];
      units[0] = 0;
      tableInqEntry(CDI_Default_TableID, pnum, -1, name, longname, units);
      if (name[0])
      {
        if (tableID != CDI_UNDEFID)
        {
          cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, name);
          if (longname[0]) cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, longname);
          if (units[0]) cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, units);
        }
        else
          tableID = CDI_Default_TableID;
      }
      if (CDI_Default_ModelID != CDI_UNDEFID) modelID = CDI_Default_ModelID;
      if (CDI_Default_InstID != CDI_UNDEFID) instID = CDI_Default_InstID;
    }

    if (instID != CDI_UNDEFID) vlistDefVarInstitut(vlistID, varID, instID);
    if (modelID != CDI_UNDEFID) vlistDefVarModel(vlistID, varID, modelID);
    if (tableID != CDI_UNDEFID) vlistDefVarTable(vlistID, varID, tableID);
  }

  for (int index = 0; index < varInfoListUsed; index++)
  {
    int varid = varids[index];
    VarInfo *var = &varInfoList[varid];
    int nlevels = var->recordTable[0].nlevels;

    int nsub = (var->nsubtypes >= 0) ? var->nsubtypes : 0;
    for (int isub = 0; isub < nsub; isub++)
    {
      sleveltable_t *streamRecordTable = streamptr->vars[index].recordTable + isub;
      leveltable_t *vartableLevelTable = var->recordTable[isub].levelTable;
      for (int levelID = 0; levelID < nlevels; levelID++)
      {
        streamRecordTable->recordID[levelID] = vartableLevelTable[levelID].recID;
        int lindex;
        for (lindex = 0; lindex < nlevels; lindex++)
          if (levelID == vartableLevelTable[lindex].lindex) break;
        if (lindex == nlevels) Error("Internal problem! lindex not found.");
        streamRecordTable->lindex[levelID] = (int) lindex;
      }
    }
  }

  Free(varids);

  varFree();
}

void
varDefVCT(size_t vctsize, double *vctptr)
{
  if (Vct == NULL && vctptr != NULL && vctsize > 0)
  {
    Vctsize = vctsize;
    Vct = (double *) Malloc(vctsize * sizeof(double));
    memcpy(Vct, vctptr, vctsize * sizeof(double));
  }
}

void
varDefZAxisReference(int nhlev, int nvgrid, unsigned char uuid[CDI_UUID_SIZE])
{
  numberOfVerticalLevels = nhlev;
  numberOfVerticalGrid = nvgrid;
  memcpy(uuidVGrid, uuid, CDI_UUID_SIZE);
}

bool
zaxis_compare(int zaxisID, int zaxistype, int nlevels, const double *levels, const double *lbounds, const double *ubounds,
              const char *longname, const char *units, int ltype1, int ltype2)
{
  bool differ = true;

  int ltype1_0 = 0, ltype2_0 = -1;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, &ltype1_0);
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFSECONDFIXEDSURFACE, &ltype2_0);
  bool ltype1IsEqual = (ltype1 == ltype1_0);
  bool ltype2IsEqual = (ltype2 == ltype2_0);
  bool hasBounds = (lbounds && ubounds);

  if (ltype1IsEqual && ltype2IsEqual && (zaxistype == zaxisInqType(zaxisID) || zaxistype == ZAXIS_GENERIC))
  {
    bool hasBoundsZ = (zaxisInqLbounds(zaxisID, NULL) > 0 && zaxisInqUbounds(zaxisID, NULL) > 0);
    if (nlevels == zaxisInqSize(zaxisID) && hasBoundsZ == hasBounds)
    {
      const double *dlevels = zaxisInqLevelsPtr(zaxisID);
      if (dlevels && levels)
      {
        int levelID;
        for (levelID = 0; levelID < nlevels; levelID++)
          if (fabs(dlevels[levelID] - levels[levelID]) > 1.e-9) break;
        if (levelID == nlevels) differ = false;
      }

      if (!differ && hasBounds)
      {
        double *bounds = (double *) Malloc(2 * (size_t) nlevels * sizeof(double));
        zaxisInqLbounds(zaxisID, bounds);
        zaxisInqUbounds(zaxisID, bounds + nlevels);
        for (int levelID = 0; levelID < nlevels; levelID++)
        {
          if (fabs(lbounds[levelID] - bounds[levelID]) > 1.e-9 || fabs(ubounds[levelID] - bounds[levelID + nlevels]) > 1.e-9)
          {
            differ = true;
            break;
          }
        }
        Free(bounds);
      }

      if (!differ)
      {
        if (longname && longname[0])
        {
          char zlongname[CDI_MAX_NAME];
          int length = CDI_MAX_NAME;
          cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_LONGNAME, zlongname, &length);
          if (zlongname[0] && !str_is_equal(longname, zlongname)) differ = true;
        }
        if (units && units[0])
        {
          char zunits[CDI_MAX_NAME];
          int length = CDI_MAX_NAME;
          cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, zunits, &length);
          if (zunits[0] && !str_is_equal(units, zunits)) differ = true;
        }
      }
    }
  }

  return differ;
}

struct varDefZAxisSearchState
{
  int resIDValue;
  int zaxistype;
  int nlevels;
  const double *levels;
  const double *lbounds;
  const double *ubounds;
  const char *longname;
  const char *units;
  int ltype1;
  int ltype2;
};

static enum cdiApplyRet
varDefZAxisSearch(int id, void *res, void *data)
{
  struct varDefZAxisSearchState *state = (struct varDefZAxisSearchState *) data;
  (void) res;
  if (zaxis_compare(id, state->zaxistype, state->nlevels, state->levels, state->lbounds, state->ubounds, state->longname,
                    state->units, state->ltype1, state->ltype2)
      == false)
  {
    state->resIDValue = id;
    return CDI_APPLY_STOP;
  }
  else
    return CDI_APPLY_GO_ON;
}

int
varDefZaxis(int vlistID, int zaxistype, int nlevels, const double *levels, const char **cvals, size_t clength,
            const double *levels1, const double *levels2, int vctsize, const double *vct, char *name, const char *longname,
            const char *units, int prec, int mode, int ltype1, int ltype2)
{
  /*
    mode: 0 search in vlist and zaxis table
          1 search in zaxis table
   */
  int zaxisID = CDI_UNDEFID;
  bool zaxisdefined = false;
  bool zaxisglobdefined = false;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int nzaxis = vlistptr->nzaxis;

  if (ltype2 == 255) ltype2 = -1;

  if (mode == 0)
    for (int index = 0; index < nzaxis; index++)
    {
      zaxisID = vlistptr->zaxisIDs[index];

      if (!zaxis_compare(zaxisID, zaxistype, nlevels, levels, levels1, levels2, longname, units, ltype1, ltype2))
      {
        zaxisdefined = true;
        break;
      }
    }

  if (!zaxisdefined)
  {
    struct varDefZAxisSearchState query;
    query.zaxistype = zaxistype;
    query.nlevels = nlevels;
    query.levels = levels;
    query.lbounds = levels1;
    query.ubounds = levels2;
    query.longname = longname;
    query.units = units;
    query.ltype1 = ltype1;
    query.ltype2 = ltype2;

    if ((zaxisglobdefined = (cdiResHFilterApply(getZaxisOps(), varDefZAxisSearch, &query) == CDI_APPLY_STOP)))
      zaxisID = query.resIDValue;

    if (mode == 1 && zaxisglobdefined)
      for (int index = 0; index < nzaxis; index++)
        if (vlistptr->zaxisIDs[index] == zaxisID)
        {
          zaxisglobdefined = false;
          break;
        }
  }

  if (!zaxisdefined)
  {
    if (!zaxisglobdefined)
    {
      zaxisID = zaxisCreate(zaxistype, nlevels);
      if (levels) zaxisDefLevels(zaxisID, levels);
      if (levels1 && levels2)
      {
        zaxisDefLbounds(zaxisID, levels1);
        zaxisDefUbounds(zaxisID, levels2);
      }

      if (cvals != NULL && nlevels != 0 && clength != 0) zaxisDefCvals(zaxisID, cvals, (int) clength);

      if ((zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_HYBRID_HALF) && vctsize > 0) zaxisDefVct(zaxisID, vctsize, vct);

      if (name && name[0]) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, name);
      if (longname && longname[0]) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_LONGNAME, longname);
      if (units && units[0]) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units);
      zaxisDefDatatype(zaxisID, prec);
      cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, ltype1);
      if (ltype2 != -1) cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFSECONDFIXEDSURFACE, ltype2);
    }

    vlistptr->zaxisIDs[nzaxis] = zaxisID;
    vlistptr->nzaxis++;
  }

  return zaxisID;
}

void
varDefMissval(int varID, double missval)
{
  varInfoList[varID].lmissval = true;
  varInfoList[varID].missval = missval;
}

void
varDefCompType(int varID, int comptype)
{
  if (varInfoList[varID].comptype == CDI_COMPRESS_NONE) varInfoList[varID].comptype = comptype;
}

void
varDefCompLevel(int varID, int complevel)
{
  varInfoList[varID].complevel = complevel;
}

int
varInqInst(int varID)
{
  return varInfoList[varID].instID;
}

void
varDefInst(int varID, int instID)
{
  varInfoList[varID].instID = instID;
}

int
varInqModel(int varID)
{
  return varInfoList[varID].modelID;
}

void
varDefModel(int varID, int modelID)
{
  varInfoList[varID].modelID = modelID;
}

int
varInqTable(int varID)
{
  return varInfoList[varID].tableID;
}

void
varDefTable(int varID, int tableID)
{
  varInfoList[varID].tableID = tableID;
}

void
varDefKeyInt(int varID, int key, int value)
{
  cdi_keys_t *keysp = &(varInfoList[varID].keys);
  cdiDefVarKeyInt(keysp, key, value);
}

void
varDefKeyBytes(int varID, int key, const unsigned char *bytes, int length)
{
  cdi_keys_t *keysp = &(varInfoList[varID].keys);
  cdiDefVarKeyBytes(keysp, key, bytes, length);
}

void
varDefKeyString(int varID, int key, const char *string)
{
  int length = (int) strlen(string) + 1;
  cdi_keys_t *keysp = &(varInfoList[varID].keys);
  cdiDefVarKeyBytes(keysp, key, (const unsigned char *) string, length);
}

#ifdef HAVE_LIBGRIB_API
// Resizes and initializes opt_grib_kvpair data structure.
static void
resize_vartable_opt_grib_entries(VarInfo *var, int nentries)
{
  if (var->opt_grib_kvpair_size < nentries)
  {
    if (CDI_Debug) Message("resize data structure, %d -> %d", var->opt_grib_kvpair_size, nentries);

    int new_size = ((2 * var->opt_grib_kvpair_size) > nentries) ? (2 * var->opt_grib_kvpair_size) : nentries;
    if (CDI_Debug) Message("resize vartable opt_grib_entries array to size %d", new_size);
    opt_key_val_pair_t *tmp = (opt_key_val_pair_t *) Malloc((size_t) new_size * sizeof(opt_key_val_pair_t));
    for (int i = 0; i < var->opt_grib_kvpair_size; i++) { tmp[i] = var->opt_grib_kvpair[i]; }
    for (int i = var->opt_grib_kvpair_size; i < new_size; i++)
    {
      tmp[i].int_val = 0;
      tmp[i].dbl_val = 0;
      tmp[i].int_arr = 0;
      tmp[i].dbl_arr = 0;
      tmp[i].update = false;
      tmp[i].keyword = NULL;
    }  // for
    var->opt_grib_kvpair_size = new_size;
    Free(var->opt_grib_kvpair);
    var->opt_grib_kvpair = tmp;
  }
}
#endif

#ifdef HAVE_LIBGRIB_API
void
varDefOptGribInt(int varID, int tile_index, long lval, const char *keyword)
{
  VarInfo *var = &varInfoList[varID];

  int idx = -1;
  for (int i = 0; i < var->opt_grib_nentries; i++)
  {
    if (str_is_equal(keyword, var->opt_grib_kvpair[i].keyword) && (var->opt_grib_kvpair[i].data_type == t_int)
        && (var->opt_grib_kvpair[i].subtype_index == tile_index))
      idx = i;
  }

  if (idx == -1)
  {
    resize_vartable_opt_grib_entries(&varInfoList[varID], var->opt_grib_nentries + 1);
    var->opt_grib_nentries += 1;
    idx = var->opt_grib_nentries - 1;
  }
  else
  {
    if (var->opt_grib_kvpair[idx].keyword) Free(var->opt_grib_kvpair[idx].keyword);
  }
  var->opt_grib_kvpair[idx].data_type = t_int;
  var->opt_grib_kvpair[idx].int_val = (int) lval;
  var->opt_grib_kvpair[idx].keyword = strdup(keyword);
  var->opt_grib_kvpair[idx].subtype_index = tile_index;
}
#endif

#ifdef HAVE_LIBGRIB_API
void
varDefOptGribDbl(int varID, int tile_index, double dval, const char *keyword)
{
  VarInfo *var = &varInfoList[varID];
  int idx = -1;
  for (int i = 0; i < var->opt_grib_nentries; i++)
  {
    if (str_is_equal(keyword, var->opt_grib_kvpair[i].keyword) && (var->opt_grib_kvpair[i].data_type == t_double)
        && (var->opt_grib_kvpair[i].subtype_index == tile_index))
      idx = i;
  }

  if (idx == -1)
  {
    resize_vartable_opt_grib_entries(&varInfoList[varID], var->opt_grib_nentries + 1);
    var->opt_grib_nentries += 1;
    idx = var->opt_grib_nentries - 1;
  }
  else
  {
    if (var->opt_grib_kvpair[idx].keyword) Free(var->opt_grib_kvpair[idx].keyword);
  }
  var->opt_grib_kvpair[idx].data_type = t_double;
  var->opt_grib_kvpair[idx].dbl_val = dval;
  var->opt_grib_kvpair[idx].keyword = strdup(keyword);
  var->opt_grib_kvpair[idx].subtype_index = tile_index;
}
#endif

#ifdef HAVE_LIBGRIB_API
void
varDefOptGribIntArr(int varID, int tile_index, const long *larr, size_t arr_len, const char *keyword)
{
  VarInfo *var = &varInfoList[varID];
  int idx = -1;

  // Search for an existing entry with the same keyword and tile_index
  for (int i = 0; i < var->opt_grib_nentries; i++)
  {
    if (str_is_equal(keyword, var->opt_grib_kvpair[i].keyword) && (var->opt_grib_kvpair[i].data_type == t_intarr)
        && (var->opt_grib_kvpair[i].subtype_index == tile_index))
    {
      idx = i;
      break;
    }
  }

  // If entry not found, create a new one
  if (idx == -1)
  {
    resize_vartable_opt_grib_entries(&varInfoList[varID], var->opt_grib_nentries + 1);
    var->opt_grib_nentries += 1;
    idx = var->opt_grib_nentries - 1;
  }
  else
  {
    // Free existing keyword and array
    if (var->opt_grib_kvpair[idx].keyword) Free(var->opt_grib_kvpair[idx].keyword);
    if (var->opt_grib_kvpair[idx].int_arr) Free(var->opt_grib_kvpair[idx].int_arr);
  }

  // Assign keyword, data type, and tile index
  var->opt_grib_kvpair[idx].data_type = t_intarr;
  var->opt_grib_kvpair[idx].keyword = strdup(keyword);
  var->opt_grib_kvpair[idx].subtype_index = tile_index;

  // Allocate memory for integer array
  var->opt_grib_kvpair[idx].arr_len = arr_len;
  if (arr_len > 0)
  {
    var->opt_grib_kvpair[idx].int_arr = (int *) Malloc(arr_len * sizeof(int));
    for (size_t i = 0; i < arr_len; i++)
    {
      var->opt_grib_kvpair[idx].int_arr[i] = (int) larr[i];  // convert long to int
    }
  }
  else { var->opt_grib_kvpair[idx].int_arr = NULL; }
}
#endif

#ifdef HAVE_LIBGRIB_API
void
varDefOptGribDblArr(int varID, int tile_index, const double *darr, size_t arr_len, const char *keyword)
{
  VarInfo *var = &varInfoList[varID];
  int idx = -1;

  // Search for an existing entry with the same keyword and tile_index
  for (int i = 0; i < var->opt_grib_nentries; i++)
  {
    if (str_is_equal(keyword, var->opt_grib_kvpair[i].keyword) && (var->opt_grib_kvpair[i].data_type == t_doublearr)
        && (var->opt_grib_kvpair[i].subtype_index == tile_index))
    {
      idx = i;
      break;
    }
  }

  // If entry not found, create a new one
  if (idx == -1)
  {
    resize_vartable_opt_grib_entries(&varInfoList[varID], var->opt_grib_nentries + 1);
    var->opt_grib_nentries += 1;
    idx = var->opt_grib_nentries - 1;
  }
  else
  {
    // Free existing keyword and array
    if (var->opt_grib_kvpair[idx].keyword) Free(var->opt_grib_kvpair[idx].keyword);
    if (var->opt_grib_kvpair[idx].dbl_arr) Free(var->opt_grib_kvpair[idx].dbl_arr);
  }

  // Assign keyword, data type, and tile index
  var->opt_grib_kvpair[idx].data_type = t_doublearr;
  var->opt_grib_kvpair[idx].keyword = strdup(keyword);
  var->opt_grib_kvpair[idx].subtype_index = tile_index;

  // Allocate memory for double array
  var->opt_grib_kvpair[idx].arr_len = arr_len;
  if (arr_len > 0)
  {
    var->opt_grib_kvpair[idx].dbl_arr = (double *) Malloc(arr_len * sizeof(double));
    for (size_t i = 0; i < arr_len; i++) { var->opt_grib_kvpair[idx].dbl_arr[i] = darr[i]; }
  }
  else { var->opt_grib_kvpair[idx].dbl_arr = NULL; }
}

#endif

#ifdef HAVE_LIBGRIB_API
int
varOptGribNentries(int varID)
{
  return varInfoList[varID].opt_grib_nentries;
}
#endif

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifndef VLIST_VAR_H
#define VLIST_VAR_H

#ifdef HAVE_CONFIG_H
#endif

#ifndef VLIST_H
#endif

int vlistVarGetPackSize(vlist_t *p, int varID, void *context);
void vlistVarPack(vlist_t *p, int varID, char *buffer, int bufferSize, int *pos, void *context);
void vlistVarUnpack(int vlistID, char *buf, int size, int *position, int, void *context);
int vlistVarCompare(vlist_t *a, int varIDA, vlist_t *b, int varIDB);
void vlistDefVarIOrank(int, int, int);
int vlistInqVarIOrank(int, int);

void cdiVlistCreateVarLevInfo(vlist_t *vlistptr, int varID);

#endif
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif

#include <stdbool.h>



#ifdef HAVE_LIBGRIB_API
/* list of additional GRIB2 keywords which are read by the open process */
int cdiNAdditionalGRIBKeys = 0;
char *cdiAdditionalGRIBKeys[MAX_OPT_GRIB_ENTRIES];
#endif

static int VLIST_Debug = 0;

static void vlist_initialize(void);

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>

static pthread_once_t _vlist_init_thread = PTHREAD_ONCE_INIT;

#define VLIST_INIT() pthread_once(&_vlist_init_thread, vlist_initialize)

#else

static bool vlistIsInitialized = false;

#define VLIST_INIT() \
  if (!vlistIsInitialized) vlist_initialize()
#endif

void *
stream_get_pointer(int streamID)
{
  return stream_to_pointer(streamID);
}

void *
stream_get_vlist_pointer(int streamID)
{
  stream_t *streamPtr = stream_to_pointer(streamID);
  return vlist_to_pointer(streamPtr->vlistID);
}

static int
vlist_compare(vlist_t *a, vlist_t *b)
{
  int diff = (a->nvars != b->nvars) | (a->ngrids != b->ngrids) | (a->nzaxis != b->nzaxis) | (a->instID != b->instID)
             | (a->modelID != b->modelID) | (a->tableID != b->tableID) | (a->ntsteps != b->ntsteps)
             | (a->atts.nelems != b->atts.nelems);

  int nvars = a->nvars;
  for (int varID = 0; varID < nvars; ++varID) diff |= vlistVarCompare(a, varID, b, varID);

  size_t natts = a->atts.nelems;
  for (size_t attID = 0; attID < natts; ++attID) diff |= cdi_att_compare(&a->atts, &a->atts, (int) attID);

  return diff;
}

static void vlistPrintKernel(vlist_t *vlistptr, FILE *fp);
static void vlist_delete(vlist_t *vlistptr);

static int vlistGetSizeP(void *vlistptr, void *context);
static void vlistPackP(void *vlistptr, void *buff, int size, int *position, void *context);
static int vlistTxCode(void *vlistptr);

const resOps vlistOps = { (valCompareFunc) vlist_compare,
                          (valDestroyFunc) vlist_delete,
                          (valPrintFunc) vlistPrintKernel,
                          vlistGetSizeP,
                          vlistPackP,
                          vlistTxCode };

vlist_t *
vlist_to_pointer(int vlistID)
{
  VLIST_INIT();
  return (vlist_t *) reshGetVal(vlistID, &vlistOps);
}

static void
vlist_init_entry(vlist_t *vlistptr)
{
  vlistptr->immutable = 0;
  vlistptr->internal = 0;
  vlistptr->self = CDI_UNDEFID;
  vlistptr->nvars = 0;
  vlistptr->vars = NULL;
  vlistptr->ngrids = 0;
  vlistptr->nzaxis = 0;
  vlistptr->taxisID = CDI_UNDEFID;
  vlistptr->instID = CDI_Default_InstID;
  vlistptr->modelID = CDI_Default_ModelID;
  vlistptr->tableID = CDI_Default_TableID;
  vlistptr->varsAllocated = 0;
  vlistptr->ntsteps = CDI_UNDEFID;
  vlistptr->keys.nalloc = MAX_KEYS;
  vlistptr->keys.nelems = 0;
  for (int i = 0; i < MAX_KEYS; ++i) vlistptr->keys.value[i].length = 0;
  vlistptr->atts.nalloc = MAX_ATTRIBUTES;
  vlistptr->atts.nelems = 0;
  vlistptr->nsubtypes = 0;
  for (int i = 0; i < MAX_SUBTYPES_PS; ++i) vlistptr->subtypeIDs[i] = CDI_UNDEFID;
}

static vlist_t *
vlist_new_entry(cdiResH resH)
{
  vlist_t *vlistptr = (vlist_t *) Malloc(sizeof(vlist_t));
  vlist_init_entry(vlistptr);
  if (resH == CDI_UNDEFID)
    vlistptr->self = reshPut(vlistptr, &vlistOps);
  else
  {
    vlistptr->self = resH;
    reshReplace(resH, vlistptr, &vlistOps);
  }
  return vlistptr;
}

static void
vlist_delete_entry(int vlistID)
{
  reshRemove(vlistID, &vlistOps);

  if (VLIST_Debug) Message("Removed idx %d from vlist list", vlistID);
}

static void
vlist_initialize(void)
{
  char *env = getenv("VLIST_DEBUG");
  if (env) VLIST_Debug = atoi(env);
#ifndef HAVE_LIBPTHREAD
  vlistIsInitialized = true;
#endif
}

static void
vlist_copy(vlist_t *vlistptr2, vlist_t *vlistptr1)
{
  int vlistID2 = vlistptr2->self;
  int vlist2internal = vlistptr2->internal;
  memcpy(vlistptr2, vlistptr1, sizeof(vlist_t));
  vlistptr2->internal = vlist2internal;  // the question who's responsible to destroy the vlist is tied to its containing memory
                                         // region, so we retain this flag
  vlistptr2->immutable = 0;              // this is a copy, so it's mutable, independent of whether the original is mutable or not
  vlistptr2->keys.nelems = 0;
  vlistptr2->atts.nelems = 0;
  vlistptr2->self = vlistID2;
}

void
cdiVlistMakeInternal(int vlistID)
{
  vlist_to_pointer(vlistID)->internal = 1;
}

void
cdiVlistMakeImmutable(int vlistID)
{
  vlist_to_pointer(vlistID)->immutable = 1;
}

/*
@Function  vlistCreate
@Title     Create a variable list

@Prototype int vlistCreate(void)

@Example
Here is an example using @func{vlistCreate} to create a variable list
and add a variable with @func{vlistDefVar}.

@Source
   ...
int vlistID, varID;
   ...
vlistID = vlistCreate();
varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARYING);
   ...
streamDefVlist(streamID, vlistID);
   ...
vlistDestroy(vlistID);
   ...
@EndSource
@EndFunction
*/
int
vlistCreate(void)
{
  cdiInitialize();

  VLIST_INIT();

  vlist_t *vlistptr = vlist_new_entry(CDI_UNDEFID);
  if (CDI_Debug) Message("create vlistID = %d", vlistptr->self);
  return vlistptr->self;
}

static void
vlist_delete(vlist_t *vlistptr)
{
  int vlistID = vlistptr->self;
  if (CDI_Debug) Message("call to vlist_delete, vlistID = %d", vlistID);

  cdiDeleteKeys(vlistID, CDI_GLOBAL);
  cdiDeleteAtts(vlistID, CDI_GLOBAL);

  int nvars = vlistptr->nvars;
  var_t *vars = vlistptr->vars;

  for (int varID = 0; varID < nvars; varID++)
  {
    if (vars[varID].levinfo) Free(vars[varID].levinfo);

    if (vlistptr->vars[varID].opt_grib_kvpair)
    {
      for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; i++)
      {
        if (vlistptr->vars[varID].opt_grib_kvpair[i].keyword) Free(vlistptr->vars[varID].opt_grib_kvpair[i].keyword);
      }
      Free(vlistptr->vars[varID].opt_grib_kvpair);
    }
    vlistptr->vars[varID].opt_grib_nentries = 0;
    vlistptr->vars[varID].opt_grib_kvpair_size = 0;
    vlistptr->vars[varID].opt_grib_kvpair = NULL;

    cdiDeleteKeys(vlistID, varID);
    cdiDeleteAtts(vlistID, varID);
  }

  if (vars) Free(vars);

  Free(vlistptr);
}

// destroy a vlist object, should always be called through namespace lookup
void
cdiVlistDestroy_(int vlistID, bool assertInternal)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  static const char warningTextUserByInternal[]
      = "Destroying a vlist object that is owned by the user (vlistID=%d).\n"
        "This is most likely because of a missing vlistDestroy() in the application code.\n"
        "If that's not the case, and you are absolutely certain about it, please report the bug.",
      warningTextInternalByUser[] = "Attempt to destroy an internal vlist object by the user (vlistID=%d).";
  static const char *const wText[2] = { warningTextUserByInternal, warningTextInternalByUser };
  if (vlistptr->internal == assertInternal)
  {
    vlist_delete(vlistptr);
    vlist_delete_entry(vlistID);
  }
  else
    Warning(wText[!assertInternal], vlistID);
}

/*
@Function  vlistDestroy
@Title     Destroy a variable list

@Prototype void vlistDestroy(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.

@EndFunction
*/
void
vlistDestroy(int vlistID)
{
  void (*mycdiVlistDestroy_)(int, bool) = (void (*)(int, bool)) namespaceSwitchGet(NSSWITCH_VLIST_DESTROY_).func;
  mycdiVlistDestroy_(vlistID, false);
}

static void
var_copy_entries(var_t *var2, var_t *var1)
{
  var2->opt_grib_kvpair_size = 0;
  var2->opt_grib_kvpair = NULL;
  var2->opt_grib_nentries = 0;

  resize_opt_grib_entries(var2, var1->opt_grib_nentries);
  var2->opt_grib_nentries = var1->opt_grib_nentries;
  if ((var2->opt_grib_nentries > 0) && CDI_Debug) Message("copy %d optional GRIB keywords", var2->opt_grib_nentries);

  for (int i = 0; i < var1->opt_grib_nentries; i++)
  {
    if (CDI_Debug) Message("copy entry \"%s\" ...", var1->opt_grib_kvpair[i].keyword);
    var2->opt_grib_kvpair[i].keyword = NULL;
    if (var1->opt_grib_kvpair[i].keyword != NULL)
    {
      var2->opt_grib_kvpair[i] = var1->opt_grib_kvpair[i];
      var2->opt_grib_kvpair[i].keyword = strdup(var1->opt_grib_kvpair[i].keyword);
      var2->opt_grib_kvpair[i].update = true;
      if (CDI_Debug) Message("done.");
    }
    else
    {
      if (CDI_Debug) Message("not done.");
    }
  }
}

/*
@Function  vlistCopy
@Title     Copy a variable list

@Prototype void vlistCopy(int vlistID2, int vlistID1)
@Parameter
    @Item  vlistID1  Source variable list ID.
    @Item  vlistID2  Target variable list ID.

@Description
The function @func{vlistCopy} copies all entries from vlistID1 to vlistID2.

@EndFunction
*/
void
vlistCopy(int vlistID1, int vlistID2)
{
  vlist_t *vlistptr1 = vlist_to_pointer(vlistID1);
  vlist_t *vlistptr2 = vlist_to_pointer(vlistID2);
  if (CDI_Debug) Message("call to vlistCopy, vlistIDs %d -> %d", vlistID1, vlistID2);

  var_t *vars1 = vlistptr1->vars;
  var_t *vars2 = vlistptr2->vars;
  vlist_copy(vlistptr2, vlistptr1);

  vlistptr2->keys.nelems = 0;
  cdiCopyKeys(vlistID1, CDI_GLOBAL, vlistID2, CDI_GLOBAL);
  vlistptr2->atts.nelems = 0;
  cdiCopyAtts(vlistID1, CDI_GLOBAL, vlistID2, CDI_GLOBAL);

  if (vars1)
  {
    int nvars = vlistptr1->nvars;
    // vlistptr2->varsAllocated = nvars;

    size_t n = (size_t) vlistptr2->varsAllocated;
    vars2 = (var_t *) Realloc(vars2, n * sizeof(var_t));
    memcpy(vars2, vars1, n * sizeof(var_t));
    vlistptr2->vars = vars2;

    for (int varID = 0; varID < nvars; varID++)
    {
      var_copy_entries(&vars2[varID], &vars1[varID]);
      vlistptr2->vars[varID].keys.nelems = 0;
      cdiCopyKeys(vlistID1, varID, vlistID2, varID);

      vlistptr2->vars[varID].atts.nelems = 0;
      cdiCopyAtts(vlistID1, varID, vlistID2, varID);

      if (vars1[varID].levinfo)
      {
        n = (size_t) zaxisInqSize(vars1[varID].zaxisID);
        vars2[varID].levinfo = (levinfo_t *) Malloc(n * sizeof(levinfo_t));
        memcpy(vars2[varID].levinfo, vars1[varID].levinfo, n * sizeof(levinfo_t));
      }
    }
  }
}

/*
@Function  vlistDuplicate
@Title     Duplicate a variable list

@Prototype int vlistDuplicate(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistDuplicate} duplicates the variable list from vlistID1.

@Result
@func{vlistDuplicate} returns an identifier to the duplicated variable list.

@EndFunction
*/
int
vlistDuplicate(int vlistID)
{
  if (CDI_Debug) Message("call to vlistDuplicate");

  int vlistIDnew = vlistCreate();
  vlistCopy(vlistID, vlistIDnew);
  return vlistIDnew;
}

void
vlistClearFlag(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int varID = 0; varID < vlistptr->nvars; varID++)
  {
    vlistptr->vars[varID].flag = false;
    if (vlistptr->vars[varID].levinfo)
    {
      int nlevs = zaxisInqSize(vlistptr->vars[varID].zaxisID);
      for (int levID = 0; levID < nlevs; levID++) vlistptr->vars[varID].levinfo[levID].flag = false;
    }
  }
}

struct vgzSearchState
{
  int resIDValue;
  int zaxistype;
  int nlevels;
  const double *levels;
  const double *lbounds;
  const double *ubounds;
};

static enum cdiApplyRet
vgzZAxisSearch(int id, void *res, void *data)
{
  struct vgzSearchState *state = (struct vgzSearchState *) data;
  (void) res;
  if (zaxis_compare(id, state->zaxistype, state->nlevels, state->levels, state->lbounds, state->ubounds, NULL, NULL, 0, -1)
      == false)
  {
    state->resIDValue = id;
    return CDI_APPLY_STOP;
  }

  return CDI_APPLY_GO_ON;
}

static int
vlist_generate_zaxis(int vlistID, int zaxistype, int nlevels, const double *levels, const double *lbounds, const double *ubounds,
                     int vctsize, const double *vct, const char **cvals, size_t clen)
{
  int zaxisID = CDI_UNDEFID;
  bool zaxisdefined = false;
  bool zaxisglobdefined = false;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int nzaxis = vlistptr->nzaxis;

  bool hasBounds = (lbounds && ubounds);

  for (int index = 0; index < nzaxis; ++index)
  {
    zaxisID = vlistptr->zaxisIDs[index];

    if (zaxis_compare(zaxisID, zaxistype, nlevels, levels, lbounds, ubounds, NULL, NULL, 0, -1) == false)
    {
      zaxisdefined = true;
      break;
    }
  }

  if (!zaxisdefined)
  {
    struct vgzSearchState query;
    query.zaxistype = zaxistype;
    query.nlevels = nlevels;
    query.levels = levels;
    query.lbounds = lbounds;
    query.ubounds = ubounds;

    if ((zaxisglobdefined = (cdiResHFilterApply(getZaxisOps(), vgzZAxisSearch, &query) == CDI_APPLY_STOP)))
      zaxisID = query.resIDValue;
  }

  if (!zaxisdefined)
  {
    if (!zaxisglobdefined)
    {
      zaxisID = zaxisCreate(zaxistype, nlevels);
      zaxisDefLevels(zaxisID, levels);

      if (zaxistype == ZAXIS_CHAR) zaxisDefCvals(zaxisID, cvals, (int) clen);

      if (hasBounds)
      {
        zaxisDefLbounds(zaxisID, lbounds);
        zaxisDefUbounds(zaxisID, ubounds);
      }

      if (zaxistype == ZAXIS_HYBRID && vctsize > 0) zaxisDefVct(zaxisID, vctsize, vct);
    }

    nzaxis = vlistptr->nzaxis;
    vlistptr->zaxisIDs[nzaxis] = zaxisID;
    vlistptr->nzaxis++;
  }

  return zaxisID;
}

static void
delete_chunks(int vlistID, int varID)
{
  int chunkKeys[4] = { CDI_KEY_CHUNKSIZE_DIMX, CDI_KEY_CHUNKSIZE_DIMY, CDI_KEY_CHUNKSIZE_DIMZ, CDI_KEY_CHUNKSIZE_DIMT };
  for (int i = 0; i < 4; ++i)
  {
    int chunkSize = 0;
    cdiInqKeyInt(vlistID, varID, chunkKeys[i], &chunkSize);
    if (chunkSize != 0) cdiDeleteKey(vlistID, varID, chunkKeys[i]);
  }

  int chunkSize = 0;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_CHUNKSIZE, &chunkSize);
  if (chunkSize > 0) cdiDeleteKey(vlistID, varID, CDI_KEY_CHUNKSIZE);
}

/*
@Function  vlistCopyFlag
@Title     Copy some entries of a variable list

@Prototype void vlistCopyFlag(int vlistID2, int vlistID1)
@Parameter
    @Item  vlistID2  Target variable list ID.
    @Item  vlistID1  Source variable list ID.

@Description
The function @func{vlistCopyFlag} copies all entries with a flag from vlistID1 to vlistID2.

@EndFunction
*/
void
vlistCopyFlag(int vlistID2, int vlistID1)
{
  vlist_t *vlistptr1 = vlist_to_pointer(vlistID1);
  vlist_t *vlistptr2 = vlist_to_pointer(vlistID2);
  var_t *vars1 = vlistptr1->vars;
  // var_t *vars2 = vlistptr2->vars;

  vlist_copy(vlistptr2, vlistptr1);

  vlistptr2->keys.nelems = 0;
  cdiCopyKeys(vlistID1, CDI_GLOBAL, vlistID2, CDI_GLOBAL);
  vlistptr2->atts.nelems = 0;
  cdiCopyAtts(vlistID1, CDI_GLOBAL, vlistID2, CDI_GLOBAL);

  if (vlistptr1->vars)
  {
    vlistptr2->ngrids = 0;
    vlistptr2->nzaxis = 0;

    int nvars = vlistptr1->nvars;
    int nvars2 = 0;
    for (int varID = 0; varID < nvars; varID++) nvars2 += vars1[varID].flag;

    vlistptr2->nvars = nvars2;
    vlistptr2->varsAllocated = nvars2;
    var_t *vars2 = (nvars2 > 0) ? (var_t *) Malloc((size_t) nvars2 * sizeof(var_t)) : NULL;

    vlistptr2->vars = vars2;

    int varID2 = 0;
    for (int varID = 0; varID < nvars; varID++)
      if (vars1[varID].flag)
      {
        vlistptr2->vars[varID2].flag = false;
        int zaxisID = vlistptr1->vars[varID].zaxisID;
        int gridID = vlistptr1->vars[varID].gridID;
        int subtypeID = vlistptr1->vars[varID].subtypeID;

        memcpy(&vars2[varID2], &vars1[varID], sizeof(var_t));

        vars1[varID].fvarID = varID2;
        vars2[varID2].fvarID = varID;

        vars2[varID2].mvarID = varID2;

        var_copy_entries(&vars2[varID2], &vars1[varID]);
        vlistptr2->vars[varID2].keys.nelems = 0;
        cdiCopyKeys(vlistID1, varID, vlistID2, varID2);

        vlistptr2->vars[varID2].atts.nelems = 0;
        cdiCopyAtts(vlistID1, varID, vlistID2, varID2);

        int nlevs = zaxisInqSize(vars1[varID].zaxisID);
        int nlevs2 = 0;
        if (vars1[varID].levinfo)
          for (int levID = 0; levID < nlevs; levID++) nlevs2 += vars1[varID].levinfo[levID].flag;

        vars2[varID2].levinfo = (levinfo_t *) Malloc((size_t) nlevs2 * sizeof(levinfo_t));

        if (nlevs != nlevs2)
        {
          int nvct = 0;
          double *levels = NULL;
          double *lbounds = NULL, *ubounds = NULL;
          const double *vct = NULL;

          if (!vars1[varID].levinfo) cdiVlistCreateVarLevInfo(vlistptr1, varID);

          zaxisID = vars1[varID].zaxisID;
          int zaxisType = zaxisInqType(zaxisID);

          int levID2 = 0;
          for (int levID = 0; levID < nlevs; levID++)
            if (vars1[varID].levinfo[levID].flag)
            {
              vars1[varID].levinfo[levID].flevelID = levID2;
              vars1[varID].levinfo[levID].mlevelID = levID2;
            }

          if (zaxisInqLevels(zaxisID, NULL))
          {
            levels = (double *) Malloc((size_t) nlevs2 * sizeof(double));

            levID2 = 0;
            for (int levID = 0; levID < nlevs; ++levID)
              if (vars1[varID].levinfo[levID].flag) levels[levID2++] = zaxisInqLevel(zaxisID, levID);
          }

          if (zaxisType == ZAXIS_HYBRID)
          {
            nvct = zaxisInqVctSize(zaxisID);
            vct = zaxisInqVctPtr(zaxisID);
          }

          size_t clen2 = 0;
          char **cvals2 = NULL;
#ifndef USE_MPI
          if (zaxisType == ZAXIS_CHAR)
          {
            char **cvals1 = zaxisInqCValsPtr(zaxisID);
            size_t clen1 = (size_t) zaxisInqCLen(zaxisID);
            for (int levID = 0; levID < nlevs; ++levID)
              if (vars1[varID].levinfo[levID].flag)
              {
                size_t testlen = clen1;
                while (cvals1[levID][testlen] == ' ') testlen--;
                if (clen2 < testlen) clen2 = testlen;
              }
            cvals2 = (char **) Malloc((size_t) nlevs2 * sizeof(char *));
            levID2 = 0;

            for (int levID = 0; levID < nlevs; ++levID)
              if (vars1[varID].levinfo[levID].flag)
              {
                cvals2[levID2] = (char *) Malloc((size_t) (clen2) * sizeof(char));
                memcpy(cvals2[levID2], cvals1[levID], clen2 * sizeof(char));
                levID2++;
              }
          }
#endif

          if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
          {
            lbounds = (double *) Malloc(2 * (size_t) nlevs2 * sizeof(double));
            ubounds = lbounds + nlevs2;

            double *lbounds1 = (double *) Malloc(2 * (size_t) nlevs * sizeof(double)), *ubounds1 = lbounds1 + nlevs;

            zaxisInqLbounds(zaxisID, lbounds1);
            zaxisInqUbounds(zaxisID, ubounds1);

            levID2 = 0;
            for (int levID = 0; levID < nlevs; ++levID)
              if (vars1[varID].levinfo[levID].flag)
              {
                lbounds[levID2] = lbounds1[levID];
                ubounds[levID2] = ubounds1[levID];
                levID2++;
              }

            Free(lbounds1);
          }

          int zaxisID2 = vlist_generate_zaxis(vlistID2, zaxisType, nlevs2, levels, lbounds, ubounds, nvct, vct,
                                              (const char **) cvals2, clen2);
          if (levels) Free(levels);
          if (lbounds) Free(lbounds);
          if (cvals2)
          {
            for (int levID = 0; levID < nlevs2; ++levID) Free(cvals2[levID]);
            Free(cvals2);
          }

          char ctemp[CDI_MAX_NAME];
          int length = CDI_MAX_NAME;
          cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, ctemp, &length);
          cdiDefKeyString(zaxisID2, CDI_GLOBAL, CDI_KEY_NAME, ctemp);
          length = CDI_MAX_NAME;
          cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_LONGNAME, ctemp, &length);
          cdiDefKeyString(zaxisID2, CDI_GLOBAL, CDI_KEY_LONGNAME, ctemp);
          length = CDI_MAX_NAME;
          cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, ctemp, &length);
          cdiDefKeyString(zaxisID2, CDI_GLOBAL, CDI_KEY_UNITS, ctemp);

          zaxisDefDatatype(zaxisID2, zaxisInqDatatype(zaxisID));
          zaxisDefPositive(zaxisID2, zaxisInqPositive(zaxisID));

          if (zaxisType == ZAXIS_CHAR)
          {
            char dimname[CDI_MAX_NAME + 3];
            length = sizeof(dimname);
            cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
            if (dimname[0] == 0)
            {
              memcpy(dimname, "area_type", 10);
              dimname[10] = 0;
            }
            cdiDefKeyString(zaxisID2, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname);
          }

          if (zaxisType == ZAXIS_GENERIC) cdiCopyKey(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, zaxisID2);

          cdiCopyAtts(zaxisID, CDI_GLOBAL, zaxisID2, CDI_GLOBAL);

          zaxisID = zaxisID2;
          vars2[varID2].zaxisID = zaxisID2;
          delete_chunks(vlistID2, varID2);
        }

        for (int levID = 0; levID < nlevs2; levID++)
        {
          vars2[varID2].levinfo[levID].flag = false;
          vars2[varID2].levinfo[levID].index = -1;
        }

        int levID2 = 0;
        for (int levID = 0; levID < nlevs; levID++)
          if (vars1[varID].levinfo[levID].flag)
          {
            vars2[varID2].levinfo[levID2].flevelID = levID;
            vars2[varID2].levinfo[levID2].mlevelID = levID2;
            levID2++;
          }

        vlistAdd2GridIDs(vlistptr2, gridID);
        vlistAdd2ZaxisIDs(vlistptr2, zaxisID);
        vlistAdd2SubtypeIDs(vlistptr2, subtypeID);

        varID2++;
      }
  }
}

/*
@Function  vlistCat
@Title     Concatenate two variable lists

@Prototype void vlistCat(int vlistID2, int vlistID1)
@Parameter
    @Item  vlistID2  Target variable list ID.
    @Item  vlistID1  Source variable list ID.

@Description
Concatenate the variable list vlistID1 at the end of vlistID2.

@EndFunction
*/
void
vlistCat(int vlistID2, int vlistID1)
{
  vlist_t *vlistptr1 = vlist_to_pointer(vlistID1);
  vlist_t *vlistptr2 = vlist_to_pointer(vlistID2);
  var_t *vars1 = vlistptr1->vars;
  var_t *vars2 = vlistptr2->vars;
  int nvars1 = vlistptr1->nvars;
  int nvars2 = vlistptr2->nvars;
  int nvars = nvars1 + nvars2;
  vlistptr2->nvars = nvars;

  if (nvars > vlistptr2->varsAllocated)
  {
    vlistptr2->varsAllocated = nvars;
    vars2 = (var_t *) Realloc(vars2, (size_t) nvars * sizeof(var_t));
    vlistptr2->vars = vars2;
  }
  memcpy(vars2 + nvars2, vars1, (size_t) nvars1 * sizeof(var_t));

  for (int varID = 0; varID < nvars1; varID++)
  {
    int varID2 = varID + nvars2;
    vars1[varID].fvarID = varID2;
    vars2[varID2].fvarID = varID;

    vars1[varID].mvarID = varID2;
    vars2[varID2].mvarID = varID;

    if (vars1[varID].param < 0)
    {
      int pnum, pcat, pdis;
      cdiDecodeParam(vars1[varID].param, &pnum, &pcat, &pdis);
      pnum = -(varID2 + 1);
      vars2[varID2].param = cdiEncodeParam(pnum, pcat, pdis);
    }

    var_copy_entries(&vars2[varID2], &vars1[varID]);
    vars2[varID2].keys.nelems = 0;
    cdiCopyKeys(vlistID1, varID, vlistID2, varID2);

    if (vars1[varID].levinfo)
    {
      size_t nlevs = (size_t) zaxisInqSize(vars1[varID].zaxisID);
      vars2[varID2].levinfo = (levinfo_t *) Malloc(nlevs * sizeof(levinfo_t));
      memcpy(vars2[varID2].levinfo, vars1[varID].levinfo, nlevs * sizeof(levinfo_t));
    }

    vars2[varID2].atts.nelems = 0;
    cdiCopyAtts(vlistID1, varID, vlistID2, varID2);

    vlistAdd2GridIDs(vlistptr2, vars1[varID].gridID);
    vlistAdd2ZaxisIDs(vlistptr2, vars1[varID].zaxisID);
    vlistAdd2SubtypeIDs(vlistptr2, vars1[varID].subtypeID);
  }
}

/*
@Function  vlistMerge
@Title     Merge two variable lists

@Prototype void vlistMerge(int vlistID2, int vlistID1)
@Parameter
    @Item  vlistID2  Target variable list ID.
    @Item  vlistID1  Source variable list ID.

@Description
Merge the variable list vlistID1 to the variable list vlistID2.

@EndFunction
*/
void
vlistMerge(int vlistID2, int vlistID1)
{
  int varID = 0;
  vlist_t *vlistptr1 = vlist_to_pointer(vlistID1);
  vlist_t *vlistptr2 = vlist_to_pointer(vlistID2);
  var_t *vars1 = vlistptr1->vars;
  var_t *vars2 = vlistptr2->vars;
  int nvars1 = vlistptr1->nvars;
  int nvars2 = vlistptr2->nvars;

  if (nvars1 == nvars2)
  {
    char name1[CDI_MAX_NAME], name2[CDI_MAX_NAME];
    for (varID = 0; varID < nvars2; varID++)
    {
      SizeType ngp1 = gridInqSize(vars1[varID].gridID);
      SizeType ngp2 = gridInqSize(vars2[varID].gridID);
      if (ngp1 != ngp2) break;

      int length = CDI_MAX_NAME;
      (void) cdiInqKeyString(vlistID1, varID, CDI_KEY_NAME, name1, &length);
      length = CDI_MAX_NAME;
      (void) cdiInqKeyString(vlistID2, varID, CDI_KEY_NAME, name2, &length);

      if (*name1 && *name2)
      {
        if (!str_is_equal(name1, name2)) break;
      }
      else
      {
        if (vars1[varID].param != vars2[varID].param) break;
      }
    }
  }

  if (varID == nvars2) /* same variables in vlistID1 and vlistID2 */
  {
    for (varID = 0; varID < nvars2; varID++)
    {
      vars1[varID].fvarID = varID;
      vars2[varID].fvarID = varID;

      vars1[varID].mvarID = varID;
      vars2[varID].mvarID = varID;

      int nlevs1 = zaxisInqSize(vars1[varID].zaxisID);
      int nlevs2 = zaxisInqSize(vars2[varID].zaxisID);

      int nlevs = nlevs1 + nlevs2;

      /*
      fprintf(stderr, "var %d %d %d %d %d\n", varID, nlevs1, nlevs2, nlevs, sizeof(levinfo_t));
      */
      if (vars1[varID].levinfo)
      {
        vars2[varID].levinfo = (levinfo_t *) Realloc(vars2[varID].levinfo, (size_t) nlevs * sizeof(levinfo_t));

        memcpy(vars2[varID].levinfo + nlevs2, vars1[varID].levinfo, (size_t) nlevs1 * sizeof(levinfo_t));
      }
      else
        cdiVlistCreateVarLevInfo(vlistptr1, varID);

      for (int levID = 0; levID < nlevs1; levID++) vars1[varID].levinfo[levID].mlevelID = nlevs2 + levID;
    }

    bool *lvar = (bool *) Calloc((size_t) nvars2, sizeof(bool));

    for (varID = 0; varID < nvars2; varID++)
    {
      if (lvar[varID] == true) continue;

      int zaxisID1 = vars1[varID].zaxisID;
      int zaxisID2 = vars2[varID].zaxisID;
      // nlevs1 = zaxisInqSize(vars1[varID].zaxisID);
      // nlevs2 = zaxisInqSize(vars2[varID].zaxisID);
      int nlevs1 = zaxisInqSize(zaxisID1);
      int nlevs2 = zaxisInqSize(zaxisID2);
      // fprintf(stderr, "zaxis %d %d %d %d\n", zaxisID1, zaxisID2, nlevs1, nlevs2);

      int nlevs = nlevs1 + nlevs2;

      int zaxisID = zaxisDuplicate(zaxisID2);
      zaxisResize(zaxisID, nlevs);

      if (zaxisInqLevels(zaxisID1, NULL))
      {
        double *levels = (double *) Malloc((size_t) nlevs1 * sizeof(double));

        zaxisInqLevels(zaxisID1, levels);
        /*
          for (int levID = 0; levID < nlevs1; levID++)
            fprintf(stderr, "%d %d %d %d %d %g\n", varID, levID, nlevs1, nlevs2, vars2[varID].nlevs, levels[levID]);
        */
        for (int levID = 0; levID < nlevs1; levID++) zaxisDefLevel(zaxisID, nlevs2 + levID, levels[levID]);

        Free(levels);
      }

      for (int index = 0; index < vlistptr2->nzaxis; index++)
        if (vlistptr2->zaxisIDs[index] == zaxisID2) vlistptr2->zaxisIDs[index] = zaxisID;

      for (int varID2 = 0; varID2 < nvars2; varID2++)
        if (lvar[varID2] == false && vars2[varID2].zaxisID == zaxisID2)
        {
          vars2[varID2].zaxisID = zaxisID;
          lvar[varID2] = true;
        }
    }

    Free(lvar);
  }
  else { vlistCat(vlistID2, vlistID1); }
}

/*
@Function  vlistNvars
@Title     Number of variables in a variable list

@Prototype int vlistNvars(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistNvars} returns the number of variables in the variable list vlistID.

@Result
@func{vlistNvars} returns the number of variables in a variable list.

@EndFunction
*/
int
vlistNvars(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->nvars;
}

int
vlistNumFields(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int nrecs = 0;
  for (int varID = 0; varID < vlistptr->nvars; varID++) nrecs += zaxisInqSize(vlistptr->vars[varID].zaxisID);

  return nrecs;
}

int
vlistNumber(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int datatype = vlistptr->vars[0].datatype;
  int number = (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64) ? CDI_COMP : CDI_REAL;

  for (int varID = 1; varID < vlistptr->nvars; varID++)
  {
    datatype = vlistptr->vars[varID].datatype;
    int number2 = (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64) ? CDI_COMP : CDI_REAL;
    if (number2 != number)
    {
      number = CDI_BOTH;
      break;
    }
  }

  return number;
}

/*
@Function  vlistNumGrids
@Title     Number of grids in a variable list

@Prototype int vlistNumGrids(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistNumGrids} returns the number of grids in the variable list vlistID.

@Result
@func{vlistNumGrids} returns the number of grids in a variable list.

@EndFunction
*/
int
vlistNumGrids(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->ngrids;
}

int
vlistNgrids(int vlistID)
{
  return vlistNumGrids(vlistID);
}

/*
@Function  vlistNumZaxis
@Title     Number of zaxis in a variable list

@Prototype int vlistNumZaxis(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistNumZaxis} returns the number of zaxis in the variable list vlistID.

@Result
@func{vlistNumZaxis} returns the number of zaxis in a variable list.

@EndFunction
*/
int
vlistNumZaxis(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->nzaxis;
}

int
vlistNzaxis(int vlistID)
{
  return vlistNumZaxis(vlistID);
}

int
vlistNsubtypes(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->nsubtypes;
}

void
vlistDefNtsteps(int vlistID, int nts)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->ntsteps != nts)
  {
    vlistptr->ntsteps = nts;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

// This function is used in CDO!
int
vlistNtsteps(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return (int) vlistptr->ntsteps;
}

static void
vlistPrintKernel(vlist_t *vlistptr, FILE *fp)
{
  int vlistID = vlistptr->self;
  fprintf(fp, "#\n# vlistID %d\n#\n", vlistID);

  int nvars = vlistptr->nvars;

  fprintf(fp,
          "nvars    : %d\n"
          "ngrids   : %d\n"
          "nzaxis   : %d\n"
          "nsubtypes: %d\n"
          "taxisID  : %d\n"
          "instID   : %d\n"
          "modelID  : %d\n"
          "tableID  : %d\n",
          nvars, vlistptr->ngrids, vlistptr->nzaxis, vlistptr->nsubtypes, vlistptr->taxisID, vlistptr->instID, vlistptr->modelID,
          vlistptr->tableID);

  if (nvars > 0)
  {
    fprintf(fp, " varID param    gridID zaxisID stypeID tsteptype flag name     longname         units\n");
    for (int varID = 0; varID < nvars; varID++)
    {
      int param = vlistptr->vars[varID].param;
      int gridID = vlistptr->vars[varID].gridID;
      int zaxisID = vlistptr->vars[varID].zaxisID;
      int subtypeID = vlistptr->vars[varID].subtypeID;
      int tsteptype = vlistptr->vars[varID].tsteptype;
      char name[CDI_MAX_NAME], longname[CDI_MAX_NAME], units[CDI_MAX_NAME];
      int length = CDI_MAX_NAME;
      (void) cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length);
      length = CDI_MAX_NAME;
      (void) cdiInqKeyString(vlistID, varID, CDI_KEY_LONGNAME, longname, &length);
      length = CDI_MAX_NAME;
      (void) cdiInqKeyString(vlistID, varID, CDI_KEY_UNITS, units, &length);
      int flag = vlistptr->vars[varID].flag;

      char paramstr[32];
      cdiParamToString(param, paramstr, sizeof(paramstr));
      fprintf(fp, "%6d %-8s %6d  %6d  %6d  %6d  %5d %-8s %s [%s]\n", varID, paramstr, gridID, zaxisID, subtypeID, tsteptype, flag,
              name, longname, units);
    }

    fputs("\n"
          " varID  levID fvarID flevID mvarID mlevID  index  dtype  flag  level\n",
          fp);
    for (int varID = 0; varID < nvars; varID++)
    {
      int zaxisID = vlistptr->vars[varID].zaxisID;
      int nlevs = zaxisInqSize(zaxisID);
      int fvarID = vlistptr->vars[varID].fvarID;
      int mvarID = vlistptr->vars[varID].mvarID;
      int dtype = vlistptr->vars[varID].datatype;
      for (int levID = 0; levID < nlevs; levID++)
      {
        levinfo_t li;
        if (vlistptr->vars[varID].levinfo)
          li = vlistptr->vars[varID].levinfo[levID];
        else
          li = DEFAULT_LEVINFO(levID);
        int flevID = li.flevelID;
        int mlevID = li.mlevelID;
        int index = li.index;
        int flag = li.flag;

        double level = zaxisInqLevels(zaxisID, NULL) ? zaxisInqLevel(zaxisID, levID) : levID + 1;

        fprintf(fp, "%6d %6d %6d %6d %6d %6d %6d %6d %5d  %.9g\n", varID, levID, fvarID, flevID, mvarID, mlevID, index, dtype, flag,
                level);
      }
    }

    fputs("\n"
          " varID  size\n",
          fp);
    for (int varID = 0; varID < nvars; varID++)
      fprintf(fp, "%3d %8zu\n", varID,
              (size_t) zaxisInqSize(vlistptr->vars[varID].zaxisID) * (size_t) gridInqSize(vlistptr->vars[varID].gridID));
  }
}

void
vlistPrint(int vlistID)
{
  if (vlistID == CDI_UNDEFID) return;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  vlistPrintKernel(vlistptr, stdout);
}

/*
@Function  vlistDefTaxis
@Title     Define the time axis

@Prototype void vlistDefTaxis(int vlistID, int taxisID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}.

@Description
The function @func{vlistDefTaxis} defines the time axis of a variable list.

@EndFunction
*/
void
vlistDefTaxis(int vlistID, int taxisID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->taxisID != taxisID)
  {
    // FIXME: This code seems to leak a taxis_t object if `vlistptr->taxisID` was valid before the call to vlistDefTaxis.
    vlistptr->taxisID = taxisID;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  vlistInqTaxis
@Title     Get the time axis

@Prototype int vlistInqTaxis(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistInqTaxis} returns the time axis of a variable list.

@Result
@func{vlistInqTaxis} returns an identifier to the time axis.

@EndFunction
*/
int
vlistInqTaxis(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->taxisID;
}

void
vlistDefTable(int vlistID, int tableID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->tableID != tableID)
  {
    vlistptr->tableID = tableID;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqTable(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->tableID;
}

void
vlistDefInstitut(int vlistID, int instID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->instID != instID)
  {
    vlistptr->instID = instID;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqInstitut(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int instID = vlistptr->instID;

  if (instID == CDI_UNDEFID)
  {
    instID = vlistInqVarInstitut(vlistID, 0);

    for (int varID = 1; varID < vlistptr->nvars; varID++)
      if (instID != vlistInqVarInstitut(vlistID, varID))
      {
        instID = CDI_UNDEFID;
        break;
      }
    vlistDefInstitut(vlistID, instID);
  }

  return instID;
}

void
vlistDefModel(int vlistID, int modelID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->modelID != modelID)
  {
    vlistptr->modelID = modelID;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqModel(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int modelID = vlistptr->modelID;

  if (modelID == CDI_UNDEFID)
  {
    modelID = vlistInqVarModel(vlistID, 0);

    for (int varID = 1; varID < vlistptr->nvars; varID++)
      if (modelID != vlistInqVarModel(vlistID, varID))
      {
        modelID = CDI_UNDEFID;
        break;
      }

    vlistDefModel(vlistID, modelID);
  }

  return modelID;
}

SizeType
vlistGridsizeMax(int vlistID)
{
  SizeType gridsizemax = 0;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int index = 0; index < vlistptr->ngrids; index++)
  {
    int gridID = vlistptr->gridIDs[index];
    SizeType gridsize = gridInqSize(gridID);
    if (gridsize > gridsizemax) gridsizemax = gridsize;
  }

  return gridsizemax;
}

int
vlistGrid(int vlistID, int index)
{
  int gridID = CDI_UNDEFID;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (index < vlistptr->ngrids && index >= 0) gridID = vlistptr->gridIDs[index];

  return gridID;
}

int
vlistGridIndex(int vlistID, int gridID)
{
  int index;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (index = 0; index < vlistptr->ngrids; index++)
    if (gridID == vlistptr->gridIDs[index]) break;

  if (index == vlistptr->ngrids) index = -1;

  return index;
}

void
vlistChangeGridIndex(int vlistID, int index, int gridID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int gridIDold = vlistptr->gridIDs[index];
  if (gridIDold != gridID)
  {
    vlistptr->gridIDs[index] = gridID;

    int nvars = vlistptr->nvars;
    for (int varID = 0; varID < nvars; varID++)
      if (vlistptr->vars[varID].gridID == gridIDold)
      {
        vlistptr->vars[varID].gridID = gridID;
        delete_chunks(vlistID, varID);
        if (gridInqXsize(gridIDold) == 0 && gridInqXsize(gridID) > 0 && vlistInqVarXYZ(vlistID, varID) != 0)
          vlistDefVarXYZ(vlistID, varID, 0);
      }
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

void
vlistChangeGrid(int vlistID, int gridID1, int gridID2)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (gridID1 != gridID2)
  {
    int ngrids = vlistptr->ngrids;
    for (int index = 0; index < ngrids; index++)
    {
      if (vlistptr->gridIDs[index] == gridID1)
      {
        vlistptr->gridIDs[index] = gridID2;
        break;
      }
    }
    int nvars = vlistptr->nvars;
    for (int varID = 0; varID < nvars; varID++)
      if (vlistptr->vars[varID].gridID == gridID1)
      {
        vlistptr->vars[varID].gridID = gridID2;
        delete_chunks(vlistID, varID);
      }
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistZaxis(int vlistID, int index)
{
  int zaxisID = CDI_UNDEFID;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (index < vlistptr->nzaxis && index >= 0) zaxisID = vlistptr->zaxisIDs[index];

  return zaxisID;
}

int
vlistZaxisIndex(int vlistID, int zaxisID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int index;
  for (index = 0; index < vlistptr->nzaxis; index++)
    if (zaxisID == vlistptr->zaxisIDs[index]) break;

  if (index == vlistptr->nzaxis) index = -1;

  return index;
}

void
vlistChangeZaxisIndex(int vlistID, int index, int zaxisID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int zaxisIDold = vlistptr->zaxisIDs[index];
  if (zaxisIDold != zaxisID)
  {
    vlistptr->zaxisIDs[index] = zaxisID;

    int nlevs = zaxisInqSize(zaxisID), nlevsOld = zaxisInqSize(zaxisIDold);
    int nvars = vlistptr->nvars;
    for (int varID = 0; varID < nvars; varID++)
      if (vlistptr->vars[varID].zaxisID == zaxisIDold)
      {
        vlistptr->vars[varID].zaxisID = zaxisID;
        delete_chunks(vlistID, varID);
        if (vlistptr->vars[varID].levinfo && nlevs != nlevsOld)
        {
          vlistptr->vars[varID].levinfo = (levinfo_t *) Realloc(vlistptr->vars[varID].levinfo, (size_t) nlevs * sizeof(levinfo_t));

          for (int levID = 0; levID < nlevs; levID++) vlistptr->vars[varID].levinfo[levID] = DEFAULT_LEVINFO(levID);
        }
      }
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

void
vlistChangeZaxis(int vlistID, int zaxisID1, int zaxisID2)
{
  int nlevs1 = zaxisInqSize(zaxisID1), nlevs2 = zaxisInqSize(zaxisID2);
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int nzaxis = vlistptr->nzaxis;
  for (int index = 0; index < nzaxis; index++)
  {
    if (vlistptr->zaxisIDs[index] == zaxisID1)
    {
      vlistptr->zaxisIDs[index] = zaxisID2;
      break;
    }
  }

  int nvars = vlistptr->nvars;
  for (int varID = 0; varID < nvars; varID++)
    if (vlistptr->vars[varID].zaxisID == zaxisID1)
    {
      vlistptr->vars[varID].zaxisID = zaxisID2;
      delete_chunks(vlistID, varID);

      if (vlistptr->vars[varID].levinfo && nlevs2 != nlevs1)
      {
        vlistptr->vars[varID].levinfo = (levinfo_t *) Realloc(vlistptr->vars[varID].levinfo, (size_t) nlevs2 * sizeof(levinfo_t));

        for (int levID = 0; levID < nlevs2; levID++) vlistptr->vars[varID].levinfo[levID] = DEFAULT_LEVINFO(levID);
      }
    }
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

int
vlistSubtype(int vlistID, int index)
{
  int subtypeID = CDI_UNDEFID;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (index < vlistptr->nsubtypes && index >= 0) subtypeID = vlistptr->subtypeIDs[index];

  return subtypeID;
}

int
vlistSubtypeIndex(int vlistID, int subtypeID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int index;
  for (index = vlistptr->nsubtypes; index--;)
    if (subtypeID == vlistptr->subtypeIDs[index]) break;

  return index;
}

int
vlistHasTime(int vlistID)
{
  bool hastime = false;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (!(CDI_Reduce_Dim && vlistptr->ntsteps == 1))
  {
    size_t nvars = vlistptr->nvars > 0 ? (size_t) vlistptr->nvars : (size_t) 0;
    var_t *restrict vars = vlistptr->vars;
    for (size_t varID = 0; varID < nvars; varID++)
      if (vars[varID].timetype != TIME_CONSTANT)
      {
        hastime = true;
        break;
      }
  }

  return (int) hastime;
}

enum
{
  VLIST_PACK_INT_SELF,
  VLIST_PACK_INT_NVARS,
  VLIST_PACK_INT_TAXISID,
  VLIST_PACK_INT_TABLEID,
  VLIST_PACK_INT_INSTID,
  VLIST_PACK_INT_MODELID,
  vlistNints,
};

static int
vlistTxCode(void *vlistptr)
{
  (void) vlistptr;
  return VLIST;
}

static int
vlistGetSizeP(void *vlistptr, void *context)
{
  vlist_t *p = (vlist_t *) vlistptr;
  int txsize = serializeGetSize(vlistNints, CDI_DATATYPE_INT, context);
  txsize += serializeGetSize(1, CDI_DATATYPE_LONG, context);
  txsize += cdiAttsGetSize(p, CDI_GLOBAL, context);
  for (int varID = 0; varID < p->nvars; varID++) txsize += vlistVarGetPackSize(p, varID, context);
  return txsize;
}

static void
vlistPackP(void *vlistptr, void *buf, int size, int *position, void *context)
{
  int tempbuf[vlistNints];
  vlist_t *p = (vlist_t *) vlistptr;
  tempbuf[VLIST_PACK_INT_SELF] = p->self;
  tempbuf[VLIST_PACK_INT_NVARS] = p->nvars;
  tempbuf[VLIST_PACK_INT_TAXISID] = p->taxisID;
  tempbuf[VLIST_PACK_INT_TABLEID] = p->tableID;
  tempbuf[VLIST_PACK_INT_INSTID] = p->instID;
  tempbuf[VLIST_PACK_INT_MODELID] = p->modelID;
  serializePack(tempbuf, vlistNints, CDI_DATATYPE_INT, buf, size, position, context);
  serializePack(&p->ntsteps, 1, CDI_DATATYPE_LONG, buf, size, position, context);

  cdiAttsPack(p, CDI_GLOBAL, buf, size, position, context);
  for (int varID = 0; varID < p->nvars; varID++) { vlistVarPack(p, varID, (char *) buf, size, position, context); }
}

int
vlistUnpack(char *buf, int size, int *position, int originNamespace, void *context, int force_id)
{
#define adaptKey(key) (namespaceAdaptKey((key), originNamespace))
  int tempbuf[vlistNints];
  serializeUnpack(buf, size, position, tempbuf, vlistNints, CDI_DATATYPE_INT, context);
  int nvars = tempbuf[VLIST_PACK_INT_NVARS];
  int targetID = force_id ? adaptKey(tempbuf[VLIST_PACK_INT_SELF]) : CDI_UNDEFID;
  vlist_t *p = vlist_new_entry(targetID);
  xassert(!force_id || p->self == targetID);
  if (!force_id) targetID = p->self;
  cdiVlistMakeInternal(p->self);
  p->taxisID = adaptKey(tempbuf[VLIST_PACK_INT_TAXISID]);
  p->tableID = tempbuf[VLIST_PACK_INT_TABLEID];
  p->instID = adaptKey(tempbuf[VLIST_PACK_INT_INSTID]);
  p->modelID = adaptKey(tempbuf[VLIST_PACK_INT_MODELID]);
  serializeUnpack(buf, size, position, &p->ntsteps, 1, CDI_DATATYPE_LONG, context);
  cdiAttsUnpack(targetID, CDI_GLOBAL, buf, size, position, context);
  for (int varID = 0; varID < nvars; varID++) vlistVarUnpack(targetID, buf, size, position, originNamespace, context);
  reshSetStatus(targetID, &vlistOps, reshGetStatus(targetID, &vlistOps) & ~RESH_SYNC_BIT);
#undef adaptKey
  return targetID;
}

void
vlist_check_contents(int vlistID)
{
  int nzaxis = vlistNumZaxis(vlistID);
  for (int index = 0; index < nzaxis; index++)
  {
    int zaxisID = vlistZaxis(vlistID, index);
    if (zaxisInqType(zaxisID) == ZAXIS_GENERIC) cdiCheckZaxis(zaxisID);
  }
}

/* Resizes and initializes opt_grib_kvpair data structure. */
void
resize_opt_grib_entries(var_t *var, int nentries)
{
  if (var->opt_grib_kvpair_size >= nentries) return;  // nothing to do; array is still large enough

  if (CDI_Debug) Message("resize data structure, %d -> %d", var->opt_grib_kvpair_size, nentries);

  int new_size = (2 * var->opt_grib_kvpair_size) > nentries ? (2 * var->opt_grib_kvpair_size) : nentries;
  opt_key_val_pair_t *tmp = (opt_key_val_pair_t *) Malloc((size_t) new_size * sizeof(opt_key_val_pair_t));
  for (int i = 0; i < var->opt_grib_kvpair_size; ++i) { tmp[i] = var->opt_grib_kvpair[i]; }
  for (int i = var->opt_grib_kvpair_size; i < new_size; ++i)
  {
    tmp[i].int_val = 0;
    tmp[i].dbl_val = 0;
    tmp[i].int_arr = 0;
    tmp[i].dbl_arr = 0;
    tmp[i].update = false;
    tmp[i].keyword = NULL;
  }  // for
  var->opt_grib_kvpair_size = new_size;
  Free(var->opt_grib_kvpair);
  var->opt_grib_kvpair = tmp;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */


static cdi_keys_t *
vlist_get_keysp(vlist_t *vlistptr, int varID)
{
  if (varID == CDI_GLOBAL) return &vlistptr->keys;
  if (varID >= 0 && varID < vlistptr->nvars) return &(vlistptr->vars[varID].keys);

  return NULL;
}

static cdi_keys_t *
grid_get_keysp(grid_t *gridptr, int varID)
{
  if (varID == CDI_GLOBAL) return &gridptr->keys;
  if (varID == CDI_XAXIS) return &gridptr->x.keys;
  if (varID == CDI_YAXIS) return &gridptr->y.keys;

  return NULL;
}

static cdi_keys_t *
zaxis_get_keysp(zaxis_t *zaxisptr, int varID)
{
  return (varID == CDI_GLOBAL) ? &zaxisptr->keys : NULL;
}

static cdi_keys_t *
taxis_get_keysp(taxis_t *taxisptr, int varID)
{
  return (varID == CDI_GLOBAL) ? &taxisptr->keys : NULL;
}

static cdi_key_t *
new_key(cdi_keys_t *keysp, int key)
{
  xassert(keysp != NULL);

  if (keysp->nelems == keysp->nalloc) return NULL;

  cdi_key_t *keyp = &(keysp->value[keysp->nelems]);
  keysp->nelems++;

  keyp->key = key;
  keyp->type = 0;
  keyp->length = 0;
  keyp->v.s = NULL;

  return keyp;
}

cdi_key_t *
find_key(cdi_keys_t *keysp, int key)
{
  xassert(keysp != NULL);

  if (keysp->nelems == 0) return NULL;

  for (uint16_t keyid = 0; keyid < keysp->nelems; keyid++)
  {
    cdi_key_t *keyp = &(keysp->value[keyid]);
    if (keyp->key == key) return keyp;  // Normal return
  }

  return NULL;
}

static const cdi_key_t *
find_key_const(const cdi_keys_t *keysp, int key)
{
  xassert(keysp != NULL);

  if (keysp->nelems == 0) return NULL;

  for (uint16_t keyid = 0; keyid < keysp->nelems; keyid++)
  {
    const cdi_key_t *keyp = &(keysp->value[keyid]);
    if (keyp->key == key) return keyp;  // Normal return
  }

  return NULL;
}

static cdi_keys_t *
cdi_get_keysp(int objID, int varID)
{
  int reshID = reshGetTxCode(objID);
  if (reshID == GRID) return grid_get_keysp(grid_to_pointer(objID), varID);
  if (reshID == DIST_GRID) return grid_get_keysp(grid_to_pointer(objID), varID);
  if (reshID == ZAXIS) return zaxis_get_keysp(zaxis_to_pointer(objID), varID);
  if (reshID == TAXIS) return taxis_get_keysp(taxis_to_pointer(objID), varID);
  if (reshID == VLIST) return vlist_get_keysp(vlist_to_pointer(objID), varID);

  return NULL;
}

int
cdi_key_compare(cdi_keys_t *keyspa, cdi_keys_t *keyspb, int keynum)
{
  xassert(keynum >= 0 && keynum < (int) keyspa->nelems && keynum < (int) keyspb->nelems);
  cdi_key_t *keypa = keyspa->value + keynum, *keypb = keyspb->value + keynum;

  if (keypa->key != keypb->key) return 1;
  if (keypa->type != keypb->type) return 1;
  if (keypa->length != keypb->length) return 1;

  if (keypa->type == KeyBytes) return (memcmp(keypa->v.s, keypb->v.s, (size_t) keypa->length) != 0);
  if (keypa->type == KeyFloat) return (IS_NOT_EQUAL(keypa->v.d, keypb->v.d));
  if (keypa->type == KeyInt) return (keypa->v.i != keypb->v.i);

  return 0;
}

static void
cdi_delete_key(cdi_key_t *keyp)
{
  if (keyp != NULL && keyp->length)  // key in use
  {
    keyp->length = 0;
    if (keyp->type == KeyBytes)
    {
      if (keyp->v.s) Free(keyp->v.s);
      keyp->v.s = NULL;
    }
    else if (keyp->type == KeyFloat) { keyp->v.d = 0.0; }
    else if (keyp->type == KeyInt) { keyp->v.i = 0; }
  }
}

void
cdiDeleteVarKeys(cdi_keys_t *keysp)
{
  uint16_t nelems = keysp ? keysp->nelems : 0;
  for (uint16_t keyid = 0; keyid < nelems; keyid++) { cdi_delete_key(&(keysp->value[keyid])); }

  keysp->nelems = 0;
}

void
cdiDeleteKeys(int cdiID, int varID)
{
  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdiDeleteVarKeys(keysp);
}

void
cdiPrintVarKeys(cdi_keys_t *keysp)
{
  uint16_t nelems = keysp ? (int) keysp->nelems : 0;
  for (uint16_t keyid = 0; keyid < nelems; keyid++)
  {
    cdi_key_t *keyp = &(keysp->value[keyid]);
    if (keyp->length == 0) continue;
    if (keyp->type == KeyBytes)
    {
      fprintf(stdout, "%d key %d length %d value %s\n", keyid + 1, keyp->key, keyp->length, keyp->v.s);
    }
    else if (keyp->type == KeyFloat) { fprintf(stdout, "%d key %d value %g\n", keyid + 1, keyp->key, keyp->v.d); }
    else if (keyp->type == KeyInt) { fprintf(stdout, "%d key %d value %d\n", keyid + 1, keyp->key, keyp->v.i); }
  }
}

void
cdiPrintKeys(int cdiID, int varID)
{
  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdiPrintVarKeys(keysp);
}

//  cdiInqKeyLen: Get the length of the string representation of the key
int
cdiInqKeyLen(int cdiID, int varID, int key, int *length)
{
  int status = -1;

  const cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  const cdi_key_t *keyp = find_key_const(keysp, key);
  if (keyp != NULL && keyp->length > 0)
  {
    *length = keyp->length;
    status = CDI_NOERR;
  }

  return status;
}

static void
cdi_define_key(const cdi_key_t *keyp, cdi_keys_t *keysp)
{
  // clang-format off
  if      (keyp->type == KeyInt)   cdiDefVarKeyInt(keysp, keyp->key, keyp->v.i);
  else if (keyp->type == KeyFloat) cdiDefVarKeyFloat(keysp, keyp->key, keyp->v.d);
  else if (keyp->type == KeyBytes) cdiDefVarKeyBytes(keysp, keyp->key, keyp->v.s, keyp->length);
  // clang-format on
}

int
cdiDeleteKey(int cdiID, int varID, int key)
{
  int status = CDI_NOERR;

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdi_delete_key(find_key(keysp, key));

  return status;
}

void
cdiCopyVarKeys(const cdi_keys_t *keysp1, cdi_keys_t *keysp2)
{
  for (uint16_t keyid = 0; keyid < keysp1->nelems; keyid++)
  {
    const cdi_key_t *keyp = &(keysp1->value[keyid]);
    if (keyp->length > 0) cdi_define_key(keyp, keysp2);
  }
}

int
cdiCopyKeys(int cdiID1, int varID1, int cdiID2, int varID2)
{
  int status = CDI_NOERR;

  cdi_keys_t *keysp1 = cdi_get_keysp(cdiID1, varID1);
  xassert(keysp1 != NULL);

  cdi_keys_t *keysp2 = cdi_get_keysp(cdiID2, varID2);
  xassert(keysp2 != NULL);

  cdiCopyVarKeys(keysp1, keysp2);

  return status;
}

int
cdiCopyVarKey(const cdi_keys_t *keysp1, int key, cdi_keys_t *keysp2)
{
  int status = CDI_NOERR;

  const cdi_key_t *keyp = find_key_const(keysp1, key);
  if (keyp == NULL) return -1;

  if (keyp->length > 0) cdi_define_key(keyp, keysp2);

  return status;
}

int
cdiCopyKey(int cdiID1, int varID1, int key, int cdiID2)
{
  cdi_keys_t *keysp1 = cdi_get_keysp(cdiID1, varID1);
  xassert(keysp1 != NULL);

  cdi_keys_t *keysp2 = cdi_get_keysp(cdiID2, varID1);
  xassert(keysp2 != NULL);

  return cdiCopyVarKey(keysp1, key, keysp2);
}

void
cdiDefVarKeyInt(cdi_keys_t *keysp, int key, int value)
{
  cdi_key_t *keyp = find_key(keysp, key);
  if (keyp == NULL) keyp = new_key(keysp, key);

  if (keyp != NULL)
  {
    // if ( keyp->v.i != value )
    {
      keyp->type = KeyInt;
      keyp->v.i = value;
      keyp->length = 1;
    }
  }
}

/*
@Function  cdiDefKeyInt
@Title     Define an integer value from a key

@Prototype int cdiDefKeyInt(int cdiID, int varID, int key, int value)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched.
    @Item  value    An integer where the data will be read.

@Description
The function @func{cdiDefKeyInt} defines an integer value from a key.

@Result
@func{cdiDefKeyInt} returns CDI_NOERR if OK.

@EndFunction
*/
int
cdiDefKeyInt(int cdiID, int varID, int key, int value)
{
  int status = CDI_NOERR;

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdiDefVarKeyInt(keysp, key, value);

  return status;
}

/*
@Function  cdiInqKeyInt
@Title     Get an integer value from a key

@Prototype int cdiInqKeyInt(int cdiID, int varID, int key, int *value)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched..
    @Item  value    The address of an integer where the data will be retrieved.

@Description
The function @func{cdiInqKeyInt} gets an integer value from a key.

@Result
@func{cdiInqKeyInt} returns CDI_NOERR if key is available.

@EndFunction
*/
int
cdiInqKeyInt(int cdiID, int varID, int key, int *value)
{
  int status = -1;

  // if (varID != CDI_GLOBAL) status = cdiInqKeyInt(cdiID, CDI_GLOBAL, key, value);

  const cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  const cdi_key_t *keyp = find_key_const(keysp, key);
  if (keyp != NULL && keyp->length == 1)  // key in use
  {
    if (keyp->type == KeyInt)
    {
      *value = keyp->v.i;
      status = CDI_NOERR;
    }
  }

  return status;
}

int
cdiInqVarKeyInt(const cdi_keys_t *keysp, int key)
{
  int value = 0;

  const cdi_key_t *keyp = find_key_const(keysp, key);
  if (keyp && keyp->type == KeyInt) value = keyp->v.i;

  return value;
}

void
cdiDefVarKeyFloat(cdi_keys_t *keysp, int key, double value)
{
  cdi_key_t *keyp = find_key(keysp, key);
  if (keyp == NULL) keyp = new_key(keysp, key);

  if (keyp != NULL)
  {
    keyp->type = KeyFloat;
    keyp->v.d = value;
    keyp->length = 1;
  }
}

/*
@Function  cdiDefKeyFloat
@Title     Define a floating point value from a key

@Prototype int cdiDefKeyFloat(int cdiID, int varID, int key, double value)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched
    @Item  value    A double where the data will be read

@Description
The function @func{cdiDefKeyFloat} defines a CDI floating point value from a key.

@Result
@func{cdiDefKeyFloat} returns CDI_NOERR if OK.

@EndFunction
*/
int
cdiDefKeyFloat(int cdiID, int varID, int key, double value)
{
  int status = CDI_NOERR;

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdiDefVarKeyFloat(keysp, key, value);

  return status;
}

/*
@Function  cdiInqKeyFloat
@Title     Get a floating point value from a key

@Prototype int cdiInqKeyFloat(int cdiID, int varID, int key, double *value)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched.
    @Item  value    The address of a double where the data will be retrieved.

@Description
The function @func{cdiInqKeyFloat} gets a floating point value from a key.

@Result
@func{cdiInqKeyFloat} returns CDI_NOERR if key is available.

@EndFunction
*/
int
cdiInqKeyFloat(int cdiID, int varID, int key, double *value)
{
  int status = -1;

  // if (varID != CDI_GLOBAL) status = cdiInqKeyFloat(cdiID, CDI_GLOBAL, key, value);

  const cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  const cdi_key_t *keyp = find_key_const(keysp, key);
  if (keyp != NULL && keyp->length == 1)  // key in use
  {
    if (keyp->type == KeyFloat)
    {
      *value = keyp->v.d;
      status = CDI_NOERR;
    }
  }

  return status;
}

void
cdiDefVarKeyBytes(cdi_keys_t *keysp, int key, const unsigned char *bytes, int length)
{
  cdi_key_t *keyp = find_key(keysp, key);
  if (keyp == NULL) keyp = new_key(keysp, key);

  size_t length_ = length >= 0 ? (size_t) length : (size_t) 0;
  if (keyp != NULL)
  {
    if (keyp->length != 0 && keyp->length != length)
    {
      if (keyp->v.s) Free(keyp->v.s);
      keyp->length = 0;
    }
    if (keyp->length == 0)
    {
      keyp->v.s = (unsigned char *) Malloc(length_);
      keyp->length = length;
    }

    memcpy(keyp->v.s, bytes, length_);
    keyp->type = KeyBytes;
  }
}

/*
@Function  cdiDefKeyBytes
@Title     Define a byte array from a key

@Prototype int cdiDefKeyBytes(int cdiID, int varID, int key, const unsigned char *bytes, int length)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched.
    @Item  bytes    The address of a byte array where the data will be read.
    @Item  length   Length of the byte array

@Description
The function @func{cdiDefKeyBytes} defines a byte array from a key.

@Result
@func{cdiDefKeyBytes} returns CDI_NOERR if OK.

@EndFunction
*/
int
cdiDefKeyBytes(int cdiID, int varID, int key, const unsigned char *bytes, int length)
{
  int status = CDI_NOERR;

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdiDefVarKeyBytes(keysp, key, bytes, length);

  return status;
}

int
cdiInqVarKeyBytes(const cdi_keys_t *keysp, int key, unsigned char *bytes, int *length)
{
  int status = -1;

  const cdi_key_t *keyp = find_key_const(keysp, key);
  int val_len;
  if (keyp != NULL && (val_len = keyp->length) > 0)  // key in use
  {
    if (keyp->type == KeyBytes)
    {
      if (val_len < *length)
        *length = val_len;
      else
        val_len = *length;
      memcpy(bytes, keyp->v.s, (size_t) val_len);
      status = CDI_NOERR;
    }
  }

  return status;
}

//  cdiInqKeyBytes: Get a byte array from a key
/*
@Function  cdiInqKeyBytes
@Title     Get a byte array from a key

@Prototype int cdiInqKeyBytes(int cdiID, int varID, int key, unsigned char *bytes, int *length)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched.
    @Item  bytes    The address of a byte array where the data will be retrieved.
                    The caller must allocate space for the returned byte array.
    @Item  length   The allocated length of the byte array on input.
@Description
The function @func{cdiInqKeyBytes} gets a byte array from a key.

@Result
@func{cdiInqKeyBytes} returns CDI_NOERR if key is available.

@EndFunction
*/
int
cdiInqKeyBytes(int cdiID, int varID, int key, unsigned char *bytes, int *length)
{
  xassert(bytes != NULL);
  xassert(length != NULL);

  // if (varID != CDI_GLOBAL) status = cdiInqKeyBytes(cdiID, CDI_GLOBAL, key, bytes, length);

  const cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  return cdiInqVarKeyBytes(keysp, key, bytes, length);
}

/*
@Function  cdiDefKeyString
@Title     Define a string from a key

@Prototype int cdiDefKeyString(int cdiID, int varID, int key, const char *string)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched.
    @Item  string   The address of a string where the data will be read.

@Description
The function @func{cdiDefKeyString} defines a text string from a key.

@Result
@func{cdiDefKeyString} returns CDI_NOERR if OK.

@Example
Here is an example using @func{cdiDefKeyString} to define the name of a variable:

@Source
   ...
int vlistID, varID, status;
   ...
vlistID = vlistCreate();
varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARYING);
   ...
status = cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, "temperature");
   ...
@EndSource
@EndFunction
*/
int
cdiDefKeyString(int cdiID, int varID, int key, const char *string)
{
  xassert(string != NULL);

  size_t length = strlen(string) + 1;
  int status = cdiDefKeyBytes(cdiID, varID, key, (const unsigned char *) string, (int) length);

  return status;
}

/*
@Function  cdiInqKeyString
@Title     Get a string from a key

@Prototype int cdiInqKeyString(int cdiID, int varID, int key, char *string, int *length)
@Parameter
    @Item  cdiID    CDI object ID (vlistID, gridID, zaxisID).
    @Item  varID    Variable identifier or CDI_GLOBAL.
    @Item  key      The key to be searched.
    @Item  string   The address of a string where the data will be retrieved.
                    The caller must allocate space for the returned string.
    @Item  length   The allocated length of the string on input.
@Description
The function @func{cdiInqKeyString} gets a text string from a key.

@Result
@func{cdiInqKeyString} returns CDI_NOERR if key is available.

@Example
Here is an example using @func{cdiInqKeyString} to get the name of the first variable:

@Source
   ...
#define STRLEN 256
   ...
int streamID, vlistID, varID, status;
int length = STRLEN;
char name[STRLEN];
   ...
streamID = streamOpenRead(...);
vlistID = streamInqVlist(streamID);
   ...
varID = 0;
status = cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length);
   ...
@EndSource
@EndFunction
*/
int
cdiInqKeyString(int cdiID, int varID, int key, char *string, int *length)
{
  xassert(string != NULL);
  xassert(length != NULL);

  int maxlength = *length;
  if (maxlength > 0) string[0] = '\0';

  int status = cdiInqKeyBytes(cdiID, varID, key, (unsigned char *) string, length);
  if (CDI_NOERR == status)
    string[maxlength - 1] = '\0';
  else
    *length = 0;

  return status;
}

const char *
cdiInqVarKeyStringPtr(const cdi_keys_t *keysp, int key)
{
  const cdi_key_t *keyp = find_key_const(keysp, key);
  if (keyp != NULL)  // key in use
  {
    if (keyp->type == KeyBytes) return (const char *) keyp->v.s;
  }

  return NULL;
}

void
cdiInitKeys(cdi_keys_t *keysp)
{
  keysp->nalloc = MAX_KEYS;
  keysp->nelems = 0;
  for (uint16_t i = 0; i < MAX_KEYS; ++i) keysp->value[i].length = 0;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <limits.h>
#include <stdio.h>
#include <string.h>



static cdi_atts_t *
get_attsp(vlist_t *vlistptr, int varID)
{
  if (varID == CDI_GLOBAL)
    return &vlistptr->atts;
  else if (varID >= 0 && varID < vlistptr->nvars)
    return &(vlistptr->vars[varID].atts);

  return NULL;
}

static cdi_att_t *
find_att(cdi_atts_t *attsp, const char *name)
{
  xassert(attsp != NULL);

  if (attsp->nelems == 0) return NULL;

  size_t slen = strlen(name);
  if (slen > CDI_MAX_NAME) slen = CDI_MAX_NAME;

  cdi_att_t *atts = attsp->value;
  for (size_t attid = 0; attid < attsp->nelems; attid++)
  {
    cdi_att_t *attp = atts + attid;
    if (attp->namesz == slen && memcmp(attp->name, name, slen) == 0) return attp;  // Normal return
  }

  return NULL;
}

static cdi_att_t *
new_att(cdi_atts_t *attsp, const char *name)
{
  xassert(attsp != NULL);
  xassert(name != NULL);

  if (attsp->nelems == attsp->nalloc) return NULL;

  cdi_att_t *attp = &(attsp->value[attsp->nelems]);
  attsp->nelems++;

  size_t slen = strlen(name);
  if (slen > CDI_MAX_NAME) slen = CDI_MAX_NAME;

  attp->name = (char *) Malloc(slen + 1);
  memcpy(attp->name, name, slen + 1);
  attp->namesz = slen;
  attp->xvalue = NULL;

  return attp;
}

static void
fill_att(cdi_att_t *attp, int indtype, int exdtype, size_t nelems, size_t xsz, const void *xvalue)
{
  xassert(attp != NULL);

  attp->xsz = xsz;
  attp->indtype = indtype;
  attp->exdtype = exdtype;
  attp->nelems = nelems;

  if (xsz > 0)
  {
    attp->xvalue = Realloc(attp->xvalue, xsz);
    memcpy(attp->xvalue, xvalue, xsz);
  }
}

static cdi_atts_t *
cdi_get_attsp(int objID, int varID)
{
  cdi_atts_t *attsp = NULL;

  if (varID == CDI_GLOBAL && reshGetTxCode(objID) == GRID)
  {
    grid_t *gridptr = grid_to_pointer(objID);
    attsp = &gridptr->atts;
  }
  else if (varID == CDI_GLOBAL && reshGetTxCode(objID) == ZAXIS)
  {
    zaxis_t *zaxisptr = zaxis_to_pointer(objID);
    attsp = &zaxisptr->atts;
  }
  else
  {
    vlist_t *vlistptr = vlist_to_pointer(objID);
    attsp = get_attsp(vlistptr, varID);
  }

  return attsp;
}

/*
@Function  cdiInqNatts
@Title     Get number of attributes

@Prototype int cdiInqNatts(int cdiID, int varID, int *nattsp)
@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  nattsp   Pointer to location for returned number of attributes.

@Description
The function @func{cdiInqNatts} gets the number of attributes assigned to this variable.

@EndFunction
*/
int
cdiInqNatts(int cdiID, int varID, int *nattsp)
{
  int status = CDI_NOERR;

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  *nattsp = (int) attsp->nelems;

  return status;
}

/*
@Function  cdiInqAtt
@Title     Get information about an attribute

@Prototype int cdiInqAtt(int cdiID, int varID, int attnum, char *name, int *typep, int *lenp)
@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  attnum   Attribute number (from 0 to natts-1).
    @Item  name     Pointer to the location for the returned attribute name. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.
    @Item  typep    Pointer to location for returned attribute type.
    @Item  lenp     Pointer to location for returned attribute number.

@Description
The function @func{cdiInqAtt} gets information about an attribute.

@EndFunction
*/
int
cdiInqAtt(int cdiID, int varID, int attnum, char *name, int *typep, int *lenp)
{
  int status = CDI_NOERR;

  xassert(name != NULL);

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  cdi_att_t *attp = NULL;
  if (attnum >= 0 && attnum < (int) attsp->nelems) attp = &(attsp->value[attnum]);

  if (attp != NULL && attp->name)  // name in use
  {
    memcpy(name, attp->name, attp->namesz + 1);
    *typep = attp->exdtype;
    *lenp = (int) attp->nelems;
  }
  else
  {
    name[0] = 0;
    *typep = -1;
    *lenp = 0;
    status = -1;
  }

  return status;
}

int
cdiInqAttLen(int cdiID, int varID, const char *name)
{
  int length = -1;

  xassert(name != NULL);

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  for (int attid = 0; attid < (int) attsp->nelems; attid++)
  {
    cdi_att_t *attp = &(attsp->value[attid]);
    if (attp->name && str_is_equal(attp->name, name)) length = (int) attp->nelems;
  }

  return length;
}

int
cdiInqAttType(int cdiID, int varID, const char *name)
{
  int type = -1;

  xassert(name != NULL);

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  for (int attid = 0; attid < (int) attsp->nelems; attid++)
  {
    cdi_att_t *attp = &(attsp->value[attid]);
    if (attp->name && str_is_equal(attp->name, name)) type = attp->exdtype;
  }

  return type;
}

static void
cdi_attribute_free(cdi_att_t *attp)
{
  if (attp->name)
  {
    Free(attp->name);
    attp->name = NULL;
    attp->namesz = 0;
  }
  if (attp->xvalue)
  {
    Free(attp->xvalue);
    attp->xvalue = NULL;
  }
}

int
cdiDeleteAtts(int cdiID, int varID)
{
  int status = CDI_NOERR;

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  for (int attid = 0; attid < (int) attsp->nelems; attid++)
  {
    cdi_att_t *attp = &(attsp->value[attid]);
    cdi_attribute_free(attp);
  }

  attsp->nelems = 0;

  return status;
}

int
cdiDelAtt(int cdiID, int varID, const char *name)
{
  int status = -1;

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  for (int attid = 0; attid < (int) attsp->nelems; attid++)
  {
    cdi_att_t *attp = &(attsp->value[attid]);
    if (attp->name && str_is_equal(attp->name, name))
    {
      cdi_attribute_free(attp);
      status = CDI_NOERR;
      break;
    }
  }

  return status;
}

static int
cdi_def_att(int indtype, int exdtype, int cdiID, int varID, const char *name, size_t len, size_t xsz, const void *xp)
{
  int status = CDI_NOERR;

  if (len != 0 && xp == NULL) return CDI_EINVAL;  // Null arg

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  cdi_att_t *attp = find_att(attsp, name);
  if (attp == NULL) attp = new_att(attsp, name);

  if (attp != NULL) fill_att(attp, indtype, exdtype, len, xsz, xp);

  return status;
}

static int
cdi_inq_att(int indtype, int cdiID, int varID, const char *name, size_t mxsz, void *xp)
{
  int status = CDI_NOERR;

  if (mxsz != 0 && xp == NULL) return CDI_EINVAL;  // Null arg

  cdi_atts_t *attsp = cdi_get_attsp(cdiID, varID);
  xassert(attsp != NULL);

  cdi_att_t *attp = find_att(attsp, name);
  if (attp != NULL)  // name in use
  {
    if (attp->indtype == indtype)
    {
      size_t xsz = attp->xsz;
      if (mxsz < xsz) xsz = mxsz;
      if (xsz > 0) memcpy(xp, attp->xvalue, xsz);
    }
    else
    {
      Warning("Attribute %s has wrong data type!", name);
      status = -2;
    }
  }
  else
  {
    // Warning("Internal problem, attribute %s not found!", name);
    status = -1;
  }

  return status;
}

int
cdiCopyAtts(int cdiID1, int varID1, int cdiID2, int varID2)
{
  int status = CDI_NOERR;

  cdi_atts_t *attsp1 = cdi_get_attsp(cdiID1, varID1);
  xassert(attsp1 != NULL);

  for (size_t attid = 0; attid < attsp1->nelems; attid++)
  {
    cdi_att_t *attp = &(attsp1->value[attid]);
    cdi_def_att(attp->indtype, attp->exdtype, cdiID2, varID2, attp->name, attp->nelems, attp->xsz, attp->xvalue);
  }

  return status;
}

/*
@Function  cdiDefAttInt
@Title     Define an integer attribute

@Prototype int cdiDefAttInt(int cdiID, int varID, const char *name, int type, int len, const int *ip)

@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{zaxisCreate}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  name     Attribute name.
    @Item  type     External data type (@func{CDI_DATATYPE_INT16} or @func{CDI_DATATYPE_INT32}).
    @Item  len      Number of values provided for the attribute.
    @Item  ip       Pointer to one or more integer values.

@Description
The function @func{cdiDefAttInt} defines an integer attribute.

@EndFunction
*/
int
cdiDefAttInt(int cdiID, int varID, const char *name, int type, int len, const int *ip)
{
  return cdi_def_att(CDI_DATATYPE_INT, type, cdiID, varID, name, (size_t) len, (size_t) len * sizeof(int), ip);
}

/*
@Function  cdiDefAttFlt
@Title     Define a floating point attribute

@Prototype int cdiDefAttFlt(int cdiID, int varID, const char *name, int type, int len, const double *dp)

@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{zaxisCreate}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  name     Attribute name.
    @Item  type     External data type (@func{CDI_DATATYPE_FLT32} or @func{CDI_DATATYPE_FLT64}).
    @Item  len      Number of values provided for the attribute.
    @Item  dp       Pointer to one or more floating point values.

@Description
The function @func{cdiDefAttFlt} defines a floating point attribute.

@EndFunction
*/
int
cdiDefAttFlt(int cdiID, int varID, const char *name, int type, int len, const double *dp)
{
  return cdi_def_att(CDI_DATATYPE_FLT, type, cdiID, varID, name, (size_t) len, (size_t) len * sizeof(double), dp);
}

/*
@Function  cdiDefAttTxt
@Title     Define a text attribute

@Prototype int cdiDefAttTxt(int cdiID, int varID, const char *name, int len, const char *tp)

@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{zaxisCreate}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  name     Attribute name.
    @Item  len      Number of values provided for the attribute.
    @Item  tp       Pointer to one or more character values.

@Description
The function @func{cdiDefAttTxt} defines a text attribute.

@Example
Here is an example using @func{cdiDefAttTxt} to define the attribute "description":

@Source
   ...
int vlistID, varID, status;
char text[] = "description of the variable";
   ...
vlistID = vlistCreate();
varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARYING);
   ...
status = cdiDefAttTxt(vlistID, varID, "description", LEN(text), text);
   ...
@EndSource
@EndFunction
*/
int
cdiDefAttTxt(int cdiID, int varID, const char *name, int len, const char *tp)
{
  return cdi_def_att(CDI_DATATYPE_TXT, CDI_DATATYPE_TXT, cdiID, varID, name, (size_t) len, (size_t) len, tp);
}

/*
@Function  cdiInqAttInt
@Title     Get the value(s) of an integer attribute

@Prototype int cdiInqAttInt(int cdiID, int varID, const char *name, int mlen, int *ip)
@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{zaxisCreate}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  name     Attribute name.
    @Item  mlen     Number of allocated values provided for the attribute.
    @Item  ip       Pointer location for returned integer attribute value(s).

@Description
The function @func{cdiInqAttInt} gets the values(s) of an integer attribute.

@EndFunction
*/
int
cdiInqAttInt(int cdiID, int varID, const char *name, int mlen, int *ip)
{
  return cdi_inq_att(CDI_DATATYPE_INT, cdiID, varID, name, (size_t) mlen * sizeof(int), ip);
}

/*
@Function  cdiInqAttFlt
@Title     Get the value(s) of a floating point attribute

@Prototype int cdiInqAttFlt(int cdiID, int varID, const char *name, int mlen, double *dp)
@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{zaxisCreate}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  name     Attribute name.
    @Item  mlen     Number of allocated values provided for the attribute.
    @Item  dp       Pointer location for returned floating point attribute value(s).

@Description
The function @func{cdiInqAttFlt} gets the values(s) of a floating point attribute.

@EndFunction
*/
int
cdiInqAttFlt(int cdiID, int varID, const char *name, int mlen, double *dp)
{
  return cdi_inq_att(CDI_DATATYPE_FLT, cdiID, varID, name, (size_t) mlen * sizeof(double), dp);
}

/*
@Function  cdiInqAttTxt
@Title     Get the value(s) of a text attribute

@Prototype int cdiInqAttTxt(int cdiID, int varID, const char *name, int mlen, char *tp)
@Parameter
    @Item  cdiID    CDI ID, from a previous call to @fref{vlistCreate}, @fref{gridCreate} or @fref{zaxisCreate}.
    @Item  varID    Variable identifier, or @func{CDI_GLOBAL} for a global attribute.
    @Item  name     Attribute name.
    @Item  mlen     Number of allocated values provided for the attribute.
    @Item  tp       Pointer location for returned text attribute value(s).

@Description
The function @func{cdiInqAttTxt} gets the values(s) of a text attribute.

@EndFunction
*/
int
cdiInqAttTxt(int cdiID, int varID, const char *name, int mlen, char *tp)
{
  return cdi_inq_att(CDI_DATATYPE_TXT, cdiID, varID, name, (size_t) mlen * sizeof(char), tp);
}

enum
{
  cdi_att_nints = 4, /* namesz, exdtype, indtype, nelems */
};

static inline int
cdiAttTypeLookup(cdi_att_t *attp)
{
  int type;
  switch (attp->indtype)
  {
    case CDI_DATATYPE_FLT: type = CDI_DATATYPE_FLT64; break;
    case CDI_DATATYPE_INT:
    case CDI_DATATYPE_TXT: type = attp->indtype; break;
    default: xabort("Unknown datatype encountered in attribute %s: %d\n", attp->name, attp->indtype);
  }
  return type;
}

int
cdi_att_compare(cdi_atts_t *attspa, cdi_atts_t *attspb, int attnum)
{
  xassert(attnum >= 0 && attnum < (int) attspa->nelems && attnum < (int) attspb->nelems);
  cdi_att_t *attpa = attspa->value + attnum, *attpb = attspb->value + attnum;

  if (attpa->namesz != attpb->namesz) return 1;

  if (attpa->name && attpb->name && memcmp(attpa->name, attpb->name, attpa->namesz)) return 1;

  if (attpa->indtype != attpb->indtype || attpa->exdtype != attpb->exdtype || attpa->nelems != attpb->nelems) return 1;

  return memcmp(attpa->xvalue, attpb->xvalue, attpa->xsz);
}

static int
cdiAttGetSize(cdi_atts_t *attsp, int attnum, void *context)
{
  xassert(attnum >= 0 && attnum < (int) attsp->nelems);
  cdi_att_t *attp = &(attsp->value[attnum]);
  int txsize = serializeGetSize(cdi_att_nints, CDI_DATATYPE_INT, context)
               + serializeGetSize((int) attp->namesz, CDI_DATATYPE_TXT, context);
  txsize += serializeGetSize((int) attp->nelems, cdiAttTypeLookup(attp), context);
  return txsize;
}

int
cdiAttsGetSize(void *vp, int varID, void *context)
{
  cdi_atts_t *attsp;
  xassert(attsp = get_attsp((vlist_t *) vp, varID));
  int txsize = serializeGetSize(1, CDI_DATATYPE_INT, context);
  size_t numAtts = attsp->nelems;
  for (size_t i = 0; i < numAtts; ++i) txsize += cdiAttGetSize(attsp, (int) i, context);
  return txsize;
}

static void
cdiAttPack(cdi_atts_t *attsp, int attnum, void *buf, int size, int *position, void *context)
{
  int tempbuf[cdi_att_nints];

  xassert(attnum >= 0 && attnum < (int) attsp->nelems);
  cdi_att_t *attp = &(attsp->value[attnum]);
  tempbuf[0] = (int) attp->namesz;
  tempbuf[1] = attp->exdtype;
  tempbuf[2] = attp->indtype;
  tempbuf[3] = (int) attp->nelems;
  serializePack(tempbuf, cdi_att_nints, CDI_DATATYPE_INT, buf, size, position, context);
  serializePack(attp->name, (int) attp->namesz, CDI_DATATYPE_TXT, buf, size, position, context);
  serializePack(attp->xvalue, (int) attp->nelems, cdiAttTypeLookup(attp), buf, size, position, context);
}

void
cdiAttsPack(void *vp, int varID, void *buf, int size, int *position, void *context)
{
  cdi_atts_t *attsp;
  xassert(attsp = get_attsp((vlist_t *) vp, varID));
  size_t numAtts = attsp->nelems;
  int numAttsI = (int) numAtts;
  xassert(numAtts <= INT_MAX);
  serializePack(&numAttsI, 1, CDI_DATATYPE_INT, buf, size, position, context);
  for (size_t i = 0; i < numAtts; ++i) cdiAttPack(attsp, (int) i, buf, size, position, context);
}

static void
cdiAttUnpack(int cdiID, int varID, void *buf, int size, int *position, void *context)
{
  int tempbuf[cdi_att_nints];

  serializeUnpack(buf, size, position, tempbuf, cdi_att_nints, CDI_DATATYPE_INT, context);
  char *attName = (char *) Malloc((size_t) tempbuf[0] + 1);
  serializeUnpack(buf, size, position, attName, tempbuf[0], CDI_DATATYPE_TXT, context);
  attName[tempbuf[0]] = '\0';
  int attVDt;
  size_t elemSize;
  switch (tempbuf[2])
  {
    case CDI_DATATYPE_FLT:
      attVDt = CDI_DATATYPE_FLT64;
      elemSize = sizeof(double);
      break;
    case CDI_DATATYPE_INT:
      attVDt = CDI_DATATYPE_INT;
      elemSize = sizeof(int);
      break;
    case CDI_DATATYPE_TXT:
      attVDt = CDI_DATATYPE_TXT;
      elemSize = 1;
      break;
    default: xabort("Unknown datatype encountered in attribute %s: %d\n", attName, tempbuf[2]);
  }
  void *attData = Malloc(elemSize * (size_t) tempbuf[3]);
  serializeUnpack(buf, size, position, attData, tempbuf[3], attVDt, context);
  cdi_def_att(tempbuf[2], tempbuf[1], cdiID, varID, attName, (size_t) tempbuf[3], (size_t) tempbuf[3] * elemSize, attData);
  Free(attName);
  Free(attData);
}

void
cdiAttsUnpack(int cdiID, int varID, void *buf, int size, int *position, void *context)
{
  int numAtts;
  serializeUnpack(buf, size, position, &numAtts, 1, CDI_DATATYPE_INT, context);
  for (int i = 0; i < numAtts; ++i) cdiAttUnpack(cdiID, varID, buf, size, position, context);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <float.h> /* FLT_MAX */

static void
vlistvar_init_entry(var_t *vlistvars, int varID)
{
  var_t *varptr = &vlistvars[varID];
  varptr->flag = false;
  varptr->lvalidrange = false;
  varptr->xyz = 5;  // xyzStorVals[5] == 321
  varptr->missvalused = false;
  varptr->mvarID = varID;
  varptr->fvarID = varID;
  varptr->param = 0;
  varptr->gridID = CDI_UNDEFID;
  varptr->zaxisID = CDI_UNDEFID;
  varptr->timetype = CDI_UNDEFID;
  varptr->tsteptype = TSTEP_INSTANT;
  varptr->datatype = CDI_UNDEFID;
  varptr->instID = CDI_UNDEFID;
  varptr->modelID = CDI_UNDEFID;
  varptr->tableID = CDI_UNDEFID;
  varptr->timave = 0;
  varptr->nsb = 0;
  varptr->missval = CDI_Default_Missval;
  varptr->validrange[0] = VALIDMISS;
  varptr->validrange[1] = VALIDMISS;
  varptr->levinfo = NULL;
  varptr->comptype = CDI_COMPRESS_NONE;
  varptr->complevel = 1;
  varptr->keys.nalloc = MAX_KEYS;
  varptr->keys.nelems = 0;
  for (int i = 0; i < MAX_KEYS; ++i) varptr->keys.value[i].length = 0;
  varptr->atts.nalloc = MAX_ATTRIBUTES;
  varptr->atts.nelems = 0;
  varptr->subtypeID = CDI_UNDEFID;
  varptr->opt_grib_nentries = 0;
  varptr->opt_grib_kvpair_size = 0;
  varptr->opt_grib_kvpair = NULL;
}

static int
vlistvarNewEntry(int vlistID)
{
  int varID = 0;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int vlistvarSize = vlistptr->varsAllocated;
  var_t *vlistvars = vlistptr->vars;
  // Look for a free slot in vlistvar. (Create the table the first time through).
  if (!vlistvarSize)
  {
    vlistvarSize = 2;
    vlistvars = (var_t *) Malloc((size_t) vlistvarSize * sizeof(var_t));
    for (int i = 0; i < vlistvarSize; ++i) vlistvars[i].isUsed = false;
  }
  else
  {
    while (varID < vlistvarSize && vlistvars[varID].isUsed) ++varID;
  }
  // If the table overflows, double its size.
  if (varID == vlistvarSize)
  {
    vlistvars = (var_t *) Realloc(vlistvars, (size_t) (vlistvarSize *= 2) * sizeof(var_t));
    for (int i = varID; i < vlistvarSize; ++i) vlistvars[i].isUsed = false;
  }

  vlistptr->varsAllocated = vlistvarSize;
  vlistptr->vars = vlistvars;

  vlistvar_init_entry(vlistvars, varID);
  vlistvars[varID].isUsed = true;

  return varID;
}

static var_t *
vlistptr_get_varptr(const char *caller, vlist_t *vlistptr, int varID)
{
  if (varID < 0 || varID >= vlistptr->nvars || !vlistptr->vars[varID].isUsed) Errorc("varID %d undefined!", varID);
  return &vlistptr->vars[varID];
}

int
vlistDefVarTiles(int vlistID, int gridID, int zaxisID, int timetype, int tilesetID)
{
  if (CDI_Debug) Message("gridID = %d  zaxisID = %d  timetype = %d", gridID, zaxisID, timetype);

  int varID = vlistvarNewEntry(vlistID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  vlistptr->nvars++;
  var_t *varptr = &vlistptr->vars[varID];
  varptr->gridID = gridID;
  varptr->zaxisID = zaxisID;
  varptr->timetype = timetype;
  varptr->subtypeID = tilesetID;

  if (timetype < 0)
  {
    Warning("Unexpected time type %d, set to TIME_VARYING!", timetype);
    varptr->timetype = TIME_VARYING;
  }

  // Compatibility for release 1.8.3
  if (timetype > 1 && timetype < 15)
  {
    varptr->timetype = TIME_VARYING;
    varptr->tsteptype = timetype;
    static bool printInfo = true;
    if (printInfo)
    {
      printInfo = false;
      fprintf(stdout, "CDI info: The vlistDefVar() function was called with an invalid value for the timetype parameter.\n");
      fprintf(stdout, "CDI info:    This may be an obsolete form of using the vlistDefVar() function.\n");
      fprintf(stdout, "CDI info:    The correct form is:\n");
      fprintf(stdout, "CDI info:       varID = vlistDefVar(vlistID, gridID, zaxisID, timetype)\n");
      fprintf(stdout, "CDI info:       vlistDefVarTsteptype(vlistID, varID, tsteptype)\n");
      fprintf(stdout, "CDI info:          with: timetype = TIME_CONSTANT | TIME_VARYING\n");
      fprintf(stdout, "CDI info:                tsteptype = TSTEP_AVG .... TSTEP_SUM\n");
    }
  }

  vlistAdd2GridIDs(vlistptr, gridID);
  vlistAdd2ZaxisIDs(vlistptr, zaxisID);
  vlistAdd2SubtypeIDs(vlistptr, tilesetID);

  varptr->param = cdiEncodeParam(-(varID + 1), 255, 255);
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);

  return varID;
}

/*
@Function  vlistDefVar
@Title     Define a Variable

@Prototype int vlistDefVar(int vlistID, int gridID, int zaxisID, int timetype)
@Parameter
    @Item  vlistID   Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  gridID    Grid ID, from a previous call to @fref{gridCreate}.
    @Item  zaxisID   Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  timetype  One of the set of predefined CDI timestep types.
                     The valid CDI timestep types are @func{TIME_CONSTANT} and @func{TIME_VARYING}.

@Description
The function @func{vlistDefVar} adds a new variable to vlistID.

@Result
@func{vlistDefVar} returns an identifier to the new variable.

@Example
Here is an example using @func{vlistCreate} to create a variable list
and add a variable with @func{vlistDefVar}.

@Source
   ...
int vlistID, varID;
   ...
vlistID = vlistCreate();
varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARYING);
   ...
streamDefVlist(streamID, vlistID);
   ...
vlistDestroy(vlistID);
   ...
@EndSource
@EndFunction
*/
int
vlistDefVar(int vlistID, int gridID, int zaxisID, int timetype)
{
  // call "vlistDefVarTiles" with a trivial tile index:
  return vlistDefVarTiles(vlistID, gridID, zaxisID, timetype, CDI_UNDEFID);
}

void
cdiVlistCreateVarLevInfo(vlist_t *vlistptr, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlistptr, varID);
  xassert(varID >= 0 && varID < vlistptr->nvars && varptr->levinfo == NULL);

  int zaxisID = varptr->zaxisID;
  size_t nlevs = (size_t) zaxisInqSize(zaxisID);

  varptr->levinfo = (levinfo_t *) Malloc(nlevs * sizeof(levinfo_t));

  for (size_t levID = 0; levID < nlevs; ++levID) varptr->levinfo[levID] = DEFAULT_LEVINFO((int) levID);
}

/*
@Function  vlistDefVarParam
@Title     Define the parameter number of a Variable

@Prototype void vlistDefVarParam(int vlistID, int varID, int param)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  param    Parameter number.

@Description
The function @func{vlistDefVarParam} defines the parameter number of a variable.

@EndFunction
*/
void
vlistDefVarParam(int vlistID, int varID, int param)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->param != param)
  {
    varptr->param = param;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  vlistDefVarCode
@Title     Define the code number of a Variable

@Prototype void vlistDefVarCode(int vlistID, int varID, int code)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  code     Code number.

@Description
The function @func{vlistDefVarCode} defines the code number of a variable.

@EndFunction
*/
void
vlistDefVarCode(int vlistID, int varID, int code)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  int pnum, pcat, pdis;
  cdiDecodeParam(varptr->param, &pnum, &pcat, &pdis);
  int newParam = cdiEncodeParam(code, pcat, pdis);
  if (varptr->param != newParam)
  {
    varptr->param = newParam;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

void
vlistInqVar(int vlistID, int varID, int *gridID, int *zaxisID, int *timetype)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  *gridID = varptr->gridID;
  *zaxisID = varptr->zaxisID;
  *timetype = varptr->timetype;
}

/*
@Function  vlistInqVarGrid
@Title     Get the Grid ID of a Variable

@Prototype int vlistInqVarGrid(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarGrid} returns the grid ID of a Variable.

@Result
@func{vlistInqVarGrid} returns the grid ID of the Variable.

@EndFunction
*/
int
vlistInqVarGrid(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->gridID;
}

/*
@Function  vlistInqVarZaxis
@Title     Get the Zaxis ID of a Variable

@Prototype int vlistInqVarZaxis(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarZaxis} returns the zaxis ID of a variable.

@Result
@func{vlistInqVarZaxis} returns the zaxis ID of the variable.

@EndFunction
*/
int
vlistInqVarZaxis(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->zaxisID;
}

/*
@Function  vlistInqVarSubtype
@Title     Get the Subtype ID of a Variable

@Description
The function @func{vlistInqVarSubtype} returns the subtype ID of a variable.

@Result
@func{vlistInqVarSubtype} returns the subtype ID of the variable.

@EndFunction
*/
int
vlistInqVarSubtype(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->subtypeID;
}

/*
@Function  vlistInqVarParam
@Title     Get the parameter number of a Variable

@Prototype int vlistInqVarParam(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarParam} returns the parameter number of a variable.

@Result
@func{vlistInqVarParam} returns the parameter number of the variable.

@EndFunction
*/
int
vlistInqVarParam(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->param;
}

/*
@Function  vlistInqVarCode
@Title     Get the Code number of a Variable

@Prototype int vlistInqVarCode(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarCode} returns the code number of a variable.

@Result
@func{vlistInqVarCode} returns the code number of the variable.

@EndFunction
*/
int
vlistInqVarCode(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  int pdis, pcat, pnum;
  cdiDecodeParam(varptr->param, &pnum, &pcat, &pdis);
  int code = pnum;
  if (pdis != 255) code = -varID - 1;  // GRIB2 Parameter

  int tableID = varptr->tableID;
  if (code < 0 && tableID != -1)
  {
    char name[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    (void) cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length);

    if (name[0]) tableInqParCode(tableID, name, &code);
  }

  return code;
}

/*
@Function  vlistInqVarName
@Title     Get the name of a Variable

@Prototype void vlistInqVarName(int vlistID, int varID, char *name)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.
    @Item  name     Returned variable name. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{vlistInqVarName} returns the name of a variable.

@Result
@func{vlistInqVarName} returns the name of the variable to the parameter name if available,
otherwise the result is an empty string.

@EndFunction
*/
void
vlistInqVarName(int vlistID, int varID, char *name)
{
  int length = CDI_MAX_NAME;
  if (CDI_NOERR != cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length))
  {
    var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

    int pdis, pcat, pnum;
    cdiDecodeParam(varptr->param, &pnum, &pcat, &pdis);
    if (pdis == 255)
    {
      int code = pnum;
      int tableID = varptr->tableID;
      tableInqEntry(tableID, code, -1, name, NULL, NULL);
      if (!name[0]) snprintf(name, 8, "var%d", code);
    }
    else { snprintf(name, CDI_MAX_NAME, "param%d.%d.%d", pnum, pcat, pdis); }
  }
}

/*
@Function vlistCopyVarName
@Tatle    Get the name of a Variable in a safe way

@Prototype char* vlistCopyVarName(int vlistId, int varId)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Return A pointer to a malloc'ed string. Must be cleaned up with Free().

@Description
This is the buffer overflow immune version of vlistInqVarName().
The memory for the returned string is allocated to fit the string via Malloc().

@EndFunction
*/
char *
vlistCopyVarName(int vlistID, int varID)
{
  // If a name is set in the variable description, use that.
  char name[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length);
  if (name[0]) return strdup(name);

  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  // Otherwise we check if we should use the table of parameter descriptions.
  int discipline, category, number;
  cdiDecodeParam(varptr->param, &number, &category, &discipline);
  char *result = NULL;
  if (discipline == 255)
  {
    tableInqEntry(varptr->tableID, number, -1, name, NULL, NULL);
    if (name[0])
      result = strdup(name);
    else
    {
      // No luck, fall back to outputting a name of the format "var<num>".
      size_t len = 3 + 3 * sizeof(int) * CHAR_BIT / 8 + 2;
      result = (char *) Malloc(len);
      snprintf(result, len, "var%d", number);
    }
  }
  else
  {
    size_t len = 5 + 2 + 3 * (3 * sizeof(int) * CHAR_BIT + 1) + 1;
    result = (char *) Malloc(len);
    snprintf(result, len, "param%d.%d.%d", number, category, discipline);
  }
  // Finally, we fall back to outputting a name of the format "param<num>.<cat>.<dis>".
  return result;
}

/*
@Function  vlistInqVarLongname
@Title     Get the longname of a Variable

@Prototype void vlistInqVarLongname(int vlistID, int varID, char *longname)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.
    @Item  longname Long name of the variable. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{vlistInqVarLongname} returns the longname of a variable if available,
otherwise the result is an empty string.

@Result
@func{vlistInqVarLongname} returns the longname of the variable to the parameter longname.

@EndFunction
*/
void
vlistInqVarLongname(int vlistID, int varID, char *longname)
{
  int length = CDI_MAX_NAME;
  if (CDI_NOERR != cdiInqKeyString(vlistID, varID, CDI_KEY_LONGNAME, longname, &length))
  {
    var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

    int pdis, pcat, pnum;
    cdiDecodeParam(varptr->param, &pnum, &pcat, &pdis);
    if (pdis == 255) tableInqEntry(varptr->tableID, pnum, -1, NULL, longname, NULL);
  }
}

/*
@Function  vlistInqVarStdname
@Title     Get the standard name of a Variable

@Prototype void vlistInqVarStdname(int vlistID, int varID, char *stdname)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.
    @Item  stdname  Standard name of the variable. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{vlistInqVarStdname} returns the standard name of a variable if available,
otherwise the result is an empty string.

@Result
@func{vlistInqVarStdname} returns the standard name of the variable to the parameter stdname.

@EndFunction
*/
void
vlistInqVarStdname(int vlistID, int varID, char *stdname)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(vlistID, varID, CDI_KEY_STDNAME, stdname, &length);
}

// obsolete function
int
vlistInqVarTypeOfGeneratingProcess(int vlistID, int varID)
{
  static bool printInfo = true;
  if (printInfo) printInfo = cdiObsoleteInfo(__func__, "cdiInqKeyInt");
  int typeOfGeneratingProcess = 0;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFGENERATINGPROCESS, &typeOfGeneratingProcess);
  return typeOfGeneratingProcess;
}

// obsolete function
void
vlistDefVarTypeOfGeneratingProcess(int vlistID, int varID, int typeOfGeneratingProcess)
{
  static bool printInfo = true;
  if (printInfo) printInfo = cdiObsoleteInfo(__func__, "cdiDefKeyInt");
  cdiDefKeyInt(vlistID, varID, CDI_KEY_TYPEOFGENERATINGPROCESS, typeOfGeneratingProcess);
}

// obsolete function
void
vlistDefVarProductDefinitionTemplate(int vlistID, int varID, int productDefinitionTemplate)
{
  static bool printInfo = true;
  if (printInfo) printInfo = cdiObsoleteInfo(__func__, "cdiDefKeyInt");
  cdiDefKeyInt(vlistID, varID, CDI_KEY_PRODUCTDEFINITIONTEMPLATE, productDefinitionTemplate);
}

/*
@Function  vlistInqVarUnits
@Title     Get the units of a Variable

@Prototype void vlistInqVarUnits(int vlistID, int varID, char *units)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.
    @Item  units    Units of the variable. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{vlistInqVarUnits} returns the units of a variable if available,
otherwise the result is an empty string.

@Result
@func{vlistInqVarUnits} returns the units of the variable to the parameter units.

@EndFunction
*/
void
vlistInqVarUnits(int vlistID, int varID, char *units)
{
  int length = CDI_MAX_NAME;
  if (CDI_NOERR != cdiInqKeyString(vlistID, varID, CDI_KEY_UNITS, units, &length))
  {
    var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

    int pdis, pcat, pnum;
    cdiDecodeParam(varptr->param, &pnum, &pcat, &pdis);
    if (pdis == 255) tableInqEntry(varptr->tableID, pnum, -1, NULL, NULL, units);
  }
}

// used in MPIOM !
int
vlistInqVarID(int vlistID, int code)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int varID = 0; varID < vlistptr->nvars; ++varID)
  {
    int param = vlistptr->vars[varID].param;
    int pdis, pcat, pnum;
    cdiDecodeParam(param, &pnum, &pcat, &pdis);
    if (pnum == code) return varID;
  }

  return CDI_UNDEFID;
}

SizeType
vlistInqVarSize(int vlistID, int varID)
{
  int zaxisID, gridID, timetype;
  vlistInqVar(vlistID, varID, &gridID, &zaxisID, &timetype);

  SizeType nlevs = (SizeType) zaxisInqSize(zaxisID);
  SizeType gridsize = gridInqSize(gridID);

  return gridsize * nlevs;
}

/*
@Function  vlistInqVarDatatype
@Title     Get the data type of a Variable

@Prototype int vlistInqVarDatatype(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarDatatype} returns the data type of a variable.

@Result
@func{vlistInqVarDatatype} returns an identifier to the data type of the variable.
The valid CDI data types are @func{CDI_DATATYPE_PACK8}, @func{CDI_DATATYPE_PACK16}, @func{CDI_DATATYPE_PACK24},
@func{CDI_DATATYPE_FLT32}, @func{CDI_DATATYPE_FLT64}, @func{CDI_DATATYPE_INT8}, @func{CDI_DATATYPE_INT16} and
@func{CDI_DATATYPE_INT32}.

@EndFunction
*/
int
vlistInqVarDatatype(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->datatype;
}

int
vlistInqVarNumber(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return (varptr->datatype == CDI_DATATYPE_CPX32 || varptr->datatype == CDI_DATATYPE_CPX64) ? CDI_COMP : CDI_REAL;
}

static bool
check_range(double value, double lowerBound, double upperBound)
{
  return (value >= lowerBound && value <= upperBound);
}

/*
@Function  vlistDefVarDatatype
@Title     Define the data type of a Variable

@Prototype void vlistDefVarDatatype(int vlistID, int varID, int datatype)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  datatype The data type identifier.
                    The valid CDI data types are @func{CDI_DATATYPE_PACK8}, @func{CDI_DATATYPE_PACK16},
                    @func{CDI_DATATYPE_PACK24}, @func{CDI_DATATYPE_FLT32}, @func{CDI_DATATYPE_FLT64},
                    @func{CDI_DATATYPE_INT8}, @func{CDI_DATATYPE_INT16} and @func{CDI_DATATYPE_INT32}.

@Description
The function @func{vlistDefVarDatatype} defines the data type of a variable.

@EndFunction
*/
void
vlistDefVarDatatype(int vlistID, int varID, int datatype)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->datatype != datatype)
  {
    varptr->datatype = datatype;

    if (!varptr->missvalused)
    {
      double missval = varptr->missval;
      bool missvalIsDefault = DBL_IS_EQUAL(missval, CDI_Default_Missval);
      if (missvalIsDefault)
      {
        // clang-format off
        switch (datatype)
          {
          case CDI_DATATYPE_INT8:   varptr->missval = -SCHAR_MAX; break;
          case CDI_DATATYPE_UINT8:  varptr->missval =  UCHAR_MAX; break;
          case CDI_DATATYPE_INT16:  varptr->missval = -SHRT_MAX;  break;
          case CDI_DATATYPE_UINT16: varptr->missval =  USHRT_MAX; break;
          case CDI_DATATYPE_INT32:  varptr->missval = -INT_MAX;   break;
          case CDI_DATATYPE_UINT32: varptr->missval =  UINT_MAX;  break;
          case CDI_DATATYPE_FLT32:  varptr->missval =  (float) CDI_Default_Missval;  break;
          }
        // clang-format on
      }
      else
      {
        // clang-format off
        switch (datatype)
          {
          case CDI_DATATYPE_INT8:   varptr->missval = check_range(missval, -SCHAR_MAX, SCHAR_MAX) ? missval : -SCHAR_MAX; break;
          case CDI_DATATYPE_UINT8:  varptr->missval = check_range(missval,          0, UCHAR_MAX) ? missval :  UCHAR_MAX; break;
          case CDI_DATATYPE_INT16:  varptr->missval = check_range(missval,  -SHRT_MAX,  SHRT_MAX) ? missval : -SHRT_MAX;  break;
          case CDI_DATATYPE_UINT16: varptr->missval = check_range(missval,          0, USHRT_MAX) ? missval :  USHRT_MAX; break;
          case CDI_DATATYPE_INT32:  varptr->missval = check_range(missval,   -INT_MAX,   INT_MAX) ? missval : -INT_MAX;   break;
          case CDI_DATATYPE_UINT32: varptr->missval = check_range(missval,          0,  UINT_MAX) ? missval :  UINT_MAX;  break;
          case CDI_DATATYPE_FLT32:  varptr->missval = check_range(missval,   -FLT_MAX,   FLT_MAX) ? missval :  CDI_Default_Missval;  break;
          }
        // clang-format on
      }
    }
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

void
vlistDefVarInstitut(int vlistID, int varID, int instID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  if (varptr->instID != instID)
  {
    varptr->instID = instID;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarInstitut(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->instID;
}

void
vlistDefVarModel(int vlistID, int varID, int modelID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  if (varptr->modelID != modelID)
  {
    varptr->modelID = modelID;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarModel(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->modelID;
}

void
vlistDefVarTable(int vlistID, int varID, int tableID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->tableID != tableID)
  {
    varptr->tableID = tableID;
    int tablenum = tableInqNum(tableID);
    int pnum, pcat, pdis;
    cdiDecodeParam(varptr->param, &pnum, &pcat, &pdis);
    varptr->param = cdiEncodeParam(pnum, tablenum, pdis);
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarTable(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->tableID;
}

/*
@Function  vlistDefVarName
@Title     Define the name of a Variable

@Prototype void vlistDefVarName(int vlistID, int varID, const char *name)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  name     Name of the variable.

@Description
The function @func{vlistDefVarName} defines the name of a variable.

@EndFunction
*/
void
vlistDefVarName(int vlistID, int varID, const char *name)
{
  (void) cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, name);
}

/*
@Function  vlistDefVarLongname
@Title     Define the long name of a Variable

@Prototype void vlistDefVarLongname(int vlistID, int varID, const char *longname)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  longname Long name of the variable.

@Description
The function @func{vlistDefVarLongname} defines the long name of a variable.

@EndFunction
*/
void
vlistDefVarLongname(int vlistID, int varID, const char *longname)
{
  if (longname) (void) cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, longname);
}

/*
@Function  vlistDefVarStdname
@Title     Define the standard name of a Variable

@Prototype void vlistDefVarStdname(int vlistID, int varID, const char *stdname)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  stdname  Standard name of the variable.

@Description
The function @func{vlistDefVarStdname} defines the standard name of a variable.

@EndFunction
*/
void
vlistDefVarStdname(int vlistID, int varID, const char *stdname)
{
  if (stdname) (void) cdiDefKeyString(vlistID, varID, CDI_KEY_STDNAME, stdname);
}

/*
@Function  vlistDefVarUnits
@Title     Define the units of a Variable

@Prototype void vlistDefVarUnits(int vlistID, int varID, const char *units)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  units    Units of the variable.

@Description
The function @func{vlistDefVarUnits} defines the units of a variable.

@EndFunction
*/
void
vlistDefVarUnits(int vlistID, int varID, const char *units)
{
  if (units) (void) cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, units);
}

/*
@Function  vlistInqVarMissval
@Title     Get the missing value of a Variable

@Prototype double vlistInqVarMissval(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarMissval} returns the missing value of a variable.

@Result
@func{vlistInqVarMissval} returns the missing value of the variable.

@EndFunction
*/
double
vlistInqVarMissval(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->missval;
}

/*
@Function  vlistDefVarMissval
@Title     Define the missing value of a Variable

@Prototype void vlistDefVarMissval(int vlistID, int varID, double missval)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  missval  Missing value.

@Description
The function @func{vlistDefVarMissval} defines the missing value of a variable.

@EndFunction
*/
void
vlistDefVarMissval(int vlistID, int varID, double missval)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  varptr->missval = missval;
  varptr->missvalused = true;
  cdiDefKeyFloat(vlistID, varID, CDI_KEY_MISSVAL, missval);
}

int
vlistInqVarValidrange(int vlistID, int varID, double *validrange)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (validrange != NULL && varptr->lvalidrange)
  {
    validrange[0] = varptr->validrange[0];
    validrange[1] = varptr->validrange[1];
  }

  return (int) varptr->lvalidrange;
}

void
vlistDefVarValidrange(int vlistID, int varID, const double *validrange)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  varptr->validrange[0] = validrange[0];
  varptr->validrange[1] = validrange[1];
  varptr->lvalidrange = true;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

void
vlistDefVarTimetype(int vlistID, int varID, int timetype)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  if (varptr->timetype != timetype)
  {
    varptr->timetype = timetype;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarTimetype(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->timetype;
}

void
vlistDefVarTsteptype(int vlistID, int varID, int tsteptype)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  if (varptr->tsteptype != tsteptype)
  {
    varptr->tsteptype = tsteptype;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

/*
@Function  vlistInqVarTsteptype
@Title     Get the timestep type of a Variable

@Prototype int vlistInqVarTsteptype(int vlistID, int varID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.

@Description
The function @func{vlistInqVarTsteptype} returns the timestep type of a Variable.

@Result
@func{vlistInqVarTsteptype} returns the timestep type of the Variable,
one of the set of predefined CDI timestep types.
The valid CDI timestep types are @func{TSTEP_INSTANT},
@func{TSTEP_ACCUM}, @func{TSTEP_AVG}, @func{TSTEP_MAX}, @func{TSTEP_MIN} and @func{TSTEP_SD}.

@EndFunction
*/
int
vlistInqVarTsteptype(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->tsteptype;
}

int
vlistInqVarMissvalUsed(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return (int) varptr->missvalused;
}

void
vlistDefFlag(int vlistID, int varID, int levID, int flag)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  var_t *varptr = vlistptr_get_varptr(__func__, vlistptr, varID);

  levinfo_t li = DEFAULT_LEVINFO(levID);
  if (varptr->levinfo) { ; }
  else if (flag != li.flag) { cdiVlistCreateVarLevInfo(vlistptr, varID); }
  else { return; }

  varptr->levinfo[levID].flag = flag;
  varptr->flag = 0;

  int nlevs = zaxisInqSize(varptr->zaxisID);
  for (int levelID = 0; levelID < nlevs; ++levelID)
  {
    if (varptr->levinfo[levelID].flag)
    {
      varptr->flag = 1;
      break;
    }
  }

  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

int
vlist_inq_flag(vlist_t *vlistPtr, int varID, int levelID)
{
  var_t *varPtr = vlistptr_get_varptr(__func__, vlistPtr, varID);

  if (varPtr->levinfo) return varPtr->levinfo[levelID].flag;

  levinfo_t li = DEFAULT_LEVINFO(levelID);
  return li.flag;
}

int
pvlistInqFlag(void *vlistPtr, int varID, int levelID)
{
  return vlist_inq_flag((vlist_t *) vlistPtr, varID, levelID);
}

int
vlistInqFlag(int vlistID, int varID, int levelID)
{
  return vlist_inq_flag(vlist_to_pointer(vlistID), varID, levelID);
}

int
vlistFindVar(int vlistID, int fvarID)
{
  int varID;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (varID = 0; varID < vlistptr->nvars; ++varID)
  {
    if (vlistptr->vars[varID].fvarID == fvarID) break;
  }

  if (varID == vlistptr->nvars)
  {
    varID = -1;
    Warning("varID not found for fvarID %d in vlistID %d!", fvarID, vlistID);
  }

  return varID;
}

int
vlistFindLevel(int vlistID, int fvarID, int flevelID)
{
  int levelID = -1;
  int varID = vlistFindVar(vlistID, fvarID);
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varID != -1)
  {
    int nlevs = zaxisInqSize(varptr->zaxisID);
    for (levelID = 0; levelID < nlevs; ++levelID)
    {
      if (varptr->levinfo[levelID].flevelID == flevelID) break;
    }

    if (levelID == nlevs)
    {
      levelID = -1;
      Warning("levelID not found for fvarID %d and levelID %d in vlistID %d!", fvarID, flevelID, vlistID);
    }
  }

  return levelID;
}

int
vlistMergedVar(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->mvarID;
}

int
vlistMergedLevel(int vlistID, int varID, int levelID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->levinfo) return varptr->levinfo[levelID].mlevelID;

  levinfo_t li = DEFAULT_LEVINFO(levelID);
  return li.mlevelID;
}

void
vlistDefIndex(int vlistID, int varID, int levelID, int index)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  var_t *varptr = vlistptr_get_varptr(__func__, vlistptr, varID);

  levinfo_t li = DEFAULT_LEVINFO(levelID);
  if (varptr->levinfo) { ; }
  else if (index != li.index) { cdiVlistCreateVarLevInfo(vlistptr, varID); }
  else { return; }

  varptr->levinfo[levelID].index = index;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

int
vlistInqIndex(int vlistID, int varID, int levelID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->levinfo) return varptr->levinfo[levelID].index;

  levinfo_t li = DEFAULT_LEVINFO(levelID);
  return li.index;
}

void
vlistChangeVarZaxis(int vlistID, int varID, int zaxisID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  var_t *varptr = vlistptr_get_varptr(__func__, vlistptr, varID);

  int nlevs1 = zaxisInqSize(varptr->zaxisID);
  int nlevs2 = zaxisInqSize(zaxisID);

  if (nlevs1 != nlevs2) Error("Number of levels must not change!");

  int nvars = vlistptr->nvars;
  int found = 0;
  int oldZaxisID = varptr->zaxisID;
  for (int i = 0; i < varID; ++i) found |= (vlistptr->vars[i].zaxisID == oldZaxisID);
  for (int i = varID + 1; i < nvars; ++i) found |= (vlistptr->vars[i].zaxisID == oldZaxisID);

  if (found)
  {
    int nzaxis = vlistptr->nzaxis;
    for (int i = 0; i < nzaxis; ++i)
      if (vlistptr->zaxisIDs[i] == oldZaxisID) vlistptr->zaxisIDs[i] = zaxisID;
  }
  else
    vlistAdd2ZaxisIDs(vlistptr, zaxisID);

  varptr->zaxisID = zaxisID;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

void
vlistChangeVarGrid(int vlistID, int varID, int gridID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  var_t *varptr = vlistptr_get_varptr(__func__, vlistptr, varID);

  int nvars = vlistptr->nvars;
  int index;
  for (index = 0; index < nvars; index++)
    if (index != varID)
      if (vlistptr->vars[index].gridID == vlistptr->vars[varID].gridID) break;

  if (index == nvars)
  {
    for (index = 0; index < vlistptr->ngrids; index++)
      if (vlistptr->gridIDs[index] == vlistptr->vars[varID].gridID) vlistptr->gridIDs[index] = gridID;
  }
  else
    vlistAdd2GridIDs(vlistptr, gridID);

  varptr->gridID = gridID;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

void
vlistDefVarCompType(int vlistID, int varID, int comptype)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->comptype != comptype)
  {
    varptr->comptype = comptype;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarCompType(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->comptype;
}

void
vlistDefVarCompLevel(int vlistID, int varID, int complevel)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (varptr->complevel != complevel)
  {
    varptr->complevel = complevel;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarCompLevel(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->complevel;
}

void
vlistDefVarNSB(int vlistID, int varID, int nsb)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  if (varptr->nsb != nsb)
  {
    varptr->nsb = nsb;
    reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
  }
}

int
vlistInqVarNSB(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return varptr->nsb;
}

static int
vlistEncodeXyz(const int dimorder[3])
{
  return (short) (dimorder[0] * 100 + dimorder[1] * 10 + dimorder[2]);
}

static void
vlistDecodeXyz(int xyz, int outDimorder[3])
{
  outDimorder[0] = xyz / 100, xyz = xyz % 100;
  outDimorder[1] = xyz / 10;
  outDimorder[2] = xyz % 10;
}

static const short xyzStorVals[] = { 123, 132, 213, 231, 312, 321 };
enum
{
  numXYZStorVals = sizeof(xyzStorVals) / sizeof(xyzStorVals[0])
};

void
vlistDefVarXYZ(int vlistID, int varID, int xyz)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);

  if (xyz == 3) xyz = 321;

  // check xyz dimension order
  {
    int dimorder[3];
    vlistDecodeXyz(xyz, dimorder);
    int dimx = 0, dimy = 0, dimz = 0;
    for (int id = 0; id < 3; ++id)
    {
      switch (dimorder[id])
      {
        case 1: dimx++; break;
        case 2: dimy++; break;
        case 3: dimz++; break;
        default: dimorder[id] = 0; break;  // Ensure that we assign a valid dimension to this position.
      }
    }
    if (dimz > 1 || dimy > 1 || dimx > 1)
      xyz = 321;  // ZYX
    else
    {
      // clang-format off
      if (dimz == 0) for (int id = 0; id < 3; ++id) if (dimorder[id] == 0) { dimorder[id] = 3; break; }
      if (dimy == 0) for (int id = 0; id < 3; ++id) if (dimorder[id] == 0) { dimorder[id] = 2; break; }
      if (dimx == 0) for (int id = 0; id < 3; ++id) if (dimorder[id] == 0) { dimorder[id] = 1; break; }
      // clang-format on
      xyz = vlistEncodeXyz(dimorder);
    }
  }

  assert(xyz == 123 || xyz == 312 || xyz == 231 || xyz == 321 || xyz == 132 || xyz == 213);

  for (size_t i = 0; i < numXYZStorVals; ++i)
    if (xyz == xyzStorVals[i])
    {
      xyz = (int) i;
      break;
    }
  varptr->xyz = (signed char) xyz;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}

void
vlistInqVarDimorder(int vlistID, int varID, int outDimorder[3])
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  vlistDecodeXyz(xyzStorVals[varptr->xyz], outDimorder);
}

int
vlistInqVarXYZ(int vlistID, int varID)
{
  var_t *varptr = vlistptr_get_varptr(__func__, vlist_to_pointer(vlistID), varID);
  return xyzStorVals[varptr->xyz];
}

int
vlistVarCompare(vlist_t *a, int varIDA, vlist_t *b, int varIDB)
{
  xassert(a && b && varIDA >= 0 && varIDA < a->nvars && varIDB >= 0 && varIDB < b->nvars);
  var_t *pva = a->vars + varIDA, *pvb = b->vars + varIDB;
#define FCMP(f) ((pva->f) != (pvb->f))
#define FCMPFLT(f) (IS_NOT_EQUAL((pva->f), (pvb->f)))
#define FCMPSTR(fs) ((pva->fs) != (pvb->fs) && strcmp((pva->fs), (pvb->fs)))
#define FCMP2(f) (namespaceResHDecode(pva->f).idx != namespaceResHDecode(pvb->f).idx)
  int diff = (int) FCMP(fvarID) | FCMP(mvarID) | FCMP(flag) | FCMP(param) | FCMP(datatype) | FCMP(timetype) | FCMP(tsteptype)
             | FCMP(xyz) | FCMP2(gridID) | FCMP2(zaxisID) | FCMP2(instID) | FCMP2(modelID) | FCMP2(tableID) | FCMP(missvalused)
             | FCMPFLT(missval) | FCMP(comptype) | FCMP(complevel) | FCMP(lvalidrange) | FCMPFLT(validrange[0])
             | FCMPFLT(validrange[1]);
#undef FCMP
#undef FCMPFLT
#undef FCMPSTR
#undef FCMP2
  if ((diff |= ((pva->levinfo == NULL) ^ (pvb->levinfo == NULL)))) return 1;

  if (pva->levinfo)
  {
    int zaxisID = pva->zaxisID;
    size_t nlevs = (size_t) zaxisInqSize(zaxisID);
    diff |= (memcmp(pva->levinfo, pvb->levinfo, sizeof(levinfo_t) * nlevs) != 0);
    if (diff) return 1;
  }

  size_t natts = a->vars[varIDA].atts.nelems;
  if (natts != b->vars[varIDB].atts.nelems) return 1;
  for (size_t attID = 0; attID < natts; ++attID) diff |= cdi_att_compare(&a->vars[varIDA].atts, &b->vars[varIDB].atts, (int) attID);

  size_t nkeys = a->vars[varIDA].keys.nelems;
  if (nkeys != b->vars[varIDB].keys.nelems) return 1;
  for (size_t keyID = 0; keyID < nkeys; ++keyID) diff |= cdi_key_compare(&a->vars[varIDA].keys, &b->vars[varIDB].keys, (int) keyID);

  return diff;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */


enum
{
  VLISTVAR_PACK_INT_IDX_FLAG,
  VLISTVAR_PACK_INT_IDX_GRIDID,
  VLISTVAR_PACK_INT_IDX_ZAXISID,
  VLISTVAR_PACK_INT_IDX_TIMETYPE,
  VLISTVAR_PACK_INT_IDX_DATATYPE,
  VLISTVAR_PACK_INT_IDX_PARAM,
  VLISTVAR_PACK_INT_IDX_INSTID,
  VLISTVAR_PACK_INT_IDX_MODELID,
  VLISTVAR_PACK_INT_IDX_TABLEID,
  VLISTVAR_PACK_INT_IDX_MISSVALUSED,
  VLISTVAR_PACK_INT_IDX_COMPTYPE,
  VLISTVAR_PACK_INT_IDX_COMPLEVEL,
  VLISTVAR_PACK_INT_IDX_NLEVS,
  vlistvarNint
};

enum
{
  VLIST_VAR_PACK_DBL_MISSVAL,
  vlistvar_ndbls,
};

int
vlistVarGetPackSize(vlist_t *p, int varID, void *context)
{
  var_t *var = p->vars + varID;
  int varsize
      = serializeGetSize(vlistvarNint, CDI_DATATYPE_INT, context) + serializeGetSize(vlistvar_ndbls, CDI_DATATYPE_FLT64, context);

  if (var->levinfo) varsize += serializeGetSize(4 * zaxisInqSize(var->zaxisID), CDI_DATATYPE_INT, context);
  varsize += serializeKeysGetPackSize(&var->keys, context);
  varsize += cdiAttsGetSize(p, varID, context);

  return varsize;
}

void
vlistVarPack(vlist_t *p, int varID, char *buf, int size, int *position, void *context)
{
  var_t *var = p->vars + varID;
  int nlevs;
  {
    int tempbuf[vlistvarNint];
    tempbuf[VLISTVAR_PACK_INT_IDX_FLAG] = var->flag;
    tempbuf[VLISTVAR_PACK_INT_IDX_GRIDID] = var->gridID;
    tempbuf[VLISTVAR_PACK_INT_IDX_ZAXISID] = var->zaxisID;
    tempbuf[VLISTVAR_PACK_INT_IDX_TIMETYPE] = var->timetype;
    tempbuf[VLISTVAR_PACK_INT_IDX_DATATYPE] = var->datatype;
    tempbuf[VLISTVAR_PACK_INT_IDX_PARAM] = var->param;
    tempbuf[VLISTVAR_PACK_INT_IDX_INSTID] = var->instID;
    tempbuf[VLISTVAR_PACK_INT_IDX_MODELID] = var->modelID;
    tempbuf[VLISTVAR_PACK_INT_IDX_TABLEID] = var->tableID;
    tempbuf[VLISTVAR_PACK_INT_IDX_MISSVALUSED] = (int) var->missvalused;
    tempbuf[VLISTVAR_PACK_INT_IDX_COMPTYPE] = var->comptype;
    tempbuf[VLISTVAR_PACK_INT_IDX_COMPLEVEL] = var->complevel;
    nlevs = var->levinfo ? zaxisInqSize(var->zaxisID) : 0;
    tempbuf[VLISTVAR_PACK_INT_IDX_NLEVS] = nlevs;
    serializePack(tempbuf, vlistvarNint, CDI_DATATYPE_INT, buf, size, position, context);
  }
  {
    double dtempbuf[vlistvar_ndbls];
    dtempbuf[VLIST_VAR_PACK_DBL_MISSVAL] = var->missval;
    serializePack(dtempbuf, vlistvar_ndbls, CDI_DATATYPE_FLT64, buf, size, position, context);
  }
  if (nlevs)
  {
    int *levbuf = (int *) Malloc((size_t) nlevs * sizeof(int));
    for (int levID = 0; levID < nlevs; ++levID) levbuf[levID] = var->levinfo[levID].flag;
    serializePack(levbuf, nlevs, CDI_DATATYPE_INT, buf, size, position, context);
    for (int levID = 0; levID < nlevs; ++levID) levbuf[levID] = var->levinfo[levID].index;
    serializePack(levbuf, nlevs, CDI_DATATYPE_INT, buf, size, position, context);
    for (int levID = 0; levID < nlevs; ++levID) levbuf[levID] = var->levinfo[levID].mlevelID;
    serializePack(levbuf, nlevs, CDI_DATATYPE_INT, buf, size, position, context);
    for (int levID = 0; levID < nlevs; ++levID) levbuf[levID] = var->levinfo[levID].flevelID;
    Free(levbuf);
  }

  serializeKeysPack(&var->keys, buf, size, position, context);

  cdiAttsPack(p, varID, buf, size, position, context);
}

void
vlistVarUnpack(int vlistID, char *buf, int size, int *position, int originNamespace, void *context)
{
#define adaptKey(key) (namespaceAdaptKey((key), originNamespace))
  double dtempbuf[vlistvar_ndbls];
  int tempbuf[vlistvarNint];
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  serializeUnpack(buf, size, position, tempbuf, vlistvarNint, CDI_DATATYPE_INT, context);
  serializeUnpack(buf, size, position, dtempbuf, vlistvar_ndbls, CDI_DATATYPE_FLT64, context);

  /* ------------------------------------------- */
  /* NOTE: Tile sets  currently not supported!!! */
  /* ------------------------------------------- */

  int newvar = vlistDefVar(vlistID, adaptKey(tempbuf[VLISTVAR_PACK_INT_IDX_GRIDID]),
                           adaptKey(tempbuf[VLISTVAR_PACK_INT_IDX_ZAXISID]), tempbuf[VLISTVAR_PACK_INT_IDX_TIMETYPE]);
  vlistDefVarDatatype(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_DATATYPE]);
  vlistDefVarInstitut(vlistID, newvar, adaptKey(tempbuf[VLISTVAR_PACK_INT_IDX_INSTID]));
  vlistDefVarModel(vlistID, newvar, adaptKey(tempbuf[VLISTVAR_PACK_INT_IDX_MODELID]));
  vlistDefVarTable(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_TABLEID]);
  // FIXME: changing the table might change the param code
  vlistDefVarParam(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_PARAM]);
  if (tempbuf[VLISTVAR_PACK_INT_IDX_MISSVALUSED]) vlistDefVarMissval(vlistID, newvar, dtempbuf[VLIST_VAR_PACK_DBL_MISSVAL]);
  vlistDefVarCompType(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_COMPTYPE]);
  vlistDefVarCompLevel(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_COMPLEVEL]);
  const int nlevs = tempbuf[VLISTVAR_PACK_INT_IDX_NLEVS];
  var_t *var = vlistptr->vars + newvar;
  if (nlevs)
  {
    int i, flagSetLev = 0;
    cdiVlistCreateVarLevInfo(vlistptr, newvar);

    int *levbuf = (int *) Malloc((size_t) nlevs * sizeof(int));
    serializeUnpack(buf, size, position, levbuf, nlevs, CDI_DATATYPE_INT, context);
    for (i = 0; i < nlevs; ++i) vlistDefFlag(vlistID, newvar, i, levbuf[i]);
    for (i = 0; i < nlevs; ++i)
      if (levbuf[i] == tempbuf[0]) flagSetLev = i;
    vlistDefFlag(vlistID, newvar, flagSetLev, levbuf[flagSetLev]);
    serializeUnpack(buf, size, position, levbuf, nlevs, CDI_DATATYPE_INT, context);
    for (i = 0; i < nlevs; ++i) vlistDefIndex(vlistID, newvar, i, levbuf[i]);
    serializeUnpack(buf, size, position, levbuf, nlevs, CDI_DATATYPE_INT, context);
    for (i = 0; i < nlevs; ++i) var->levinfo[i].mlevelID = levbuf[i];
    serializeUnpack(buf, size, position, levbuf, nlevs, CDI_DATATYPE_INT, context);
    for (i = 0; i < nlevs; ++i) var->levinfo[i].flevelID = levbuf[i];
    Free(levbuf);
  }

  serializeKeysUnpack(buf, size, position, &var->keys, context);

  cdiAttsUnpack(vlistID, newvar, buf, size, position, context);
#undef adaptKey
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#ifdef HAVE_CONFIG_H
#endif


/* vlistDefVarIntKey: Set an arbitrary keyword/integer value pair for GRIB API */
void
vlistDefVarIntKey(int vlistID, int varID, const char *name, int value)
{
#ifdef HAVE_LIBGRIB_API
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr == NULL) Error("Internal error!");
  int idx;

  if (vlistptr->immutable)
    Error("vlistDefVarIntKey() was called on an immutable vlist object (vlistID = %d)\n"
          "Either call vlistDefVarIntKey() before passing the vlist object to streamDefVlist(),\n"
          "or use the stream-internal vlist by calling streamInqVlist().",
          vlistID);

  for (idx = 0; idx < vlistptr->vars[varID].opt_grib_nentries; idx++)
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[idx].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_int))
      break;

  if (idx < vlistptr->vars[varID].opt_grib_nentries)
  {
    vlistptr->vars[varID].opt_grib_kvpair[idx].int_val = value;
    vlistptr->vars[varID].opt_grib_kvpair[idx].update = true;
  }
  else
  {
    resize_opt_grib_entries(&vlistptr->vars[varID], vlistptr->vars[varID].opt_grib_nentries + 1);
    vlistptr->vars[varID].opt_grib_nentries += 1;
    idx = vlistptr->vars[varID].opt_grib_nentries - 1;
    vlistptr->vars[varID].opt_grib_kvpair[idx].data_type = t_int;
    vlistptr->vars[varID].opt_grib_kvpair[idx].int_val = value;
    vlistptr->vars[varID].opt_grib_kvpair[idx].update = true;
    if (name)
      vlistptr->vars[varID].opt_grib_kvpair[idx].keyword = strdup(name);
    else
      Error("Internal error, name undefined!");
  }

  if (CDI_Debug)
  {
    Message("define additional GRIB2 key \"%s\" (integer): %d", name, value);
    Message("total list of registered, additional GRIB2 keys (total: %d):", vlistptr->vars[varID].opt_grib_nentries);
    for (idx = 0; idx < vlistptr->vars[varID].opt_grib_nentries; idx++)
      if (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_int)
        Message("%s -> integer %d", vlistptr->vars[varID].opt_grib_kvpair[idx].keyword,
                vlistptr->vars[varID].opt_grib_kvpair[idx].int_val);
      else if (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_double)
        Message("%s -> double %d", vlistptr->vars[varID].opt_grib_kvpair[idx].keyword,
                vlistptr->vars[varID].opt_grib_kvpair[idx].dbl_val);
      else
        Message("%s -> unknown", vlistptr->vars[varID].opt_grib_kvpair[idx].keyword);
  }

  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
#else
  (void) vlistID;
  (void) varID;
  (void) name;
  (void) value;
#endif
}

/* vlistDefVarDblKey: Set an arbitrary keyword/double value pair for GRIB API */
void
vlistDefVarDblKey(int vlistID, int varID, const char *name, double value)
{
#ifdef HAVE_LIBGRIB_API
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr == NULL) Error("Internal error!");
  int idx;

  if (vlistptr->immutable)
    Error("vlistDefVarDblKey() was called on an immutable vlist object (vlistID = %d)\n"
          "Either call vlistDefVarIntKey() before passing the vlist object to streamDefVlist(),\n"
          "or use the stream-internal vlist by calling streamInqVlist().",
          vlistID);

  for (idx = 0; idx < vlistptr->vars[varID].opt_grib_nentries; idx++)
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[idx].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_double))
      break;

  if (idx < vlistptr->vars[varID].opt_grib_nentries)
  {
    vlistptr->vars[varID].opt_grib_kvpair[idx].dbl_val = value;
    vlistptr->vars[varID].opt_grib_kvpair[idx].update = true;
  }
  else
  {
    resize_opt_grib_entries(&vlistptr->vars[varID], vlistptr->vars[varID].opt_grib_nentries + 1);
    vlistptr->vars[varID].opt_grib_nentries += 1;
    idx = vlistptr->vars[varID].opt_grib_nentries - 1;
    vlistptr->vars[varID].opt_grib_kvpair[idx].data_type = t_double;
    vlistptr->vars[varID].opt_grib_kvpair[idx].dbl_val = value;
    vlistptr->vars[varID].opt_grib_kvpair[idx].update = true;
    if (name)
      vlistptr->vars[varID].opt_grib_kvpair[idx].keyword = strdup(name);
    else
      Error("Internal error, name undefined!");
  }

  if (CDI_Debug)
  {
    Message("define additional GRIB2 key \"%s\" (double): %d", name, value);
    Message("total list of registered, additional GRIB2 keys (total: %d):", vlistptr->vars[varID].opt_grib_nentries);
    for (idx = 0; idx < vlistptr->vars[varID].opt_grib_nentries; idx++)
      if (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_int)
        Message("%s -> integer %d", vlistptr->vars[varID].opt_grib_kvpair[idx].keyword,
                vlistptr->vars[varID].opt_grib_kvpair[idx].int_val);
      else if (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_double)
        Message("%s -> double %d", vlistptr->vars[varID].opt_grib_kvpair[idx].keyword,
                vlistptr->vars[varID].opt_grib_kvpair[idx].dbl_val);
      else
        Message("%s -> unknown", vlistptr->vars[varID].opt_grib_kvpair[idx].keyword);
  }

  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
#else
  (void) vlistID;
  (void) varID;
  (void) name;
  (void) value;
#endif
}

void
vlistDefVarIntArrKey(int vlistID, int varID, const char *name, int *values, int nvalues)
{
#ifdef HAVE_LIBGRIB_API
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr == NULL) Error("Internal error!");
  if (name == NULL) Error("Internal error, name undefined!");

  if (vlistptr->immutable)
    Error("vlistDefVarIntArrKey() was called on an immutable vlist object (vlistID = %d)\n"
          "Either call this before passing the vlist object to streamDefVlist(),\n"
          "or use the stream-internal vlist by calling streamInqVlist().",
          vlistID);

  int idx;
  for (idx = 0; idx < vlistptr->vars[varID].opt_grib_nentries; idx++)
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[idx].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_intarr))
      break;

  opt_key_val_pair_t *entry = NULL;

  if (idx < vlistptr->vars[varID].opt_grib_nentries)
  {
    entry = &vlistptr->vars[varID].opt_grib_kvpair[idx];
    free(entry->int_arr);
  }
  else
  {
    resize_opt_grib_entries(&vlistptr->vars[varID], vlistptr->vars[varID].opt_grib_nentries + 1);
    vlistptr->vars[varID].opt_grib_nentries += 1;
    idx = vlistptr->vars[varID].opt_grib_nentries - 1;
    entry = &vlistptr->vars[varID].opt_grib_kvpair[idx];

    entry->keyword = strdup(name);
    entry->data_type = t_intarr;
  }

  entry->update = true;
  entry->arr_len = nvalues;
  entry->int_arr = (int *) malloc(nvalues * sizeof(int));
  if (!entry->int_arr) Error("Memory allocation failed for int_arr");
  memcpy(entry->int_arr, values, nvalues * sizeof(int));

  entry->dbl_arr = NULL;  // safety

  if (CDI_Debug)
  {
    Message("define additional GRIB2 key \"%s\" (integer array, len=%zu)", name, nvalues);
    for (int i = 0; i < nvalues; ++i) Message("  [%zu] = %d", i, entry->int_arr[i]);
  }

  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
#else
  (void) vlistID;
  (void) varID;
  (void) name;
  (void) values;
  (void) nvalues;
#endif
}

void
vlistDefVarDblArrKey(int vlistID, int varID, const char *name, double *values, int nvalues)
{
#ifdef HAVE_LIBGRIB_API
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr == NULL) Error("Internal error!");
  if (name == NULL) Error("Internal error, name undefined!");

  if (vlistptr->immutable)
    Error("vlistDefVarDblArrKey() was called on an immutable vlist object (vlistID = %d)\n"
          "Either call this before passing the vlist object to streamDefVlist(),\n"
          "or use the stream-internal vlist by calling streamInqVlist().",
          vlistID);

  int idx;
  for (idx = 0; idx < vlistptr->vars[varID].opt_grib_nentries; idx++)
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[idx].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[idx].data_type == t_doublearr))
      break;

  opt_key_val_pair_t *entry;

  if (idx < vlistptr->vars[varID].opt_grib_nentries)
  {
    entry = &vlistptr->vars[varID].opt_grib_kvpair[idx];
    free(entry->dbl_arr);
  }
  else
  {
    resize_opt_grib_entries(&vlistptr->vars[varID], vlistptr->vars[varID].opt_grib_nentries + 1);
    vlistptr->vars[varID].opt_grib_nentries += 1;
    idx = vlistptr->vars[varID].opt_grib_nentries - 1;
    entry = &vlistptr->vars[varID].opt_grib_kvpair[idx];

    entry->keyword = strdup(name);
    entry->data_type = t_doublearr;
  }

  entry->update = true;
  entry->arr_len = nvalues;
  entry->dbl_arr = (double *) malloc(nvalues * sizeof(double));
  if (!entry->dbl_arr) Error("Memory allocation failed for dbl_arr");
  memcpy(entry->dbl_arr, values, nvalues * sizeof(double));

  entry->int_arr = NULL;  // safety

  if (CDI_Debug)
  {
    Message("define additional GRIB2 key \"%s\" (double array, len=%zu)", name, nvalues);
    for (int i = 0; i < nvalues; ++i) Message("  [%zu] = %g", i, entry->dbl_arr[i]);
  }

  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
#else
  (void) vlistID;
  (void) varID;
  (void) name;
  (void) values;
  (void) nvalues;
#endif
}

/* cdiClearAdditionalKeys: Clears the list of additional GRIB keys. */
void
cdiClearAdditionalKeys(void)
{
#ifdef HAVE_LIBGRIB_API
  for (int i = 0; i < cdiNAdditionalGRIBKeys; ++i) free(cdiAdditionalGRIBKeys[i]);
  cdiNAdditionalGRIBKeys = 0;
#endif
}

/* cdiDefAdditionalKey: Register an additional GRIB key which is read when file is opened. */
void
cdiDefAdditionalKey(const char *name)
{
#ifdef HAVE_LIBGRIB_API
  int idx = cdiNAdditionalGRIBKeys;
  cdiNAdditionalGRIBKeys++;
  if (idx >= MAX_OPT_GRIB_ENTRIES) Error("Too many additional keywords!");
  if (name)
    cdiAdditionalGRIBKeys[idx] = strdup(name);
  else
    Error("Internal error!");
#else
  (void) name;
#endif
}

/* vlistHasVarKey: returns 1 if meta-data key was read, 0 otherwise. */
int
vlistHasVarKey(int vlistID, int varID, const char *name)
{
#ifdef HAVE_LIBGRIB_API
  /* check if the GRIB key was previously read and is stored */
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; ++i)
  {
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword)) return 1;
  }
#else
  (void) vlistID;
  (void) varID;
  (void) name;
#endif
  return 0;
}

/* vlistInqVarDblKey: raw access to GRIB meta-data */
double
vlistInqVarDblKey(int vlistID, int varID, const char *name)
{
  double value = 0;
#ifdef HAVE_LIBGRIB_API
  /* check if the GRIB key was previously read and is stored in "opt_grib_dbl_val" */
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; ++i)
  {
    int isub = subtypeInqActiveIndex(vlistptr->vars[varID].subtypeID);
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_double)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].subtype_index == isub))
      return vlistptr->vars[varID].opt_grib_kvpair[i].dbl_val;
  }
#else
  (void) vlistID;
  (void) varID;
  (void) name;
#endif
  return value;
}

/* vlistInqVarIntKey: raw access to GRIB meta-data */
int
vlistInqVarIntKey(int vlistID, int varID, const char *name)
{
  long value = 0;
#ifdef HAVE_LIBGRIB_API
  /* check if the GRIB key was previously read and is stored in "opt_grib_int_val" */
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; ++i)
  {
    int isub = subtypeInqActiveIndex(vlistptr->vars[varID].subtypeID);
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_int)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].subtype_index == isub))
      return vlistptr->vars[varID].opt_grib_kvpair[i].int_val;
  }

#else
  (void) vlistID;
  (void) varID;
  (void) name;
#endif
  return (int) value;
}

/* vlistInqVarIntArrKey: raw access to GRIB integer array meta-data */
int *
vlistInqVarIntArrKey(int vlistID, int varID, const char *name)
{
  int *int_arr = NULL;

#ifdef HAVE_LIBGRIB_API
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; ++i)
  {
    int isub = subtypeInqActiveIndex(vlistptr->vars[varID].subtypeID);
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_intarr)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].subtype_index == isub))
    {
      int_arr = vlistptr->vars[varID].opt_grib_kvpair[i].int_arr;
      break;
    }
  }
#else
  (void) vlistID;
  (void) varID;
  (void) name;
#endif

  return int_arr;
}

/* vlistInqVarDblArrKey: raw access to GRIB double array meta-data */
double *
vlistInqVarDblArrKey(int vlistID, int varID, const char *name)
{
  double *dbl_arr = NULL;

#ifdef HAVE_LIBGRIB_API
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for (int i = 0; i < vlistptr->vars[varID].opt_grib_nentries; ++i)
  {
    int isub = subtypeInqActiveIndex(vlistptr->vars[varID].subtypeID);
    if (str_is_equal(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_doublearr)
        && (vlistptr->vars[varID].opt_grib_kvpair[i].subtype_index == isub))
    {
      dbl_arr = vlistptr->vars[varID].opt_grib_kvpair[i].dbl_arr;
      break;
    }
  }
#else
  (void) vlistID;
  (void) varID;
  (void) name;
#endif

  return dbl_arr;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
/* DO NOT REMOVE the config.h include file under any circumstances,
 * it's very much needed on some platforms */
#if defined(HAVE_CONFIG_H)
#endif
/* DO NOT REMOVE the above config.h include file under any
 * circumstances as long as it's the autoconf configuration header
 * used to build this package. When it's missing on some platforms,
 * some poor person has to do long, tedious debugging sessions, where
 * struct offsets almost imperceptibly change from one file to the
 * next to find out what happened */

#include <string.h>
#include <math.h>
#include <float.h>



enum
{
  LevelUp = 1,
  LevelDown = 2
};

// clang-format off
static const struct
{
  unsigned char positive;   // 1: up;  2: down
  const char *name;
  const char *longname;
  const char *stdname;
  const char *units;
}
ZaxistypeEntry[] = {
  { /*  0 */ 0, "sfc",               "surface",                     "",               ""},
  { /*  1 */ 0, "lev",               "generic",                     "",               ""},
  { /*  2 */ 2, "lev",               "hybrid",                      "",               "level"},
  { /*  3 */ 2, "lev",               "hybrid_half",                 "",               "level"},
  { /*  4 */ 2, "plev",              "pressure",                    "air_pressure",   "Pa"},
  { /*  5 */ 1, "height",            "height",                      "height",         "m"},
  { /*  6 */ 2, "depth",             "depth_below_sea",             "depth",          "m"},
  { /*  7 */ 2, "depth",             "depth_below_land",            "",               "cm"},
  { /*  8 */ 0, "lev",               "isentropic",                  "",               "K"},
  { /*  9 */ 0, "lev",               "trajectory",                  "",               ""},
  { /* 10 */ 1, "alt",               "height_above_mean_sea_level", "altitude",       "m"},
  { /* 11 */ 0, "lev",               "sigma",                       "",               "level"},
  { /* 12 */ 0, "lev",               "meansea",                     "",               "level"},
  { /* 13 */ 0, "toa",               "top_of_atmosphere",           "",               ""},
  { /* 14 */ 0, "seabottom",         "sea_bottom",                  "",               ""},
  { /* 15 */ 0, "atmosphere",        "atmosphere",                  "",               ""},
  { /* 16 */ 0, "cloudbase",         "cloud_base",                  "",               ""},
  { /* 17 */ 0, "cloudtop",          "cloud_top",                   "",               ""},
  { /* 18 */ 0, "isotherm0",         "isotherm_zero",               "",               ""},
  { /* 19 */ 0, "snow",              "snow",                        "",               ""},
  { /* 20 */ 0, "lakebottom",        "lake_bottom",                 "",               ""},
  { /* 21 */ 0, "sedimentbottom",    "sediment_bottom",             "",               ""},
  { /* 22 */ 0, "sedimentbottomta",  "sediment_bottom_ta",          "",               ""},
  { /* 23 */ 0, "sedimentbottomtw",  "sediment_bottom_tw",          "",               ""},
  { /* 24 */ 0, "mixlayer",          "mix_layer",                   "",               ""},
  { /* 25 */ 0, "height",            "generalized_height",          "height",         ""},
  { /* 26 */ 0, "character",         "area_type",                   "",               ""},
  { /* 27 */ 0, "tropopause",        "tropopause",                  "",               ""},
};
// clang-format on

enum
{
  CDI_NumZaxistype = sizeof(ZaxistypeEntry) / sizeof(ZaxistypeEntry[0]),
};

static int zaxisCompareP(zaxis_t *z1, zaxis_t *z2);
static void zaxisDestroyP(void *zaxisptr);
static void zaxisPrintP(void *zaxisptr, FILE *fp);
static int zaxisGetPackSize(void *zaxisptr, void *context);
static void zaxisPack(void *zaxisptr, void *buffer, int size, int *pos, void *context);
static int zaxisTxCode(void *zaxisptr);

static const resOps zaxisOps
    = { (int (*)(void *, void *)) zaxisCompareP, zaxisDestroyP, zaxisPrintP, zaxisGetPackSize, zaxisPack, zaxisTxCode };

const resOps *
getZaxisOps(void)
{
  return &zaxisOps;
}

void
zaxisGetTypeDescription(int zaxisType, int *outPositive, const char **outName, const char **outLongName, const char **outStdName,
                        const char **outUnit)
{
  if (zaxisType < 0 || zaxisType >= CDI_NumZaxistype)
  {
    if (outPositive) *outPositive = 0;
    if (outName) *outName = NULL;
    if (outLongName) *outLongName = NULL;
    if (outStdName) *outStdName = NULL;
    if (outUnit) *outUnit = NULL;
  }
  else
  {
    if (outPositive) *outPositive = ZaxistypeEntry[zaxisType].positive;
    if (outName) *outName = ZaxistypeEntry[zaxisType].name;
    if (outLongName && zaxisType != ZAXIS_GENERIC) *outLongName = ZaxistypeEntry[zaxisType].longname;
    if (outStdName) *outStdName = ZaxistypeEntry[zaxisType].stdname;
    if (outUnit) *outUnit = ZaxistypeEntry[zaxisType].units;
  }
}

zaxis_t *
zaxis_to_pointer(int id)
{
  return (zaxis_t *) reshGetVal(id, &zaxisOps);
}

static void
zaxis_init(zaxis_t *zaxisptr)
{
  zaxisptr->self = CDI_UNDEFID;
  zaxisptr->vals = NULL;
#ifndef USE_MPI
  zaxisptr->cvals = NULL;
  zaxisptr->clength = 0;
#endif
  zaxisptr->ubounds = NULL;
  zaxisptr->lbounds = NULL;
  zaxisptr->weights = NULL;
  zaxisptr->type = CDI_UNDEFID;
  zaxisptr->positive = 0;
  zaxisptr->scalar = 0;
  zaxisptr->direction = 0;
  zaxisptr->size = 0;
  zaxisptr->vctsize = 0;
  zaxisptr->vct = NULL;

  cdiInitKeys(&zaxisptr->keys);
  zaxisptr->atts.nalloc = MAX_ATTRIBUTES;
  zaxisptr->atts.nelems = 0;

  cdiDefVarKeyInt(&zaxisptr->keys, CDI_KEY_DATATYPE, CDI_DATATYPE_FLT64);
}

static zaxis_t *
zaxisNewEntry(int id)
{
  zaxis_t *zaxisptr = (zaxis_t *) Malloc(sizeof(zaxis_t));
  zaxis_init(zaxisptr);

  if (id == CDI_UNDEFID) { zaxisptr->self = reshPut(zaxisptr, &zaxisOps); }
  else
  {
    zaxisptr->self = id;
    reshReplace(id, zaxisptr, &zaxisOps);
  }

  return zaxisptr;
}

static void
zaxisInit(void)
{
  static bool zaxisInitialized = false;
  if (zaxisInitialized) return;
  zaxisInitialized = true;
}

static void
zaxis_copy(zaxis_t *zaxisptr2, zaxis_t *zaxisptr1)
{
  int zaxisID2 = zaxisptr2->self;
  memcpy(zaxisptr2, zaxisptr1, sizeof(zaxis_t));
  zaxisptr2->self = zaxisID2;
  cdiInitKeys(&zaxisptr2->keys);
  cdiCopyVarKeys(&zaxisptr1->keys, &zaxisptr2->keys);
}

unsigned
cdiZaxisCount(void)
{
  return reshCountType(&zaxisOps);
}

static int
zaxisCreate_(int zaxistype, int size, int id)
{
  zaxis_t *zaxisptr = zaxisNewEntry(id);

  xassert(size >= 0);
  zaxisptr->type = zaxistype;
  zaxisptr->size = size;

  int zaxisID = zaxisptr->self;

  if (zaxistype >= 0 && zaxistype < CDI_NumZaxistype)
  {
    cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, ZaxistypeEntry[zaxistype].name);
    if (zaxistype != ZAXIS_GENERIC) zaxisDefLongname(zaxisID, ZaxistypeEntry[zaxistype].longname);
    cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, ZaxistypeEntry[zaxistype].units);

    const char *stdname = ZaxistypeEntry[zaxistype].stdname;
    if (*stdname) cdiDefVarKeyBytes(&zaxisptr->keys, CDI_KEY_STDNAME, (const unsigned char *) stdname, (int) strlen(stdname) + 1);

    zaxisptr->positive = ZaxistypeEntry[zaxistype].positive;
  }
  else { Error("Internal problem! zaxistype=%d out of range (min=0/max=%d)!", zaxistype, CDI_NumZaxistype - 1); }

  return zaxisID;
}

/*
@Function  zaxisCreate
@Title     Create a vertical Z-axis

@Prototype int zaxisCreate(int zaxistype, int size)
@Parameter
    @Item  zaxistype  The type of the Z-axis, one of the set of predefined CDI Z-axis types.
                      The valid CDI Z-axis types are @func{ZAXIS_GENERIC}, @func{ZAXIS_SURFACE},
                      @func{ZAXIS_HYBRID}, @func{ZAXIS_SIGMA}, @func{ZAXIS_PRESSURE}, @func{ZAXIS_HEIGHT},
                      @func{ZAXIS_ISENTROPIC}, @func{ZAXIS_ALTITUDE}, @func{ZAXIS_MEANSEA}, @func{ZAXIS_TOA},
                      @func{ZAXIS_SEA_BOTTOM}, @func{ZAXIS_ATMOSPHERE}, @func{ZAXIS_CLOUD_BASE},
                      @func{ZAXIS_CLOUD_TOP}, @func{ZAXIS_ISOTHERM_ZERO}, @func{ZAXIS_SNOW},
                      @func{ZAXIS_LAKE_BOTTOM}, @func{ZAXIS_SEDIMENT_BOTTOM}, @func{ZAXIS_SEDIMENT_BOTTOM_TA},
                      @func{ZAXIS_SEDIMENT_BOTTOM_TW}, @func{ZAXIS_MIX_LAYER},
                      @func{ZAXIS_DEPTH_BELOW_SEA} and @func{ZAXIS_DEPTH_BELOW_LAND}.
    @Item  size       Number of levels.

@Description
The function @func{zaxisCreate} creates a vertical Z-axis.

@Result
@func{zaxisCreate} returns an identifier to the Z-axis.

@Example
Here is an example using @func{zaxisCreate} to create a pressure level Z-axis:

@Source
   ...
#define  nlev    5
   ...
double levs[nlev] = {101300, 92500, 85000, 50000, 20000};
int zaxisID;
   ...
zaxisID = zaxisCreate(ZAXIS_PRESSURE, nlev);
zaxisDefLevels(zaxisID, levs);
   ...
@EndSource
@EndFunction
*/
int
zaxisCreate(int zaxistype, int size)
{
  if (CDI_Debug) Message("zaxistype: %d size: %d ", zaxistype, size);

  xassert(size);
  zaxisInit();

  return zaxisCreate_(zaxistype, size, CDI_UNDEFID);
}

static void
zaxisDestroyKernel(zaxis_t *zaxisptr)
{
  xassert(zaxisptr);

  if (zaxisptr->vals) Free(zaxisptr->vals);
#ifndef USE_MPI
  if (zaxisptr->cvals)
  {
    for (int i = 0; i < zaxisptr->size; i++) Free(zaxisptr->cvals[i]);
    Free(zaxisptr->cvals);
  }
#endif
  if (zaxisptr->lbounds) Free(zaxisptr->lbounds);
  if (zaxisptr->ubounds) Free(zaxisptr->ubounds);
  if (zaxisptr->weights) Free(zaxisptr->weights);
  if (zaxisptr->vct) Free(zaxisptr->vct);

  int zaxisID = zaxisptr->self;
  cdiDeleteKeys(zaxisID, CDI_GLOBAL);
  cdiDeleteAtts(zaxisID, CDI_GLOBAL);

  Free(zaxisptr);
}

/*
@Function  zaxisDestroy
@Title     Destroy a vertical Z-axis

@Prototype void zaxisDestroy(int zaxisID)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate}.

@EndFunction
*/
void
zaxisDestroy(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  zaxisDestroyKernel(zaxisptr);
  reshRemove(zaxisID, &zaxisOps);
}

static void
zaxisDestroyP(void *zaxisptr)
{
  zaxisDestroyKernel((zaxis_t *) zaxisptr);
}

const char *
zaxisNamePtr(int zaxistype)
{
  const char *name = (zaxistype >= 0 && zaxistype < CDI_NumZaxistype) ? ZaxistypeEntry[zaxistype].longname
                                                                      : ZaxistypeEntry[ZAXIS_GENERIC].longname;
  return name;
}

void
zaxisName(int zaxistype, char *zaxisname)
{
  strcpy(zaxisname, zaxisNamePtr(zaxistype));
}

// obsolete function
void
zaxisDefLtype(int zaxisID, int ltype)
{
  static bool printInfo = true;
  if (printInfo) printInfo = cdiObsoleteInfo(__func__, "cdiDefKeyInt");

  (void) cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, ltype);
}

/*
@Function  zaxisDefName
@Title     Define the name of a Z-axis

@Prototype void zaxisDefName(int zaxisID, const char *name)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  name     Name of the Z-axis.

@Description
The function @func{zaxisDefName} defines the name of a Z-axis.

@EndFunction
*/
void
zaxisDefName(int zaxisID, const char *name)
{
  (void) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, name);
}

/*
@Function  zaxisDefLongname
@Title     Define the longname of a Z-axis

@Prototype void zaxisDefLongname(int zaxisID, const char *longname)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  longname Longname of the Z-axis.

@Description
The function @func{zaxisDefLongname} defines the longname of a Z-axis.

@EndFunction
*/
void
zaxisDefLongname(int zaxisID, const char *longname)
{
  (void) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_LONGNAME, longname);
}

/*
@Function  zaxisDefUnits
@Title     Define the units of a Z-axis

@Prototype void zaxisDefUnits(int zaxisID, const char *units)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  units    Units of the Z-axis.

@Description
The function @func{zaxisDefUnits} defines the units of a Z-axis.

@EndFunction
*/
void
zaxisDefUnits(int zaxisID, const char *units)
{
  (void) cdiDefKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units);
}

/*
@Function  zaxisInqName
@Title     Get the name of a Z-axis

@Prototype void zaxisInqName(int zaxisID, char *name)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.
    @Item  name     Name of the Z-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{zaxisInqName} returns the name of a Z-axis.

@Result
@func{zaxisInqName} returns the name of the Z-axis to the parameter name.

@EndFunction
*/
void
zaxisInqName(int zaxisID, char *name)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, name, &length);
}

const char *
zaxisInqNamePtr(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return cdiInqVarKeyString(&zaxisptr->keys, CDI_KEY_NAME);
}

/*
@Function  zaxisInqLongname
@Title     Get the longname of a Z-axis

@Prototype void zaxisInqLongname(int zaxisID, char *longname)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.
    @Item  longname Longname of the Z-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{zaxisInqLongname} returns the longname of a Z-axis.

@Result
@func{zaxisInqLongname} returns the longname of the Z-axis to the parameter longname.

@EndFunction
*/
void
zaxisInqLongname(int zaxisID, char *longname)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_LONGNAME, longname, &length);
}

/*
@Function  zaxisInqUnits
@Title     Get the units of a Z-axis

@Prototype void zaxisInqUnits(int zaxisID, char *units)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.
    @Item  units    Units of the Z-axis. The caller must allocate space for the
                    returned string. The maximum possible length, in characters, of
                    the string is given by the predefined constant @func{CDI_MAX_NAME}.

@Description
The function @func{zaxisInqUnits} returns the units of a Z-axis.

@Result
@func{zaxisInqUnits} returns the units of the Z-axis to the parameter units.

@EndFunction
*/
void
zaxisInqUnits(int zaxisID, char *units)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
}

void
zaxisInqStdname(int zaxisID, char *stdname)
{
  int length = CDI_MAX_NAME;
  (void) cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_STDNAME, stdname, &length);
}

void
zaxisDefDatatype(int zaxisID, int datatype)
{
  cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_DATATYPE, datatype);
}

int
zaxisInqDatatype(int zaxisID)
{
  int datatype = 0;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  return datatype;
}

void
zaxisDefPositive(int zaxisID, int positive)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  if (zaxisptr->positive != (unsigned) positive)
  {
    zaxisptr->positive = (unsigned) positive;
    reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
  }
}

int
zaxisInqPositive(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return (int) zaxisptr->positive;
}

void
zaxisDefScalar(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  zaxisptr->scalar = 1;
  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

int
zaxisInqScalar(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->scalar;
}

/*
@Function  zaxisDefLevels
@Title     Define the levels of a Z-axis

@Prototype void zaxisDefLevels(int zaxisID, const double *levels)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  levels   All levels of the Z-axis.

@Description
The function @func{zaxisDefLevels} defines the levels of a Z-axis.

@EndFunction
*/
void
zaxisDefLevels(int zaxisID, const double *levels)
{
  if (levels)
  {
    zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
    const size_t size = (size_t) zaxisptr->size;
    xassert(size);

    if (zaxisptr->vals == NULL && size) zaxisptr->vals = (double *) Malloc(size * sizeof(double));

    double *vals = zaxisptr->vals;

    for (size_t ilev = 0; ilev < size; ++ilev) vals[ilev] = levels[ilev];

    reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
  }
}

void
zaxisDefCvals(int zaxisID, const char **cvals, int clen)
{
#ifndef USE_MPI
  if (cvals && clen)
  {
    zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
    const size_t size = (size_t) zaxisptr->size;
    xassert(size);

    zaxisptr->clength = clen;
    if (size) zaxisptr->cvals = (char **) Malloc(size * sizeof(char *));

    for (size_t ilev = 0; ilev < size; ++ilev)
    {
      zaxisptr->cvals[ilev] = (char *) Malloc(clen);
      memcpy(zaxisptr->cvals[ilev], cvals[ilev], (size_t) clen);
    }
    reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
  }
#else
  (void) zaxisID;
  (void) cvals;
  (void) clen;
  Error("This function was disabled!");
#endif
}

/*
@Function  zaxisDefLevel
@Title     Define one level of a Z-axis

@Prototype void zaxisDefLevel(int zaxisID, int levelID, double level)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  levelID  Level identifier.
    @Item  level    Level.

@Description
The function @func{zaxisDefLevel} defines one level of a Z-axis.

@EndFunction
*/
void
zaxisDefLevel(int zaxisID, int levelID, double level)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  int size = zaxisptr->size;
  xassert(size);
  xassert(levelID >= 0 && levelID < size);

  if (zaxisptr->vals == NULL && size) zaxisptr->vals = (double *) Malloc((size_t) size * sizeof(double));

  if (levelID >= 0 && levelID < size) zaxisptr->vals[levelID] = level;

  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

void
zaxisDefNlevRef(int zaxisID, int nlev)
{
  cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NLEV, nlev);
}

int
zaxisInqNlevRef(int zaxisID)
{
  int nlev = 0;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NLEV, &nlev);
  return nlev;
}

/*
@Function  zaxisDefNumber
@Title     Define the reference number for a generalized Z-axis

@Prototype void zaxisDefNumber(int zaxisID, int number)
@Parameter
    @Item  zaxisID     Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  number      Reference number for a generalized Z-axis.

@Description
The function @func{zaxisDefNumber} defines the reference number for a generalized Z-axis.

@EndFunction
*/
void
zaxisDefNumber(int zaxisID, int number)
{
  cdiDefKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NUMBEROFVGRIDUSED, number);
}

/*
@Function  zaxisInqNumber
@Title     Get the reference number to a generalized Z-axis

@Prototype int zaxisInqNumber(int zaxisID)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.

@Description
The function @func{zaxisInqNumber} returns the reference number to a generalized Z-axis.

@Result
@func{zaxisInqNumber} returns the reference number to a generalized Z-axis.
@EndFunction
*/
int
zaxisInqNumber(int zaxisID)
{
  int referenceNumber = 0;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_NUMBEROFVGRIDUSED, &referenceNumber);
  return referenceNumber;
}

/*
@Function  zaxisDefUUID
@Title     Define the UUID for a genralized Z-axis

@Prototype void zaxisDefUUID(int zaxisID, const char *uuid)
@Parameter
    @Item  zaxisID     Z-axis ID, from a previous call to @fref{zaxisCreate}.
    @Item  uuid        UUID for a generalized Z-axis.

@Description
The function @func{zaxisDefUUID} defines the UUID for a generalized  Z-axis.

@EndFunction
*/
void
zaxisDefUUID(int zaxisID, const unsigned char uuid[CDI_UUID_SIZE])
{
  cdiDefKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuid, CDI_UUID_SIZE);

  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

/*
@Function  zaxisInqUUID
@Title     Get the uuid to a generalized Z-axis

@Prototype void zaxisInqUUID(int zaxisID, char *uuid)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.
    @Item uuid A user supplied buffer of at least 16 bytes.

@Description
The function @func{zaxisInqUUID} returns the UUID to a generalized Z-axis.

@Result
@func{zaxisInqUUID} returns the UUID to a generalized Z-axis to the parameter uuid.
@EndFunction
*/
void
zaxisInqUUID(int zaxisID, unsigned char uuid[CDI_UUID_SIZE])
{
  memset(uuid, 0, CDI_UUID_SIZE);
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
}

/*
@Function  zaxisInqLevel
@Title     Get one level of a Z-axis

@Prototype double zaxisInqLevel(int zaxisID, int levelID)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.
    @Item  levelID  Level index (range: 0 to nlevel-1).

@Description
The function @func{zaxisInqLevel} returns one level of a Z-axis.

@Result
@func{zaxisInqLevel} returns the level of a Z-axis.
@EndFunction
*/
double
zaxisInqLevel(int zaxisID, int levelID)
{
  double level = 0;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->vals && levelID >= 0 && levelID < zaxisptr->size) level = zaxisptr->vals[levelID];

  return level;
}

double
zaxisInqLbound(int zaxisID, int levelID)
{
  double level = 0;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->lbounds && levelID >= 0 && levelID < zaxisptr->size) level = zaxisptr->lbounds[levelID];

  return level;
}

double
zaxisInqUbound(int zaxisID, int levelID)
{
  double level = 0;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->ubounds && levelID >= 0 && levelID < zaxisptr->size) level = zaxisptr->ubounds[levelID];

  return level;
}

const double *
zaxisInqLevelsPtr(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->vals;
}

#ifndef USE_MPI
char **
zaxisInqCValsPtr(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->cvals;
}
#endif

/*
@Function  zaxisInqLevels
@Title     Get all levels of a Z-axis

@Prototype void zaxisInqLevels(int zaxisID, double *levels)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.
    @Item  levels   Pointer to the location into which the levels are read.
                    The caller must allocate space for the returned values.

@Description
The function @func{zaxisInqLevels} returns all levels of a Z-axis.

@Result
@func{zaxisInqLevels} saves all levels to the parameter @func{levels}.
@EndFunction
*/
int
zaxisInqLevels(int zaxisID, double *levels)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  int size = 0;
  if (zaxisptr->vals)
  {
    size = zaxisptr->size;

    if (levels)
      for (int i = 0; i < size; i++) levels[i] = zaxisptr->vals[i];
  }

  return size;
}

int
zaxisInqCLen(int zaxisID)
{
  int clen = 0;
#ifndef USE_MPI
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->cvals && zaxisptr->clength) clen = zaxisptr->clength;
#else
  (void) zaxisID;
#endif

  return clen;
}

int
zaxisInqCVals(int zaxisID, char ***clevels)
{
  int size = 0;
#ifndef USE_MPI
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->cvals)
  {
    size = zaxisptr->size;
    const size_t clen = zaxisptr->clength;
    if (size && clen)
    {
      (*clevels) = (char **) Malloc(size * sizeof(char *));
      for (int i = 0; i < size; i++)
      {
        (*clevels)[i] = (char *) Malloc(clen * sizeof(char));
        memcpy((*clevels)[i], zaxisptr->cvals[i], clen * sizeof(char));
      }
    }
  }
#else
  (void) zaxisID;
  (void) clevels;
#endif

  return size;
}

int
zaxisInqLbounds(int zaxisID, double *lbounds)
{
  int size = 0;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->lbounds)
  {
    size = zaxisptr->size;

    if (lbounds)
      for (int i = 0; i < size; i++) lbounds[i] = zaxisptr->lbounds[i];
  }

  return size;
}

int
zaxisInqUbounds(int zaxisID, double *ubounds)
{
  int size = 0;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->ubounds)
  {
    size = zaxisptr->size;

    if (ubounds)
      for (int i = 0; i < size; i++) ubounds[i] = zaxisptr->ubounds[i];
  }

  return size;
}

int
zaxisInqWeights(int zaxisID, double *weights)
{
  int size = 0;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->weights)
  {
    size = zaxisptr->size;

    if (weights)
      for (int i = 0; i < size; i++) weights[i] = zaxisptr->weights[i];
  }

  return size;
}

int
zaxisInqLevelID(int zaxisID, double level)
{
  int levelID = CDI_UNDEFID;
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  if (zaxisptr->vals)
  {
    int size = zaxisptr->size;
    for (int i = 0; i < size; i++)
      if (fabs(level - zaxisptr->vals[i]) < DBL_EPSILON)
      {
        levelID = i;
        break;
      }
  }

  return levelID;
}

/*
@Function  zaxisInqType
@Title     Get the type of a Z-axis

@Prototype int zaxisInqType(int zaxisID)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.

@Description
The function @func{zaxisInqType} returns the type of a Z-axis.

@Result
@func{zaxisInqType} returns the type of the Z-axis,
one of the set of predefined CDI Z-axis types.
The valid CDI Z-axis types are @func{ZAXIS_GENERIC}, @func{ZAXIS_SURFACE},
@func{ZAXIS_HYBRID}, @func{ZAXIS_SIGMA}, @func{ZAXIS_PRESSURE}, @func{ZAXIS_HEIGHT},
@func{ZAXIS_ISENTROPIC}, @func{ZAXIS_ALTITUDE}, @func{ZAXIS_MEANSEA}, @func{ZAXIS_TOA},
@func{ZAXIS_SEA_BOTTOM}, @func{ZAXIS_ATMOSPHERE}, @func{ZAXIS_CLOUD_BASE},
@func{ZAXIS_CLOUD_TOP}, @func{ZAXIS_ISOTHERM_ZERO}, @func{ZAXIS_SNOW},
@func{ZAXIS_LAKE_BOTTOM}, @func{ZAXIS_SEDIMENT_BOTTOM}, @func{ZAXIS_SEDIMENT_BOTTOM_TA},
@func{ZAXIS_SEDIMENT_BOTTOM_TW}, @func{ZAXIS_MIX_LAYER},
@func{ZAXIS_DEPTH_BELOW_SEA} and @func{ZAXIS_DEPTH_BELOW_LAND}.

@EndFunction
*/
int
zaxisInqType(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->type;
}

/*
@Function  zaxisInqSize
@Title     Get the size of a Z-axis

@Prototype int zaxisInqSize(int zaxisID)
@Parameter
    @Item  zaxisID  Z-axis ID, from a previous call to @fref{zaxisCreate} or @fref{vlistInqVarZaxis}.

@Description
The function @func{zaxisInqSize} returns the size of a Z-axis.

@Result
@func{zaxisInqSize} returns the number of levels of a Z-axis.

@EndFunction
*/
int
zaxisInqSize(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->size;
}

void
cdiCheckZaxis(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  if (zaxisInqType(zaxisID) == ZAXIS_GENERIC && zaxisptr->vals)
  {
    int size = zaxisptr->size;
    if (size > 1)
    {
      /* check direction */
      if (!zaxisptr->direction)
      {
        int ups = 0, downs = 0;
        for (int i = 1; i < size; i++)
        {
          ups += (zaxisptr->vals[i] > zaxisptr->vals[i - 1]);
          downs += (zaxisptr->vals[i] < zaxisptr->vals[i - 1]);
        }
        if (ups == size - 1) { zaxisptr->direction = LevelUp; }
        else if (downs == size - 1) { zaxisptr->direction = LevelDown; }
        else /* !zaxisptr->direction */ { Warning("Direction undefined for zaxisID %d", zaxisID); }
      }
    }
  }
}

void
zaxisDefVct(int zaxisID, int size, const double *vct)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  if (zaxisptr->vct == 0 || zaxisptr->vctsize != size)
  {
    zaxisptr->vctsize = size;
    zaxisptr->vct = (double *) Realloc(zaxisptr->vct, (size_t) size * sizeof(double));
  }

  if (vct) memcpy(zaxisptr->vct, vct, (size_t) size * sizeof(double));
  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

void
zaxisInqVct(int zaxisID, double *vct)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  memcpy(vct, zaxisptr->vct, (size_t) zaxisptr->vctsize * sizeof(double));
}

int
zaxisInqVctSize(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->vctsize;
}

const double *
zaxisInqVctPtr(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->vct;
}

void
zaxisDefLbounds(int zaxisID, const double *lbounds)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  const size_t size = (size_t) zaxisptr->size;

  if (CDI_Debug)
    if (zaxisptr->lbounds) Warning("Lower bounds already defined for zaxisID = %d", zaxisID);

  if (zaxisptr->lbounds == NULL) zaxisptr->lbounds = (double *) Malloc(size * sizeof(double));

  if (lbounds) memcpy(zaxisptr->lbounds, lbounds, size * sizeof(double));
  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

void
zaxisDefUbounds(int zaxisID, const double *ubounds)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  const size_t size = (size_t) zaxisptr->size;

  if (CDI_Debug)
    if (zaxisptr->ubounds) Warning("Upper bounds already defined for zaxisID = %d", zaxisID);

  if (zaxisptr->ubounds == NULL) zaxisptr->ubounds = (double *) Malloc(size * sizeof(double));

  if (ubounds) memcpy(zaxisptr->ubounds, ubounds, size * sizeof(double));
  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

void
zaxisDefWeights(int zaxisID, const double *weights)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  const size_t size = (size_t) zaxisptr->size;

  if (CDI_Debug)
    if (zaxisptr->weights != NULL) Warning("Weights already defined for zaxisID = %d", zaxisID);

  if (zaxisptr->weights == NULL) zaxisptr->weights = (double *) Malloc(size * sizeof(double));

  memcpy(zaxisptr->weights, weights, size * sizeof(double));
  reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
}

void
zaxisChangeType(int zaxisID, int zaxistype)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  zaxisptr->type = zaxistype;
}

void
zaxisResize(int zaxisID, int size)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  xassert(size >= 0);

  zaxisptr->size = size;

  if (zaxisptr->vals) zaxisptr->vals = (double *) Realloc(zaxisptr->vals, (size_t) size * sizeof(double));
}

static inline void
zaxisCopyKeyStr(zaxis_t *zaxisptr1, zaxis_t *zaxisptr2, int key)
{
  cdi_key_t *keyp = find_key(&zaxisptr1->keys, key);
  if (keyp && keyp->type == KeyBytes)
    cdiDefVarKeyBytes(&zaxisptr2->keys, key, (const unsigned char *) keyp->v.s, (int) keyp->length);
}

int
zaxisDuplicate(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  int zaxistype = zaxisInqType(zaxisID);
  int zaxissize = zaxisInqSize(zaxisID);

  int zaxisIDnew = zaxisCreate(zaxistype, zaxissize);
  zaxis_t *zaxisptrnew = zaxis_to_pointer(zaxisIDnew);

  zaxis_copy(zaxisptrnew, zaxisptr);

  zaxisCopyKeyStr(zaxisptr, zaxisptrnew, CDI_KEY_NAME);
  zaxisCopyKeyStr(zaxisptr, zaxisptrnew, CDI_KEY_LONGNAME);
  zaxisCopyKeyStr(zaxisptr, zaxisptrnew, CDI_KEY_UNITS);

  if (zaxisptr->vals)
  {
    const size_t size = (size_t) zaxissize;
    zaxisptrnew->vals = (double *) Malloc(size * sizeof(double));
    memcpy(zaxisptrnew->vals, zaxisptr->vals, size * sizeof(double));
  }

  if (zaxisptr->lbounds)
  {
    const size_t size = (size_t) zaxissize;
    zaxisptrnew->lbounds = (double *) Malloc(size * sizeof(double));
    memcpy(zaxisptrnew->lbounds, zaxisptr->lbounds, size * sizeof(double));
  }

  if (zaxisptr->ubounds)
  {
    const size_t size = (size_t) zaxissize;
    zaxisptrnew->ubounds = (double *) Malloc(size * sizeof(double));
    memcpy(zaxisptrnew->ubounds, zaxisptr->ubounds, size * sizeof(double));
  }

  if (zaxisptr->vct)
  {
    const size_t size = (size_t) zaxisptr->vctsize;
    if (size)
    {
      zaxisptrnew->vctsize = (int) size;
      zaxisptrnew->vct = (double *) Malloc(size * sizeof(double));
      memcpy(zaxisptrnew->vct, zaxisptr->vct, size * sizeof(double));
    }
  }

  zaxisptrnew->atts.nelems = 0;
  cdiCopyAtts(zaxisID, CDI_GLOBAL, zaxisIDnew, CDI_GLOBAL);

  return zaxisIDnew;
}

static void
zaxisPrintKernel(zaxis_t *zaxisptr, FILE *fp)
{
  xassert(zaxisptr);

  int zaxisID = zaxisptr->self;
  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);

  int type = zaxisptr->type;
  int nlevels = zaxisptr->size;

  int dig = (datatype == CDI_DATATYPE_FLT64) ? 15 : 7;

  fprintf(fp, "zaxistype = %s\n", zaxisNamePtr(type));
  fprintf(fp, "size      = %d\n", nlevels);
  if (nlevels == 1)
  {
    const bool zscalar = (bool) zaxisptr->scalar;
    if (zscalar) fprintf(fp, "scalar    = true\n");
  }

  const char *string = cdiInqVarKeyString(&zaxisptr->keys, CDI_KEY_NAME);
  if (string[0]) fprintf(fp, "name      = %s\n", string);
  string = cdiInqVarKeyString(&zaxisptr->keys, CDI_KEY_LONGNAME);
  if (string[0]) fprintf(fp, "longname  = %s\n", string);
  string = cdiInqVarKeyString(&zaxisptr->keys, CDI_KEY_UNITS);
  if (string[0]) fprintf(fp, "units     = %s\n", string);

  if (zaxisptr->vals)
  {
    int nbyte0 = fprintf(fp, "levels    = ");
    int nbyte = nbyte0;
    for (int levelID = 0; levelID < nlevels; levelID++)
    {
      if (nbyte > 80)
      {
        fprintf(fp, "\n");
        fprintf(fp, "%*s", nbyte0, "");
        nbyte = nbyte0;
      }
      nbyte += fprintf(fp, "%.*g ", dig, zaxisptr->vals[levelID]);
    }
    fprintf(fp, "\n");
  }

  if (zaxisptr->lbounds && zaxisptr->ubounds)
  {
    int nbyte0 = fprintf(fp, "lbounds   = ");
    int nbyte = nbyte0;
    for (int levelID = 0; levelID < nlevels; levelID++)
    {
      if (nbyte > 80)
      {
        fprintf(fp, "\n");
        fprintf(fp, "%*s", nbyte0, "");
        nbyte = nbyte0;
      }
      nbyte += fprintf(fp, "%.*g ", dig, zaxisptr->lbounds[levelID]);
    }
    fprintf(fp, "\n");

    nbyte0 = fprintf(fp, "ubounds   = ");
    nbyte = nbyte0;
    for (int levelID = 0; levelID < nlevels; levelID++)
    {
      if (nbyte > 80)
      {
        fprintf(fp, "\n");
        fprintf(fp, "%*s", nbyte0, "");
        nbyte = nbyte0;
      }
      nbyte += fprintf(fp, "%.*g ", dig, zaxisptr->ubounds[levelID]);
    }
    fprintf(fp, "\n");
  }

  if (type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF)
  {
    int vctsize = zaxisptr->vctsize;
    const double *vct = zaxisptr->vct;
    fprintf(fp, "vctsize   = %d\n", vctsize);
    if (vctsize)
    {
      int nbyte0 = fprintf(fp, "vct       = ");
      int nbyte = nbyte0;
      for (int i = 0; i < vctsize; i++)
      {
        if (nbyte > 70 || i == vctsize / 2)
        {
          fprintf(fp, "\n%*s", nbyte0, "");
          nbyte = nbyte0;
        }
        nbyte += fprintf(fp, "%.15g ", vct[i]);
      }
      fprintf(fp, "\n");
    }
  }
}

static void
zaxisPrintP(void *voidptr, FILE *fp)
{
  zaxis_t *zaxisptr = (zaxis_t *) voidptr;

  xassert(zaxisptr);

  zaxisPrintKernel(zaxisptr, fp);
}

static int
zaxisCompareP(zaxis_t *z1, zaxis_t *z2)
{
  enum
  {
    differ = 1
  };
  int diff = 0;
  xassert(z1 && z2);

  diff |= (z1->type != z2->type)
          | (cdiInqVarKeyInt(&z1->keys, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE)
             != cdiInqVarKeyInt(&z2->keys, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE))
          | (cdiInqVarKeyInt(&z1->keys, CDI_KEY_DATATYPE) != cdiInqVarKeyInt(&z2->keys, CDI_KEY_DATATYPE))
          | (z1->direction != z2->direction) | (z1->size != z2->size) | (z1->vctsize != z2->vctsize)
          | (z1->positive != z2->positive);

  if (diff) return differ;

  int size = z1->size;
  int anyPresent = 0;
  int present = (z1->vals != NULL);
  diff |= (present ^ (z2->vals != NULL));
  anyPresent |= present;
  if (!diff && present)
  {
    const double *p = z1->vals, *q = z2->vals;
    for (int i = 0; i < size; i++) diff |= IS_NOT_EQUAL(p[i], q[i]);
  }

  present = (z1->lbounds != NULL);
  diff |= (present ^ (z2->lbounds != NULL));
  anyPresent |= present;
  if (!diff && present)
  {
    const double *p = z1->lbounds, *q = z2->lbounds;
    for (int i = 0; i < size; i++) diff |= IS_NOT_EQUAL(p[i], q[i]);
  }

  present = (z1->ubounds != NULL);
  diff |= (present ^ (z2->ubounds != NULL));
  anyPresent |= present;
  if (!diff && present)
  {
    const double *p = z1->ubounds, *q = z2->ubounds;
    for (int i = 0; i < size; ++i) diff |= IS_NOT_EQUAL(p[i], q[i]);
  }

  present = (z1->weights != NULL);
  diff |= (present ^ (z2->weights != NULL));
  anyPresent |= present;
  if (!diff && present)
  {
    const double *p = z1->weights, *q = z2->weights;
    for (int i = 0; i < size; ++i) diff |= IS_NOT_EQUAL(p[i], q[i]);
  }

  present = (z1->vct != NULL);
  diff |= (present ^ (z2->vct != NULL));
  if (!diff && present)
  {
    int vctsize = z1->vctsize;
    xassert(vctsize);
    const double *p = z1->vct, *q = z2->vct;
    for (int i = 0; i < vctsize; ++i) diff |= IS_NOT_EQUAL(p[i], q[i]);
  }

  if (anyPresent) xassert(size);

  diff |= strcmp(cdiInqVarKeyString(&z1->keys, CDI_KEY_NAME), cdiInqVarKeyString(&z2->keys, CDI_KEY_NAME))
          | strcmp(cdiInqVarKeyString(&z1->keys, CDI_KEY_LONGNAME), cdiInqVarKeyString(&z2->keys, CDI_KEY_LONGNAME))
          | strcmp(cdiInqVarKeyString(&z1->keys, CDI_KEY_STDNAME), cdiInqVarKeyString(&z2->keys, CDI_KEY_STDNAME))
          | strcmp(cdiInqVarKeyString(&z1->keys, CDI_KEY_UNITS), cdiInqVarKeyString(&z2->keys, CDI_KEY_UNITS));

  return diff != 0;
}

static int
zaxisTxCode(void *zaxisPtr)
{
  (void) zaxisPtr;
  return ZAXIS;
}

enum
{
  ZAXIS_PACK_INT_SELF,
  ZAXIS_PACK_INT_TYPE,
  ZAXIS_PACK_INT_SIZE,
  ZAXIS_PACK_INT_DIRECTION,
  ZAXIS_PACK_INT_VCTSIZE,
  ZAXIS_PACK_INT_MEMBERMASK,
  zaxisNint
};

enum
{
  vals = 1 << 0,
  lbounds = 1 << 1,
  ubounds = 1 << 2,
  weights = 1 << 3,
  vct = 1 << 4,
};

static int
zaxisGetMemberMask(zaxis_t *zaxisP)
{
  int memberMask = 0;

  if (zaxisP->vals) memberMask |= vals;
  if (zaxisP->lbounds) memberMask |= lbounds;
  if (zaxisP->ubounds) memberMask |= ubounds;
  if (zaxisP->weights) memberMask |= weights;
  if (zaxisP->vct) memberMask |= vct;

  return memberMask;
}

static int
zaxisGetPackSize(void *voidP, void *context)
{
  zaxis_t *zaxisP = (zaxis_t *) voidP;
  int packBufferSize = serializeGetSize(zaxisNint, CDI_DATATYPE_INT, context) + serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (zaxisP->vals || zaxisP->lbounds || zaxisP->ubounds || zaxisP->weights) xassert(zaxisP->size);

  if (zaxisP->vals)
    packBufferSize
        += serializeGetSize(zaxisP->size, CDI_DATATYPE_FLT64, context) + serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (zaxisP->lbounds)
    packBufferSize
        += serializeGetSize(zaxisP->size, CDI_DATATYPE_FLT64, context) + serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (zaxisP->ubounds)
    packBufferSize
        += serializeGetSize(zaxisP->size, CDI_DATATYPE_FLT64, context) + serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (zaxisP->weights)
    packBufferSize
        += serializeGetSize(zaxisP->size, CDI_DATATYPE_FLT64, context) + serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (zaxisP->vct)
  {
    xassert(zaxisP->vctsize);
    packBufferSize
        += serializeGetSize(zaxisP->vctsize, CDI_DATATYPE_FLT64, context) + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
  }

  packBufferSize += serializeKeysGetPackSize(&zaxisP->keys, context);

  packBufferSize += serializeGetSize(1, CDI_DATATYPE_UINT, context);

  return packBufferSize;
}

int
zaxisUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context, int force_id)
{
  int intBuffer[zaxisNint], memberMask;
  uint32_t d;

  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, intBuffer, zaxisNint, CDI_DATATYPE_INT, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);

  xassert(cdiCheckSum(CDI_DATATYPE_INT, zaxisNint, intBuffer) == d);

  zaxisInit();

  zaxis_t *zaxisP = zaxisNewEntry(force_id ? namespaceAdaptKey(intBuffer[ZAXIS_PACK_INT_SELF], originNamespace) : CDI_UNDEFID);

  zaxisP->type = intBuffer[ZAXIS_PACK_INT_TYPE];
  zaxisP->size = intBuffer[ZAXIS_PACK_INT_SIZE];
  zaxisP->direction = intBuffer[ZAXIS_PACK_INT_DIRECTION];
  zaxisP->vctsize = intBuffer[ZAXIS_PACK_INT_VCTSIZE];
  memberMask = intBuffer[ZAXIS_PACK_INT_MEMBERMASK];

  if (memberMask & vals)
  {
    int size = zaxisP->size;
    xassert(size >= 0);

    zaxisP->vals = (double *) Malloc((size_t) size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, zaxisP->vals, size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, zaxisP->vals) == d);
  }

  if (memberMask & lbounds)
  {
    int size = zaxisP->size;
    xassert(size >= 0);

    zaxisP->lbounds = (double *) Malloc((size_t) size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, zaxisP->lbounds, size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, zaxisP->lbounds) == d);
  }

  if (memberMask & ubounds)
  {
    int size = zaxisP->size;
    xassert(size >= 0);

    zaxisP->ubounds = (double *) Malloc((size_t) size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, zaxisP->ubounds, size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, zaxisP->ubounds) == d);
  }

  if (memberMask & weights)
  {
    int size = zaxisP->size;
    xassert(size >= 0);

    zaxisP->weights = (double *) Malloc((size_t) size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, zaxisP->weights, size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, zaxisP->weights) == d);
  }

  if (memberMask & vct)
  {
    int size = zaxisP->vctsize;
    xassert(size >= 0);

    zaxisP->vct = (double *) Malloc((size_t) size * sizeof(double));
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, zaxisP->vct, size, CDI_DATATYPE_FLT64, context);
    serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &d, 1, CDI_DATATYPE_UINT32, context);
    xassert(cdiCheckSum(CDI_DATATYPE_FLT64, size, zaxisP->vct) == d);
  }

  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &zaxisP->keys, context);

  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &zaxisP->positive, 1, CDI_DATATYPE_UINT, context);

  reshSetStatus(zaxisP->self, &zaxisOps, reshGetStatus(zaxisP->self, &zaxisOps) & ~RESH_SYNC_BIT);
  return zaxisP->self;
}

static void
zaxisPack(void *voidP, void *packBuffer, int packBufferSize, int *packBufferPos, void *context)
{
  zaxis_t *zaxisP = (zaxis_t *) voidP;
  int intBuffer[zaxisNint];
  int memberMask;
  uint32_t d;

  intBuffer[ZAXIS_PACK_INT_SELF] = zaxisP->self;
  intBuffer[ZAXIS_PACK_INT_TYPE] = zaxisP->type;
  intBuffer[ZAXIS_PACK_INT_SIZE] = zaxisP->size;
  intBuffer[ZAXIS_PACK_INT_DIRECTION] = zaxisP->direction;
  intBuffer[ZAXIS_PACK_INT_VCTSIZE] = zaxisP->vctsize;
  intBuffer[ZAXIS_PACK_INT_MEMBERMASK] = memberMask = zaxisGetMemberMask(zaxisP);

  serializePack(intBuffer, zaxisNint, CDI_DATATYPE_INT, packBuffer, packBufferSize, packBufferPos, context);
  d = cdiCheckSum(CDI_DATATYPE_INT, zaxisNint, intBuffer);
  serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);

  if (memberMask & vals)
  {
    xassert(zaxisP->size);
    serializePack(zaxisP->vals, zaxisP->size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, zaxisP->size, zaxisP->vals);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & lbounds)
  {
    xassert(zaxisP->size);
    serializePack(zaxisP->lbounds, zaxisP->size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, zaxisP->size, zaxisP->lbounds);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & ubounds)
  {
    xassert(zaxisP->size);

    serializePack(zaxisP->ubounds, zaxisP->size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, zaxisP->size, zaxisP->ubounds);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & weights)
  {
    xassert(zaxisP->size);

    serializePack(zaxisP->weights, zaxisP->size, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT, zaxisP->size, zaxisP->weights);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  if (memberMask & vct)
  {
    xassert(zaxisP->vctsize);

    serializePack(zaxisP->vct, zaxisP->vctsize, CDI_DATATYPE_FLT64, packBuffer, packBufferSize, packBufferPos, context);
    d = cdiCheckSum(CDI_DATATYPE_FLT64, zaxisP->vctsize, zaxisP->vct);
    serializePack(&d, 1, CDI_DATATYPE_UINT32, packBuffer, packBufferSize, packBufferPos, context);
  }

  serializeKeysPack(&zaxisP->keys, packBuffer, packBufferSize, packBufferPos, context);

  serializePack(&zaxisP->positive, 1, CDI_DATATYPE_UINT, packBuffer, packBufferSize, packBufferPos, context);
}

void
cdiZaxisGetIndexList(unsigned nzaxis, int *zaxisResHs)
{
  reshGetResHListOfType(nzaxis, zaxisResHs, &zaxisOps);
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
static const char cdi_libvers[] = "2.6.0";
const char *
cdiLibraryVersion(void)
{
  return cdi_libvers;
}
#if defined (HAVE_CF_INTERFACE)
#undef realloc
#undef malloc
#undef calloc
#undef free
#undef DOUBLE_PRECISION
/* cfortran.h  4.4 */
/* http://www-zeus.desy.de/~burow/cfortran/                   */
/* Burkhard Burow  burow@desy.de                 1990 - 2002. */

#ifndef __CFORTRAN_LOADED
#define __CFORTRAN_LOADED

// clang-format off

/* 
   THIS FILE IS PROPERTY OF BURKHARD BUROW. IF YOU ARE USING THIS FILE YOU
   SHOULD ALSO HAVE ACCESS TO CFORTRAN.DOC WHICH PROVIDES TERMS FOR USING,
   MODIFYING, COPYING AND DISTRIBUTING THE CFORTRAN.H PACKAGE.
*/

/* THIS PACKAGE, I.E. CFORTRAN.H, THIS DOCUMENT, AND THE CFORTRAN.H EXAMPLE
PROGRAMS ARE PROPERTY OF THE AUTHOR WHO RESERVES ALL RIGHTS. THIS PACKAGE AND
THE CODE IT PRODUCES MAY BE FREELY DISTRIBUTED WITHOUT FEES, SUBJECT
(AT YOUR CHOICE) EITHER TO THE GNU LIBRARY GENERAL PUBLIC LICENSE
AT http://www.gnu.org/licenses/lgpl.html OR TO THE FOLLOWING RESTRICTIONS:
- YOU MUST ACCOMPANY ANY COPIES OR DISTRIBUTION WITH THIS (UNALTERED) NOTICE.
- YOU MAY NOT RECEIVE MONEY FOR THE DISTRIBUTION OR FOR ITS MEDIA 
  (E.G. TAPE, DISK, COMPUTER, PAPER.)
- YOU MAY NOT PREVENT OTHERS FROM COPYING IT FREELY.
- YOU MAY NOT DISTRIBUTE MODIFIED VERSIONS WITHOUT CLEARLY DOCUMENTING YOUR
  CHANGES AND NOTIFYING THE AUTHOR.
- YOU MAY NOT MISREPRESENTED THE ORIGIN OF THIS SOFTWARE, EITHER BY EXPLICIT
  CLAIM OR BY OMISSION.

THE INTENT OF THE ABOVE TERMS IS TO ENSURE THAT THE CFORTRAN.H PACKAGE NOT BE
USED FOR PROFIT MAKING ACTIVITIES UNLESS SOME ROYALTY ARRANGEMENT IS ENTERED
INTO WITH ITS AUTHOR.
              
THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST
OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. THE AUTHOR IS NOT RESPONSIBLE
FOR ANY SUPPORT OR SERVICE OF THE CFORTRAN.H PACKAGE.

                                              Burkhard Burow 
                                              burow@desy.de
*/

/* The following modifications were made by the authors of CFITSIO or by me. 
 * They are flagged below with CFITSIO, the author's initials, or KMCCARTY.
 * PDW = Peter Wilson
 * DM  = Doug Mink
 * LEB = Lee E Brotzman
 * MR  = Martin Reinecke
 * WDP = William D Pence
 * BR  = Bastien ROUCARIES
 * -- Kevin McCarty, for Debian (19 Dec. 2005) */

/*******
   Modifications:
      Oct 1997: Changed symbol name extname to appendus (PDW/HSTX)
                (Conflicted with a common variable name in FTOOLS)
      Nov 1997: If g77Fortran defined, also define f2cFortran (PDW/HSTX)
      Feb 1998: Let VMS see the NUM_ELEMS code. Lets programs treat
                single strings as vectors with single elements
      Nov 1999: If macintoxh defined, also define f2cfortran (for Mac OS-X)
      Apr 2000: If WIN32 defined, also define PowerStationFortran and
                VISUAL_CPLUSPLUS (Visual C++)
      Jun 2000: If __GNUC__ and linux defined, also define f2cFortran
                (linux/gcc environment detection)
      Apr 2002: If __CYGWIN__ is defined, also define f2cFortran
      Nov 2002: If __APPLE__ defined, also define f2cfortran (for Mac OS-X)

      Nov 2003: If __INTEL_COMPILER or INTEL_COMPILER defined, also define
                f2cFortran (KMCCARTY)
      Dec 2005: If f2cFortran is defined, enforce REAL functions in FORTRAN
                returning "double" in C.  This was one of the items on
		Burkhard's TODO list. (KMCCARTY)
      Dec 2005: Modifications to support 8-byte integers. (MR)
		USE AT YOUR OWN RISK!
      Feb 2006  Added logic to typedef the symbol 'LONGLONG' to an appropriate
                intrinsic 8-byte integer datatype  (WDP)
      Apr 2006: Modifications to support gfortran (and g77 with -fno-f2c flag)
                since by default it returns "float" for FORTRAN REAL function.
                (KMCCARTY)
      May 2008: Revert commenting out of "extern" in COMMON_BLOCK_DEF macro.
		Add braces around do-nothing ";" in 3 empty while blocks to
		get rid of compiler warnings.  Thanks to ROOT developers
		Jacek Holeczek and Rene Brun for these suggestions. (KMCCARTY)
      Aug 2008: If __GNUC__ is defined and no FORTRAN compiler is specified
		via a #define or -D, default to gfortran behavior rather than
		g77 behavior. (KMCCARTY)
      Oct 2009: Add warning if guessing default fortran. Move g77 above guessing bloc
 *******/

/* 
  Avoid symbols already used by compilers and system *.h:
  __ - OSF1 zukal06 V3.0 347 alpha, cc -c -std1 cfortest.c

*/

/* 
   Determine what 8-byte integer data type is available.
  'long long' is now supported by most compilers, but older
  MS Visual C++ compilers before V7.0 use '__int64' instead. (WDP)
*/

#ifndef LONGLONG_TYPE   /* this may have been previously defined */
#if defined(_MSC_VER)   /* Microsoft Visual C++ */

#if (_MSC_VER < 1300)   /* versions earlier than V7.0 do not have 'long long' */
    typedef __int64 LONGLONG;
#else                   /* newer versions do support 'long long' */
    typedef long long LONGLONG; 
#endif

#else
    typedef long long LONGLONG; 
#endif

#define LONGLONG_TYPE
#endif  


/* First prepare for the C compiler. */

#ifndef ANSI_C_preprocessor /* i.e. user can override. */
#ifdef __CF__KnR
#define ANSI_C_preprocessor 0
#else
#ifdef __STDC__
#define ANSI_C_preprocessor 1
#else
#define _cfleft             1
#define _cfright 
#define _cfleft_cfright     0
#define ANSI_C_preprocessor _cfleft/**/_cfright
#endif
#endif
#endif

#if ANSI_C_preprocessor
#define _0(A,B)   A##B
#define  _(A,B)   _0(A,B)  /* see cat,xcat of K&R ANSI C p. 231 */
#define _2(A,B)   A##B     /* K&R ANSI C p.230: .. identifier is not replaced */
#define _3(A,B,C) _(A,_(B,C))
#else                      /* if it turns up again during rescanning.         */
#define  _(A,B)   A/**/B
#define _2(A,B)   A/**/B
#define _3(A,B,C) A/**/B/**/C
#endif

#if (defined(vax)&&defined(unix)) || (defined(__vax__)&&defined(__unix__))
#define VAXUltrix
#endif

#include <stdio.h>     /* NULL [in all machines stdio.h]                      */
#include <string.h>    /* strlen, memset, memcpy, memchr.                     */
#if !( defined(VAXUltrix) || defined(sun) || (defined(apollo)&&!defined(__STDCPP__)) )
#include <stdlib.h>    /* malloc,free                                         */
#else
#include <malloc.h>    /* Had to be removed for DomainOS h105 10.4 sys5.3 425t*/
#ifdef apollo
#define __CF__APOLLO67 /* __STDCPP__ is in Apollo 6.8 (i.e. ANSI) and onwards */
#endif
#endif

#if !defined(__GNUC__) && !defined(__sun) && (defined(sun)||defined(VAXUltrix)||defined(lynx))
#define __CF__KnR     /* Sun, LynxOS and VAX Ultrix cc only supports K&R.     */
                      /* Manually define __CF__KnR for HP if desired/required.*/
#endif                /*       i.e. We will generate Kernighan and Ritchie C. */
/* Note that you may define __CF__KnR before #include cfortran.h, in order to
generate K&R C instead of the default ANSI C. The differences are mainly in the
function prototypes and declarations. All machines, except the Apollo, work
with either style. The Apollo's argument promotion rules require ANSI or use of
the obsolete std_$call which we have not implemented here. Hence on the Apollo,
only C calling FORTRAN subroutines will work using K&R style.*/


/* Remainder of cfortran.h depends on the Fortran compiler. */

/* 11/29/2003 (KMCCARTY): add *INTEL_COMPILER symbols here */
/* 04/05/2006 (KMCCARTY): add gFortran symbol here */
#if defined(CLIPPERFortran) || defined(pgiFortran) || defined(__INTEL_COMPILER) || defined(INTEL_COMPILER) || defined(gFortran)
#define f2cFortran
#endif

#if defined(g77Fortran)                        /* 11/03/97 PDW (CFITSIO) */
#define f2cFortran
#endif

/* VAX/VMS does not let us \-split long #if lines. */ 
/* Split #if into 2 because some HP-UX can't handle long #if */
#if !(defined(NAGf90Fortran)||defined(f2cFortran)||defined(hpuxFortran)||defined(apolloFortran)||defined(sunFortran)||defined(IBMR2Fortran)||defined(CRAYFortran))
#if !(defined(mipsFortran)||defined(DECFortran)||defined(vmsFortran)||defined(CONVEXFortran)||defined(PowerStationFortran)||defined(AbsoftUNIXFortran)||defined(AbsoftProFortran)||defined(SXFortran))
/* If no Fortran compiler is given, we choose one for the machines we know.   */
#if defined(__GNUC__) || defined(WIN32) /* 10/2009 BR: warm if guess */
#warning "Please specify the fortran compiler using -D flags. Try to guess the compiler used"
#endif
#if defined(lynx) || defined(VAXUltrix)
#define f2cFortran    /* Lynx:      Only support f2c at the moment.
                         VAXUltrix: f77 behaves like f2c.
                           Support f2c or f77 with gcc, vcc with f2c. 
                           f77 with vcc works, missing link magic for f77 I/O.*/
#endif
/* 04/13/00 DM (CFITSIO): Add these lines for NT */
/*   with PowerStationFortran and and Visual C++ */
#if defined(WIN32) && !defined(__CYGWIN__)
#define PowerStationFortran   
#define VISUAL_CPLUSPLUS
#endif
#if        defined(__CYGWIN__)                 /* 04/11/02 LEB (CFITSIO) */
#define       f2cFortran 
#define	      gFortran /* 8/26/08 (KMCCARTY) */
#endif
#if        defined(__GNUC__) && defined(linux) /* 06/21/00 PDW (CFITSIO) */
#define       f2cFortran 
#define	      gFortran /* 8/26/08 (KMCCARTY) */
#endif
#if defined(macintosh)                         /* 11/1999 (CFITSIO) */
#define f2cFortran
#define	      gFortran /* 8/26/08 (KMCCARTY) */
#endif
#if defined(__APPLE__)                         /* 11/2002 (CFITSIO) */
#define f2cFortran
#define	      gFortran /* 8/26/08 (KMCCARTY) */
#endif
#if defined(__hpux)             /* 921107: Use __hpux instead of __hp9000s300 */
#define       hpuxFortran       /*         Should also allow hp9000s7/800 use.*/
#endif
#if       defined(apollo)
#define           apolloFortran /* __CF__APOLLO67 also defines some behavior. */
#endif
#if          defined(sun) || defined(__sun) 
#define              sunFortran
#endif
#if       defined(_IBMR2)
#define            IBMR2Fortran
#endif
#if        defined(_CRAY)
#define             CRAYFortran /*       _CRAYT3E also defines some behavior. */
#endif
#if        defined(_SX)
#define               SXFortran
#endif
#if        defined(__NEC__)
#define               SXFortran
#endif
#if         defined(mips) || defined(__mips)
#define             mipsFortran
#endif
#if          defined(vms) || defined(__vms)
#define              vmsFortran
#endif
#if      defined(__alpha) && defined(__unix__)
#define              DECFortran
#endif
#if   defined(__convex__)
#define           CONVEXFortran
#endif
#if   defined(VISUAL_CPLUSPLUS)
#define     PowerStationFortran
#endif
#endif /* ...Fortran */
#endif /* ...Fortran */

/* Split #if into 2 because some HP-UX can't handle long #if */
#if !(defined(NAGf90Fortran)||defined(f2cFortran)||defined(hpuxFortran)||defined(apolloFortran)||defined(sunFortran)||defined(IBMR2Fortran)||defined(CRAYFortran))
#if !(defined(mipsFortran)||defined(DECFortran)||defined(vmsFortran)||defined(CONVEXFortran)||defined(PowerStationFortran)||defined(AbsoftUNIXFortran)||defined(AbsoftProFortran)||defined(SXFortran))
/* If your compiler barfs on ' #error', replace # with the trigraph for #     */
 #error "cfortran.h:  Can't find your environment among:\
    - GNU gcc (gfortran) on Linux.                                       \
    - MIPS cc and f77 2.0. (e.g. Silicon Graphics, DECstations, ...)     \
    - IBM AIX XL C and FORTRAN Compiler/6000 Version 01.01.0000.0000     \
    - VAX   VMS CC 3.1 and FORTRAN 5.4.                                  \
    - Alpha VMS DEC C 1.3 and DEC FORTRAN 6.0.                           \
    - Alpha OSF DEC C and DEC Fortran for OSF/1 AXP Version 1.2          \
    - Apollo DomainOS 10.2 (sys5.3) with f77 10.7 and cc 6.7.            \
    - CRAY                                                               \
    - NEC SX-4 SUPER-UX                                                  \
    - CONVEX                                                             \
    - Sun                                                                \
    - PowerStation Fortran with Visual C++                               \
    - HP9000s300/s700/s800 Latest test with: HP-UX A.08.07 A 9000/730    \
    - LynxOS: cc or gcc with f2c.                                        \
    - VAXUltrix: vcc,cc or gcc with f2c. gcc or cc with f77.             \
    -            f77 with vcc works; but missing link magic for f77 I/O. \
    -            NO fort. None of gcc, cc or vcc generate required names.\
    - f2c/g77:   Use #define    f2cFortran, or cc -Df2cFortran           \
    - gfortran:  Use #define    gFortran,   or cc -DgFortran             \
                 (also necessary for g77 with -fno-f2c option)           \
    - NAG f90: Use #define NAGf90Fortran, or cc -DNAGf90Fortran          \
    - Absoft UNIX F77: Use #define AbsoftUNIXFortran or cc -DAbsoftUNIXFortran \
    - Absoft Pro Fortran: Use #define AbsoftProFortran \
    - Portland Group Fortran: Use #define pgiFortran \
    - Intel Fortran: Use #define INTEL_COMPILER"
/* Compiler must throw us out at this point! */
#endif
#endif


#if defined(VAXC) && !defined(__VAXC)
#define OLD_VAXC
#pragma nostandard                       /* Prevent %CC-I-PARAMNOTUSED.       */
#endif

/* Throughout cfortran.h we use: UN = Uppercase Name.  LN = Lowercase Name.   */

/* "extname" changed to "appendus" below (CFITSIO) */
#if defined(f2cFortran) || defined(NAGf90Fortran) || defined(DECFortran) || defined(mipsFortran) || defined(apolloFortran) || defined(sunFortran) || defined(CONVEXFortran) || defined(SXFortran) || defined(appendus)
#define CFC_(UN,LN)            _(LN,_)      /* Lowercase FORTRAN symbols.     */
#define orig_fcallsc(UN,LN)    CFC_(UN,LN)
#else 
#if defined(CRAYFortran) || defined(PowerStationFortran) || defined(AbsoftProFortran)
#ifdef _CRAY          /* (UN), not UN, circumvents CRAY preprocessor bug.     */
#define CFC_(UN,LN)            (UN)         /* Uppercase FORTRAN symbols.     */
#else                 /* At least VISUAL_CPLUSPLUS barfs on (UN), so need UN. */
#define CFC_(UN,LN)            UN           /* Uppercase FORTRAN symbols.     */
#endif
#define orig_fcallsc(UN,LN)    CFC_(UN,LN)  /* CRAY insists on arg.'s here.   */
#else  /* For following machines one may wish to change the fcallsc default.  */
#define CF_SAME_NAMESPACE
#ifdef vmsFortran
#define CFC_(UN,LN)            LN           /* Either case FORTRAN symbols.   */
     /* BUT we usually use UN for C macro to FORTRAN routines, so use LN here,*/
     /* because VAX/VMS doesn't do recursive macros.                          */
#define orig_fcallsc(UN,LN)    UN
#else      /* HP-UX without +ppu or IBMR2 without -qextname. NOT reccomended. */
#define CFC_(UN,LN)            LN           /* Lowercase FORTRAN symbols.     */
#define orig_fcallsc(UN,LN)    CFC_(UN,LN)
#endif /*  vmsFortran */
#endif /* CRAYFortran PowerStationFortran */
#endif /* ....Fortran */

#define fcallsc(UN,LN)               orig_fcallsc(UN,LN)
#define preface_fcallsc(P,p,UN,LN)   CFC_(_(P,UN),_(p,LN))
#define  append_fcallsc(P,p,UN,LN)   CFC_(_(UN,P),_(LN,p))

#define C_FUNCTION(UN,LN)            fcallsc(UN,LN)      
#define FORTRAN_FUNCTION(UN,LN)      CFC_(UN,LN)

#ifndef COMMON_BLOCK
#ifndef CONVEXFortran
#ifndef CLIPPERFortran
#if     !(defined(AbsoftUNIXFortran)||defined(AbsoftProFortran))
#define COMMON_BLOCK(UN,LN)          CFC_(UN,LN)
#else
#define COMMON_BLOCK(UN,LN)          _(_C,LN)
#endif  /* AbsoftUNIXFortran or AbsoftProFortran */
#else
#define COMMON_BLOCK(UN,LN)          _(LN,__)
#endif  /* CLIPPERFortran */
#else
#define COMMON_BLOCK(UN,LN)          _3(_,LN,_)
#endif  /* CONVEXFortran */
#endif  /* COMMON_BLOCK */

#ifndef DOUBLE_PRECISION
#if defined(CRAYFortran) && !defined(_CRAYT3E)
#define DOUBLE_PRECISION long double
#else
#define DOUBLE_PRECISION double
#endif
#endif

#ifndef FORTRAN_REAL
#if defined(CRAYFortran) &&  defined(_CRAYT3E)
#define FORTRAN_REAL double
#else
#define FORTRAN_REAL float
#endif
#endif

#ifdef CRAYFortran
#ifdef _CRAY
#include <fortran.h>
#else
#include "fortran.h"  /* i.e. if crosscompiling assume user has file. */
#endif
#define FLOATVVVVVVV_cfPP (FORTRAN_REAL *)   /* Used for C calls FORTRAN.     */
/* CRAY's double==float but CRAY says pointers to doubles and floats are diff.*/
#define VOIDP  (void *)  /* When FORTRAN calls C, we don't know if C routine 
                            arg.'s have been declared float *, or double *.   */
#else
#define FLOATVVVVVVV_cfPP
#define VOIDP
#endif

#ifdef vmsFortran
#if    defined(vms) || defined(__vms)
#include <descrip.h>
#else
#include "descrip.h"  /* i.e. if crosscompiling assume user has file. */
#endif
#endif

#ifdef sunFortran
#if defined(sun) || defined(__sun)
#include <math.h>     /* Sun's FLOATFUNCTIONTYPE, ASSIGNFLOAT, RETURNFLOAT.  */
#else
#include "math.h"     /* i.e. if crosscompiling assume user has file. */
#endif
/* At least starting with the default C compiler SC3.0.1 of SunOS 5.3,
 * FLOATFUNCTIONTYPE, ASSIGNFLOAT, RETURNFLOAT are not required and not in
 * <math.h>, since sun C no longer promotes C float return values to doubles.
 * Therefore, only use them if defined.
 * Even if gcc is being used, assume that it exhibits the Sun C compiler
 * behavior in order to be able to use *.o from the Sun C compiler.
 * i.e. If FLOATFUNCTIONTYPE, etc. are in math.h, they required by gcc.
 */
#endif

#ifndef apolloFortran
#define COMMON_BLOCK_DEF(DEFINITION, NAME) extern DEFINITION NAME
#define CF_NULL_PROTO
#else                                         /* HP doesn't understand #elif. */
/* Without ANSI prototyping, Apollo promotes float functions to double.    */
/* Note that VAX/VMS, IBM, Mips choke on 'type function(...);' prototypes. */
#define CF_NULL_PROTO ...
#ifndef __CF__APOLLO67
#define COMMON_BLOCK_DEF(DEFINITION, NAME) \
 DEFINITION NAME __attribute((__section(NAME)))
#else
#define COMMON_BLOCK_DEF(DEFINITION, NAME) \
 DEFINITION NAME #attribute[section(NAME)]
#endif
#endif

#ifdef __cplusplus
#undef  CF_NULL_PROTO
#define CF_NULL_PROTO  ...
#endif


#ifndef USE_NEW_DELETE
#ifdef __cplusplus
#define USE_NEW_DELETE 1
#else
#define USE_NEW_DELETE 0
#endif
#endif
#if USE_NEW_DELETE
#define _cf_malloc(N) new char[N]
#define _cf_free(P)   delete[] P
#else
#define _cf_malloc(N) (char *)malloc(N)
#define _cf_free(P)   free(P)
#endif

#ifdef mipsFortran
#define CF_DECLARE_GETARG         int f77argc; char **f77argv
#define CF_SET_GETARG(ARGC,ARGV)  f77argc = ARGC; f77argv = ARGV
#else
#define CF_DECLARE_GETARG
#define CF_SET_GETARG(ARGC,ARGV)
#endif

#ifdef OLD_VAXC                          /* Allow %CC-I-PARAMNOTUSED.         */
#pragma standard                         
#endif

#define AcfCOMMA ,
#define AcfCOLON ;

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

/*               UTILITIES USED WITHIN CFORTRAN.H                          */

#define _cfMIN(A,B) (A<B?A:B)

/* 970211 - XIX.145:
   firstindexlength  - better name is all_but_last_index_lengths
   secondindexlength - better name is         last_index_length
 */
#define  firstindexlength(A) (sizeof(A[0])==1 ? 1 : (sizeof(A) / sizeof(A[0])) )
#define secondindexlength(A) (sizeof(A[0])==1 ?      sizeof(A) : sizeof(A[0])  )

/* Behavior of FORTRAN LOGICAL. All machines' LOGICAL is same size as C's int.
Conversion is automatic except for arrays which require F2CLOGICALV/C2FLOGICALV.
f2c, MIPS f77 [DECstation, SGI], VAX Ultrix f77,
HP-UX f77                                        : as in C.
VAX/VMS FORTRAN, VAX Ultrix fort,
Absoft Unix Fortran, IBM RS/6000 xlf             : LS Bit = 0/1 = TRUE/FALSE.
Apollo                                           : neg.   = TRUE, else FALSE. 
[Apollo accepts -1 as TRUE for function values, but NOT all other neg. values.]
[DECFortran for Ultrix RISC is also called f77 but is the same as VAX/VMS.]   
[MIPS f77 treats .eqv./.neqv. as .eq./.ne. and hence requires LOGICAL_STRICT.]*/

#if defined(NAGf90Fortran) || defined(f2cFortran) || defined(mipsFortran) || defined(PowerStationFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran) || defined(AbsoftProFortran) || defined(SXFortran)
/* SX/PowerStationFortran have 0 and 1 defined, others are neither T nor F.   */
/* hpuxFortran800 has 0 and 0x01000000 defined. Others are unknown.           */
#define LOGICAL_STRICT      /* Other Fortran have .eqv./.neqv. == .eq./.ne.   */
#endif

#define C2FLOGICALV(A,I) \
 do {int __i; for(__i=0;__i<I;__i++) A[__i]=C2FLOGICAL(A[__i]); } while (0)
#define F2CLOGICALV(A,I) \
 do {int __i; for(__i=0;__i<I;__i++) A[__i]=F2CLOGICAL(A[__i]); } while (0)

#if defined(apolloFortran)
#define C2FLOGICAL(L) ((L)?-1:(L)&~((unsigned)1<<sizeof(int)*8-1))
#define F2CLOGICAL(L) ((L)<0?(L):0) 
#else
#if defined(CRAYFortran)
#define C2FLOGICAL(L) _btol(L)
#define F2CLOGICAL(L) _ltob(&(L))     /* Strangely _ltob() expects a pointer. */
#else
#if defined(IBMR2Fortran) || defined(vmsFortran) || defined(DECFortran) || defined(AbsoftUNIXFortran)
/* How come no AbsoftProFortran ? */
#define C2FLOGICAL(L) ((L)?(L)|1:(L)&~(int)1)
#define F2CLOGICAL(L) ((L)&1?(L):0)
#else
#if defined(CONVEXFortran)
#define C2FLOGICAL(L) ((L) ? ~0 : 0 )
#define F2CLOGICAL(L) (L)
#else   /* others evaluate LOGICALs as for C. */
#define C2FLOGICAL(L) (L)
#define F2CLOGICAL(L) (L)
#ifndef LOGICAL_STRICT
#undef  C2FLOGICALV
#undef  F2CLOGICALV
#define C2FLOGICALV(A,I)
#define F2CLOGICALV(A,I)
#endif  /* LOGICAL_STRICT                     */
#endif  /* CONVEXFortran || All Others        */
#endif  /* IBMR2Fortran vmsFortran DECFortran AbsoftUNIXFortran */
#endif  /* CRAYFortran                        */
#endif  /* apolloFortran                      */

/* 970514 - In addition to CRAY, there may be other machines
            for which LOGICAL_STRICT makes no sense. */
#if defined(LOGICAL_STRICT) && !defined(CRAYFortran)
/* Force C2FLOGICAL to generate only the values for either .TRUE. or .FALSE.
   SX/PowerStationFortran only have 0 and 1 defined.
   Elsewhere, only needed if you want to do:
     logical lvariable
     if (lvariable .eq.  .true.) then       ! (1)
   instead of
     if (lvariable .eqv. .true.) then       ! (2)
   - (1) may not even be FORTRAN/77 and that Apollo's f77 and IBM's xlf
     refuse to compile (1), so you are probably well advised to stay away from 
     (1) and from LOGICAL_STRICT.
   - You pay a (slight) performance penalty for using LOGICAL_STRICT. */
#undef  C2FLOGICAL
#ifdef hpuxFortran800
#define C2FLOGICAL(L) ((L)?0x01000000:0)
#else
#if defined(apolloFortran) || defined(vmsFortran) || defined(DECFortran)
#define C2FLOGICAL(L) ((L)?-1:0) /* These machines use -1/0 for .true./.false.*/
#else
#define C2FLOGICAL(L) ((L)? 1:0) /* All others     use +1/0 for .true./.false.*/
#endif
#endif
#endif /* LOGICAL_STRICT */

/* Convert a vector of C strings into FORTRAN strings. */
#ifndef __CF__KnR
static char *c2fstrv(char* cstr, char *fstr, int elem_len, int sizeofcstr)
#else
static char *c2fstrv(      cstr,       fstr,     elem_len,     sizeofcstr)
                     char* cstr; char *fstr; int elem_len; int sizeofcstr;
#endif
{ int i,j;
/* elem_len includes \0 for C strings. Fortran strings don't have term. \0.
   Useful size of string must be the same in both languages. */
for (i=0; i<sizeofcstr/elem_len; i++) {
  for (j=1; j<elem_len && *cstr; j++) *fstr++ = *cstr++;
  cstr += 1+elem_len-j;
  for (; j<elem_len; j++) *fstr++ = ' ';
} /* 95109 - Seems to be returning the original fstr. */
return fstr-sizeofcstr+sizeofcstr/elem_len; }

/* Convert a vector of FORTRAN strings into C strings. */
#ifndef __CF__KnR
static char *f2cstrv(char *fstr, char* cstr, int elem_len, int sizeofcstr)
#else
static char *f2cstrv(      fstr,       cstr,     elem_len,     sizeofcstr)
                     char *fstr; char* cstr; int elem_len; int sizeofcstr; 
#endif
{ int i,j;
/* elem_len includes \0 for C strings. Fortran strings don't have term. \0.
   Useful size of string must be the same in both languages. */
cstr += sizeofcstr;
fstr += sizeofcstr - sizeofcstr/elem_len;
for (i=0; i<sizeofcstr/elem_len; i++) {
  *--cstr = '\0';
  for (j=1; j<elem_len; j++) *--cstr = *--fstr;
} return cstr; }

/* kill the trailing char t's in string s. */
#ifndef __CF__KnR
static char *kill_trailing(char *s, char t)
#else
static char *kill_trailing(      s,      t) char *s; char t;
#endif
{char *e; 
e = s + strlen(s);
if (e>s) {                           /* Need this to handle NULL string.*/
  while (e>s && *--e==t) {;}         /* Don't follow t's past beginning. */
  e[*e==t?0:1] = '\0';               /* Handle s[0]=t correctly.       */
} return s; }

#ifndef __CF__KnR
static int num_elem(const char *strv, unsigned elem_len, int term_char, int num_term);
#endif
/* kill_trailingn(s,t,e) will kill the trailing t's in string s. e normally 
points to the terminating '\0' of s, but may actually point to anywhere in s.
s's new '\0' will be placed at e or earlier in order to remove any trailing t's.
If e<s string s is left unchanged. */ 
#ifndef __CF__KnR
static char *kill_trailingn(char *s, char t, char *e)
#else
static char *kill_trailingn(      s,      t,       e) char *s; char t; char *e;
#endif
{ 
if (e==s) *e = '\0';                 /* Kill the string makes sense here.*/
else if (e>s) {                      /* Watch out for neg. length string.*/
  while (e>s && *--e==t){;}          /* Don't follow t's past beginning. */
  e[*e==t?0:1] = '\0';               /* Handle s[0]=t correctly.       */
}
(void)num_elem;  /* to prevent not used warnings in gcc (added by TJ) */

 return s; }

/* Note the following assumes that any element which has t's to be chopped off,
does indeed fill the entire element. */
#ifndef __CF__KnR
static char *vkill_trailing(char* cstr, int elem_len, int sizeofcstr, char t)
#else
static char *vkill_trailing(      cstr,     elem_len,     sizeofcstr,      t)
                            char* cstr; int elem_len; int sizeofcstr; char t;
#endif
{ int i;
for (i=0; i<sizeofcstr/elem_len; i++) /* elem_len includes \0 for C strings. */
  kill_trailingn(cstr+elem_len*i,t,cstr+elem_len*(i+1)-1);
return cstr; }

#ifdef vmsFortran
typedef struct dsc$descriptor_s fstring;
#define DSC$DESCRIPTOR_A(DIMCT)  		                               \
struct {                                                                       \
  unsigned short dsc$w_length;	        unsigned char	 dsc$b_dtype;	       \
  unsigned char	 dsc$b_class;	                 char	*dsc$a_pointer;	       \
           char	 dsc$b_scale;	        unsigned char	 dsc$b_digits;         \
  struct {                                                                     \
    unsigned		       : 3;	  unsigned dsc$v_fl_binscale : 1;      \
    unsigned dsc$v_fl_redim    : 1;       unsigned dsc$v_fl_column   : 1;      \
    unsigned dsc$v_fl_coeff    : 1;       unsigned dsc$v_fl_bounds   : 1;      \
  } dsc$b_aflags;	                                                       \
  unsigned char	 dsc$b_dimct;	        unsigned long	 dsc$l_arsize;	       \
           char	*dsc$a_a0;	                 long	 dsc$l_m [DIMCT];      \
  struct {                                                                     \
    long dsc$l_l;                         long dsc$l_u;                        \
  } dsc$bounds [DIMCT];                                                        \
}
typedef DSC$DESCRIPTOR_A(1) fstringvector;
/*typedef DSC$DESCRIPTOR_A(2) fstringarrarr;
  typedef DSC$DESCRIPTOR_A(3) fstringarrarrarr;*/
#define initfstr(F,C,ELEMNO,ELEMLEN)                                           \
( (F).dsc$l_arsize=  ( (F).dsc$w_length                        =(ELEMLEN) )    \
                    *( (F).dsc$l_m[0]=(F).dsc$bounds[0].dsc$l_u=(ELEMNO)  ),   \
  (F).dsc$a_a0    =  ( (F).dsc$a_pointer=(C) ) - (F).dsc$w_length          ,(F))

#endif      /* PDW: 2/10/98 (CFITSIO) -- Let VMS see NUM_ELEMS definitions */
#define _NUM_ELEMS      -1
#define _NUM_ELEM_ARG   -2
#define NUM_ELEMS(A)    A,_NUM_ELEMS
#define NUM_ELEM_ARG(B) *_2(A,B),_NUM_ELEM_ARG
#define TERM_CHARS(A,B) A,B
#ifndef __CF__KnR
static int num_elem(const char *strv, unsigned elem_len, int term_char, int num_term)
#else
static int num_elem(      strv,          elem_len,     term_char,     num_term)
                    char *strv; unsigned elem_len; int term_char; int num_term;
#endif
/* elem_len is the number of characters in each element of strv, the FORTRAN
vector of strings. The last element of the vector must begin with at least
num_term term_char characters, so that this routine can determine how 
many elements are in the vector. */
{
unsigned num,i;
if (num_term == _NUM_ELEMS || num_term == _NUM_ELEM_ARG) 
  return term_char;
if (num_term <=0) num_term = (int)elem_len;
for (num=0; ; num++) {
  for (i=0; i<(unsigned)num_term && *strv==term_char; i++,strv++){;}
  if (i==(unsigned)num_term) break;
  else strv += elem_len-i;
}
/* to prevent not used warnings in gcc (added by ROOT, changed by TJ
 * because of unreachable warnings from clang) */
(void)c2fstrv; (void)f2cstrv; (void)kill_trailing;
(void)vkill_trailing; (void)num_elem;
return (int)num;
}
/* #endif removed 2/10/98 (CFITSIO) */

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

/*           UTILITIES FOR C TO USE STRINGS IN FORTRAN COMMON BLOCKS       */

/* C string TO Fortran Common Block STRing. */
/* DIM is the number of DIMensions of the array in terms of strings, not
   characters. e.g. char a[12] has DIM = 0, char a[12][4] has DIM = 1, etc. */
#define C2FCBSTR(CSTR,FSTR,DIM)                                                \
 c2fstrv((char *)CSTR, (char *)FSTR, sizeof(FSTR)/cfelementsof(FSTR,DIM)+1,    \
         sizeof(FSTR)+cfelementsof(FSTR,DIM))

/* Fortran Common Block string TO C STRing. */
#define FCB2CSTR(FSTR,CSTR,DIM)                                                \
 vkill_trailing(f2cstrv((char *)FSTR, (char *)CSTR,                            \
                        sizeof(FSTR)/cfelementsof(FSTR,DIM)+1,                 \
                        sizeof(FSTR)+cfelementsof(FSTR,DIM)),                  \
                sizeof(FSTR)/cfelementsof(FSTR,DIM)+1,                         \
                sizeof(FSTR)+cfelementsof(FSTR,DIM), ' ')

#define cfDEREFERENCE0
#define cfDEREFERENCE1 *
#define cfDEREFERENCE2 **
#define cfDEREFERENCE3 ***
#define cfDEREFERENCE4 ****
#define cfDEREFERENCE5 *****
#define cfelementsof(A,D) (sizeof(A)/sizeof(_(cfDEREFERENCE,D)(A)))

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

/*               UTILITIES FOR C TO CALL FORTRAN SUBROUTINES               */

/* Define lookup tables for how to handle the various types of variables.  */

#ifdef OLD_VAXC                                /* Prevent %CC-I-PARAMNOTUSED. */
#pragma nostandard
#endif

#define ZTRINGV_NUM(I)       I
#define ZTRINGV_ARGFP(I) (*(_2(A,I))) /* Undocumented. For PINT, etc. */
#define ZTRINGV_ARGF(I) _2(A,I)
#ifdef CFSUBASFUN
#define ZTRINGV_ARGS(I) ZTRINGV_ARGF(I)
#else
#define ZTRINGV_ARGS(I) _2(B,I)
#endif

#define    PBYTE_cfVP(A,B) PINT_cfVP(A,B)
#define  PDOUBLE_cfVP(A,B)
#define   PFLOAT_cfVP(A,B)
#ifdef ZTRINGV_ARGS_allows_Pvariables
/* This allows Pvariables for ARGS. ARGF machinery is above ARGFP.
 * B is not needed because the variable may be changed by the Fortran routine,
 * but because B is the only way to access an arbitrary macro argument.       */
#define     PINT_cfVP(A,B) int  B = (int)A;              /* For ZSTRINGV_ARGS */
#else
#define     PINT_cfVP(A,B)
#endif
#define PLOGICAL_cfVP(A,B) int *B;      /* Returning LOGICAL in FUNn and SUBn */
#define    PLONG_cfVP(A,B) PINT_cfVP(A,B)
#define   PSHORT_cfVP(A,B) PINT_cfVP(A,B)

#define        VCF_INT_S(T,A,B) _(T,VVVVVVV_cfTYPE) B = A;
#define        VCF_INT_F(T,A,B) _(T,_cfVCF)(A,B)
/* _cfVCF table is directly mapped to _cfCCC table. */
#define     BYTE_cfVCF(A,B)
#define   DOUBLE_cfVCF(A,B)
#if !defined(__CF__KnR)
#define    FLOAT_cfVCF(A,B)
#else
#define    FLOAT_cfVCF(A,B) FORTRAN_REAL B = A;
#endif
#define      INT_cfVCF(A,B)
#define  LOGICAL_cfVCF(A,B)
#define     LONG_cfVCF(A,B)
#define    SHORT_cfVCF(A,B)

/* 980416
   Cast (void (*)(CF_NULL_PROTO)) causes SunOS CC 4.2 occasionally to barf,
   while the following equivalent typedef is fine.
   For consistency use the typedef on all machines.
 */
typedef void (*cfCAST_FUNCTION)(CF_NULL_PROTO);

#define VCF(TN,I)       _Icf4(4,V,TN,_(A,I),_(B,I),F)
#define VVCF(TN,AI,BI)  _Icf4(4,V,TN,AI,BI,S)
#define        INT_cfV(T,A,B,F) _(VCF_INT_,F)(T,A,B)
#define       INTV_cfV(T,A,B,F)
#define      INTVV_cfV(T,A,B,F)
#define     INTVVV_cfV(T,A,B,F)
#define    INTVVVV_cfV(T,A,B,F)
#define   INTVVVVV_cfV(T,A,B,F)
#define  INTVVVVVV_cfV(T,A,B,F)
#define INTVVVVVVV_cfV(T,A,B,F)
#define PINT_cfV(      T,A,B,F) _(T,_cfVP)(A,B)
#define PVOID_cfV(     T,A,B,F)
#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
#define    ROUTINE_cfV(T,A,B,F) void (*B)(CF_NULL_PROTO) = (cfCAST_FUNCTION)A;
#else
#define    ROUTINE_cfV(T,A,B,F)
#endif
#define     SIMPLE_cfV(T,A,B,F)
#ifdef vmsFortran
#define     STRING_cfV(T,A,B,F) static struct {fstring f; unsigned clen;} B =  \
                                       {{0,DSC$K_DTYPE_T,DSC$K_CLASS_S,NULL},0};
#define    PSTRING_cfV(T,A,B,F) static fstring B={0,DSC$K_DTYPE_T,DSC$K_CLASS_S,NULL};
#define    STRINGV_cfV(T,A,B,F) static fstringvector B =                       \
  {sizeof(A),DSC$K_DTYPE_T,DSC$K_CLASS_A,NULL,0,0,{0,0,1,1,1},1,0,NULL,0,{1,0}};
#define   PSTRINGV_cfV(T,A,B,F) static fstringvector B =                       \
          {0,DSC$K_DTYPE_T,DSC$K_CLASS_A,NULL,0,0,{0,0,1,1,1},1,0,NULL,0,{1,0}};
#else
#define     STRING_cfV(T,A,B,F) struct {unsigned int clen, flen; char *nombre;} B;
#define    STRINGV_cfV(T,A,B,F) struct {char *s, *fs; unsigned flen; char *nombre;} B;
#define    PSTRING_cfV(T,A,B,F) int     B;
#define   PSTRINGV_cfV(T,A,B,F) struct{char *fs; unsigned int sizeofA,flen;}B;
#endif
#define    ZTRINGV_cfV(T,A,B,F)  STRINGV_cfV(T,A,B,F)
#define   PZTRINGV_cfV(T,A,B,F) PSTRINGV_cfV(T,A,B,F)

/* Note that the actions of the A table were performed inside the AA table.
   VAX Ultrix vcc, and HP-UX cc, didn't evaluate arguments to functions left to
   right, so we had to split the original table into the current robust two. */
#define ACF(NAME,TN,AI,I)      _(TN,_cfSTR)(4,A,NAME,I,AI,_(B,I),0)
#define   DEFAULT_cfA(M,I,A,B)
#define   LOGICAL_cfA(M,I,A,B) B=C2FLOGICAL(B);
#define  PLOGICAL_cfA(M,I,A,B) A=C2FLOGICAL(A);
#define    STRING_cfA(M,I,A,B)  STRING_cfC(M,I,A,B,sizeof(A))
#define   PSTRING_cfA(M,I,A,B) PSTRING_cfC(M,I,A,B,sizeof(A))
#ifdef vmsFortran
#define  AATRINGV_cfA(    A,B, sA,filA,silA)                                   \
 initfstr(B,_cf_malloc((sA)-(filA)),(filA),(silA)-1),                          \
          c2fstrv(A,B.dsc$a_pointer,(silA),(sA));
#define APATRINGV_cfA(    A,B, sA,filA,silA)                                   \
 initfstr(B,A,(filA),(silA)-1),c2fstrv(A,A,(silA),(sA));
#else
#define  AATRINGV_cfA(    A,B, sA,filA,silA)                                   \
     (B.s=_cf_malloc((sA)-(filA)),B.fs=c2fstrv(A,B.s,(B.flen=(silA)-1)+1,(sA)));
#define APATRINGV_cfA(    A,B, sA,filA,silA)                                   \
 B.fs=c2fstrv(A,A,(B.flen=(silA)-1)+1,B.sizeofA=(sA));
#endif
#define   STRINGV_cfA(M,I,A,B)                                                 \
    AATRINGV_cfA((char *)A,B,sizeof(A),firstindexlength(A),secondindexlength(A))
#define  PSTRINGV_cfA(M,I,A,B)                                                 \
   APATRINGV_cfA((char *)A,B,sizeof(A),firstindexlength(A),secondindexlength(A))
#define   ZTRINGV_cfA(M,I,A,B)  AATRINGV_cfA( (char *)A,B,                     \
                    (_3(M,_ELEMS_,I))*(( _3(M,_ELEMLEN_,I))+1),                \
                              (_3(M,_ELEMS_,I)),(_3(M,_ELEMLEN_,I))+1)
#define  PZTRINGV_cfA(M,I,A,B) APATRINGV_cfA( (char *)A,B,                     \
                    (_3(M,_ELEMS_,I))*(( _3(M,_ELEMLEN_,I))+1),                \
                              (_3(M,_ELEMS_,I)),(_3(M,_ELEMLEN_,I))+1)

#define    PBYTE_cfAAP(A,B) &A
#define  PDOUBLE_cfAAP(A,B) &A
#define   PFLOAT_cfAAP(A,B) FLOATVVVVVVV_cfPP &A
#define     PINT_cfAAP(A,B) &A
#define PLOGICAL_cfAAP(A,B) B= &A         /* B used to keep a common W table. */
#define    PLONG_cfAAP(A,B) &A
#define   PSHORT_cfAAP(A,B) &A

#define AACF(TN,AI,I,C) _SEP_(TN,C,cfCOMMA) _Icf(3,AA,TN,AI,_(B,I))
#define        INT_cfAA(T,A,B) &B
#define       INTV_cfAA(T,A,B) _(T,VVVVVV_cfPP) A
#define      INTVV_cfAA(T,A,B) _(T,VVVVV_cfPP)  A[0]
#define     INTVVV_cfAA(T,A,B) _(T,VVVV_cfPP)   A[0][0]
#define    INTVVVV_cfAA(T,A,B) _(T,VVV_cfPP)    A[0][0][0]
#define   INTVVVVV_cfAA(T,A,B) _(T,VV_cfPP)     A[0][0][0][0]
#define  INTVVVVVV_cfAA(T,A,B) _(T,V_cfPP)      A[0][0][0][0][0]
#define INTVVVVVVV_cfAA(T,A,B) _(T,_cfPP)       A[0][0][0][0][0][0]
#define       PINT_cfAA(T,A,B) _(T,_cfAAP)(A,B)
#define      PVOID_cfAA(T,A,B) (void *) A
#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
#define    ROUTINE_cfAA(T,A,B) &B
#else
#define    ROUTINE_cfAA(T,A,B) (cfCAST_FUNCTION)A
#endif
#define     STRING_cfAA(T,A,B)  STRING_cfCC(T,A,B)
#define    PSTRING_cfAA(T,A,B) PSTRING_cfCC(T,A,B)
#ifdef vmsFortran
#define    STRINGV_cfAA(T,A,B) &B
#else
#ifdef CRAYFortran
#define    STRINGV_cfAA(T,A,B) _cptofcd(B.fs,B.flen)
#else
#define    STRINGV_cfAA(T,A,B) B.fs
#endif
#endif
#define   PSTRINGV_cfAA(T,A,B) STRINGV_cfAA(T,A,B)
#define    ZTRINGV_cfAA(T,A,B) STRINGV_cfAA(T,A,B)
#define   PZTRINGV_cfAA(T,A,B) STRINGV_cfAA(T,A,B)

#if defined(vmsFortran) || defined(CRAYFortran)
#define JCF(TN,I)
#define KCF(TN,I)
#else
#define JCF(TN,I)    _(TN,_cfSTR)(1,J,_(B,I), 0,0,0,0)
#if defined(AbsoftUNIXFortran)
#define  DEFAULT_cfJ(B) ,0
#else
#define  DEFAULT_cfJ(B)
#endif
#define  LOGICAL_cfJ(B) DEFAULT_cfJ(B)
#define PLOGICAL_cfJ(B) DEFAULT_cfJ(B)
#define   STRING_cfJ(B) ,B.flen
#define  PSTRING_cfJ(B) ,B
#define  STRINGV_cfJ(B) STRING_cfJ(B)
#define PSTRINGV_cfJ(B) STRING_cfJ(B)
#define  ZTRINGV_cfJ(B) STRING_cfJ(B)
#define PZTRINGV_cfJ(B) STRING_cfJ(B)

/* KCF is identical to DCF, except that KCF ZTRING is not empty. */
#define KCF(TN,I)    _(TN,_cfSTR)(1,KK,_(B,I), 0,0,0,0)
#if defined(AbsoftUNIXFortran)
#define  DEFAULT_cfKK(B) , unsigned B
#else
#define  DEFAULT_cfKK(B)
#endif
#define  LOGICAL_cfKK(B) DEFAULT_cfKK(B)
#define PLOGICAL_cfKK(B) DEFAULT_cfKK(B)
#define   STRING_cfKK(B) , unsigned B
#define  PSTRING_cfKK(B) STRING_cfKK(B)
#define  STRINGV_cfKK(B) STRING_cfKK(B)
#define PSTRINGV_cfKK(B) STRING_cfKK(B)
#define  ZTRINGV_cfKK(B) STRING_cfKK(B)
#define PZTRINGV_cfKK(B) STRING_cfKK(B)
#endif

#define WCF(TN,AN,I)      _(TN,_cfSTR)(2,W,AN,_(B,I), 0,0,0)
#define  DEFAULT_cfW(A,B)
#define  LOGICAL_cfW(A,B)
#define PLOGICAL_cfW(A,B) *B=F2CLOGICAL(*B);
#define   STRING_cfW(A,B) (B.nombre=A,B.nombre[B.clen]!='\0'?B.nombre[B.clen]='\0':0); /* A?="constnt"*/
#define  PSTRING_cfW(A,B) kill_trailing(A,' ');
#ifdef vmsFortran
#define  STRINGV_cfW(A,B) _cf_free(B.dsc$a_pointer);
#define PSTRINGV_cfW(A,B)                                                      \
  vkill_trailing(f2cstrv((char*)A, (char*)A,                                   \
                           B.dsc$w_length+1, B.dsc$l_arsize+B.dsc$l_m[0]),     \
                   B.dsc$w_length+1, B.dsc$l_arsize+B.dsc$l_m[0], ' ');
#else
#define  STRINGV_cfW(A,B) _cf_free(B.s);
#define PSTRINGV_cfW(A,B) vkill_trailing(                                      \
         f2cstrv((char*)A,(char*)A,B.flen+1,B.sizeofA), B.flen+1,B.sizeofA,' ');
#endif
#define  ZTRINGV_cfW(A,B)      STRINGV_cfW(A,B)
#define PZTRINGV_cfW(A,B)     PSTRINGV_cfW(A,B)

#define   NCF(TN,I,C)       _SEP_(TN,C,cfCOMMA) _Icf(2,N,TN,_(A,I),0) 
#define  NNCF(TN,I,C)        UUCF(TN,I,C)
#define NNNCF(TN,I,C)       _SEP_(TN,C,cfCOLON) _Icf(2,N,TN,_(A,I),0) 
#define        INT_cfN(T,A) _(T,VVVVVVV_cfTYPE) * A
#define       INTV_cfN(T,A) _(T,VVVVVV_cfTYPE)  * A
#define      INTVV_cfN(T,A) _(T,VVVVV_cfTYPE)   * A
#define     INTVVV_cfN(T,A) _(T,VVVV_cfTYPE)    * A
#define    INTVVVV_cfN(T,A) _(T,VVV_cfTYPE)     * A
#define   INTVVVVV_cfN(T,A) _(T,VV_cfTYPE)      * A
#define  INTVVVVVV_cfN(T,A) _(T,V_cfTYPE)       * A
#define INTVVVVVVV_cfN(T,A) _(T,_cfTYPE)        * A
#define       PINT_cfN(T,A) _(T,_cfTYPE)        * A
#define      PVOID_cfN(T,A) void *                A
#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
#define    ROUTINE_cfN(T,A) void (**A)(CF_NULL_PROTO)
#else
#define    ROUTINE_cfN(T,A) void ( *A)(CF_NULL_PROTO)
#endif
#ifdef vmsFortran
#define     STRING_cfN(T,A) fstring *             A
#define    STRINGV_cfN(T,A) fstringvector *       A
#else
#ifdef CRAYFortran
#define     STRING_cfN(T,A) _fcd                  A
#define    STRINGV_cfN(T,A) _fcd                  A
#else
#define     STRING_cfN(T,A) char *                A
#define    STRINGV_cfN(T,A) char *                A
#endif
#endif
#define    PSTRING_cfN(T,A)   STRING_cfN(T,A) /* CRAY insists on arg.'s here. */
#define   PNSTRING_cfN(T,A)   STRING_cfN(T,A) /* CRAY insists on arg.'s here. */
#define   PPSTRING_cfN(T,A)   STRING_cfN(T,A) /* CRAY insists on arg.'s here. */
#define   PSTRINGV_cfN(T,A)  STRINGV_cfN(T,A)
#define    ZTRINGV_cfN(T,A)  STRINGV_cfN(T,A)
#define   PZTRINGV_cfN(T,A) PSTRINGV_cfN(T,A)


/* Apollo 6.7, CRAY, old Sun, VAX/Ultrix vcc/cc and new ultrix
   can't hack more than 31 arg's.
   e.g. ultrix >= 4.3 gives message:
       zow35> cc -c -DDECFortran cfortest.c
       cfe: Fatal: Out of memory: cfortest.c
       zow35>
   Old __hpux had the problem, but new 'HP-UX A.09.03 A 9000/735' is fine
   if using -Aa, otherwise we have a problem.
 */
#ifndef MAX_PREPRO_ARGS
#if !defined(__GNUC__) && (defined(VAXUltrix) || defined(__CF__APOLLO67) || (defined(sun)&&!defined(__sun)) || defined(_CRAY) || defined(__ultrix__) || (defined(__hpux)&&defined(__CF__KnR)))
#define MAX_PREPRO_ARGS 31
#else
#define MAX_PREPRO_ARGS 99
#endif
#endif

#if defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
/* In addition to explicit Absoft stuff, only Absoft requires:
   - DEFAULT coming from _cfSTR.
     DEFAULT could have been called e.g. INT, but keep it for clarity.
   - M term in CFARGT14 and CFARGT14FS.
 */
#define ABSOFT_cf1(T0) _(T0,_cfSTR)(0,ABSOFT1,0,0,0,0,0)
#define ABSOFT_cf2(T0) _(T0,_cfSTR)(0,ABSOFT2,0,0,0,0,0)
#define ABSOFT_cf3(T0) _(T0,_cfSTR)(0,ABSOFT3,0,0,0,0,0)
#define DEFAULT_cfABSOFT1
#define LOGICAL_cfABSOFT1
#define  STRING_cfABSOFT1 ,MAX_LEN_FORTRAN_FUNCTION_STRING
#define DEFAULT_cfABSOFT2
#define LOGICAL_cfABSOFT2
#define  STRING_cfABSOFT2 ,unsigned D0
#define DEFAULT_cfABSOFT3
#define LOGICAL_cfABSOFT3
#define  STRING_cfABSOFT3 ,D0
#else
#define ABSOFT_cf1(T0)
#define ABSOFT_cf2(T0)
#define ABSOFT_cf3(T0)
#endif

/* _Z introduced to cicumvent IBM and HP silly preprocessor warning.
   e.g. "Macro CFARGT14 invoked with a null argument."
 */
#define _Z

#define  CFARGT14S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)                \
 S(T1,1)   S(T2,2)   S(T3,3)    S(T4,4)    S(T5,5)    S(T6,6)    S(T7,7)       \
 S(T8,8)   S(T9,9)   S(TA,10)   S(TB,11)   S(TC,12)   S(TD,13)   S(TE,14)
#define  CFARGT27S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
 S(T1,1)   S(T2,2)   S(T3,3)    S(T4,4)    S(T5,5)    S(T6,6)    S(T7,7)       \
 S(T8,8)   S(T9,9)   S(TA,10)   S(TB,11)   S(TC,12)   S(TD,13)   S(TE,14)      \
 S(TF,15)  S(TG,16)  S(TH,17)   S(TI,18)   S(TJ,19)   S(TK,20)   S(TL,21)      \
 S(TM,22)  S(TN,23)  S(TO,24)   S(TP,25)   S(TQ,26)   S(TR,27)

#define  CFARGT14FS(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)           \
 F(T1,1,0) F(T2,2,1) F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)     \
 F(T8,8,1) F(T9,9,1) F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)    \
 M       CFARGT14S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
#define  CFARGT27FS(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
 F(T1,1,0)  F(T2,2,1)  F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)   \
 F(T8,8,1)  F(T9,9,1)  F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)  \
 F(TF,15,1) F(TG,16,1) F(TH,17,1) F(TI,18,1) F(TJ,19,1) F(TK,20,1) F(TL,21,1)  \
 F(TM,22,1) F(TN,23,1) F(TO,24,1) F(TP,25,1) F(TQ,26,1) F(TR,27,1)             \
 M       CFARGT27S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)

#if !(defined(PowerStationFortran)||defined(hpuxFortran800))
/*  Old CFARGT14 -> CFARGT14FS as seen below, for Absoft cross-compile yields:
      SunOS> cc -c -Xa -DAbsoftUNIXFortran c.c
      "c.c", line 406: warning: argument mismatch
    Haven't checked if this is ANSI C or a SunOS bug. SunOS -Xs works ok.
    Behavior is most clearly seen in example:
      #define A 1 , 2
      #define  C(X,Y,Z) x=X. y=Y. z=Z.
      #define  D(X,Y,Z) C(X,Y,Z)
      D(x,A,z)
    Output from preprocessor is: x = x . y = 1 . z = 2 .
 #define CFARGT14(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
       CFARGT14FS(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
*/
#define  CFARGT14(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)             \
 F(T1,1,0) F(T2,2,1) F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)     \
 F(T8,8,1) F(T9,9,1) F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)    \
 M       CFARGT14S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
#define  CFARGT27(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
 F(T1,1,0)  F(T2,2,1)  F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)   \
 F(T8,8,1)  F(T9,9,1)  F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)  \
 F(TF,15,1) F(TG,16,1) F(TH,17,1) F(TI,18,1) F(TJ,19,1) F(TK,20,1) F(TL,21,1)  \
 F(TM,22,1) F(TN,23,1) F(TO,24,1) F(TP,25,1) F(TQ,26,1) F(TR,27,1)             \
 M       CFARGT27S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)

#define  CFARGT20(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
 F(T1,1,0)  F(T2,2,1)  F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)   \
 F(T8,8,1)  F(T9,9,1)  F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)  \
 F(TF,15,1) F(TG,16,1) F(TH,17,1) F(TI,18,1) F(TJ,19,1) F(TK,20,1)             \
 S(T1,1)    S(T2,2)    S(T3,3)    S(T4,4)    S(T5,5)    S(T6,6)    S(T7,7)     \
 S(T8,8)    S(T9,9)    S(TA,10)   S(TB,11)   S(TC,12)   S(TD,13)   S(TE,14)    \
 S(TF,15)   S(TG,16)   S(TH,17)   S(TI,18)   S(TJ,19)   S(TK,20)
#define CFARGTA14(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE) \
 F(T1,A1,1,0)  F(T2,A2,2,1)  F(T3,A3,3,1) F(T4,A4,4,1)  F(T5,A5,5,1)  F(T6,A6,6,1)  \
 F(T7,A7,7,1)  F(T8,A8,8,1)  F(T9,A9,9,1) F(TA,AA,10,1) F(TB,AB,11,1) F(TC,AC,12,1) \
 F(TD,AD,13,1) F(TE,AE,14,1) S(T1,1)      S(T2,2)       S(T3,3)       S(T4,4)       \
 S(T5,5)       S(T6,6)       S(T7,7)      S(T8,8)       S(T9,9)       S(TA,10)      \
 S(TB,11)      S(TC,12)      S(TD,13)     S(TE,14)
#if MAX_PREPRO_ARGS>31
#define CFARGTA20(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
 F(T1,A1,1,0)  F(T2,A2,2,1)  F(T3,A3,3,1)  F(T4,A4,4,1)  F(T5,A5,5,1)  F(T6,A6,6,1)  \
 F(T7,A7,7,1)  F(T8,A8,8,1)  F(T9,A9,9,1)  F(TA,AA,10,1) F(TB,AB,11,1) F(TC,AC,12,1) \
 F(TD,AD,13,1) F(TE,AE,14,1) F(TF,AF,15,1) F(TG,AG,16,1) F(TH,AH,17,1) F(TI,AI,18,1) \
 F(TJ,AJ,19,1) F(TK,AK,20,1) S(T1,1)       S(T2,2)       S(T3,3)       S(T4,4)       \
 S(T5,5)       S(T6,6)       S(T7,7)       S(T8,8)       S(T9,9)       S(TA,10)      \
 S(TB,11)      S(TC,12)      S(TD,13)      S(TE,14)      S(TF,15)      S(TG,16)      \
 S(TH,17)      S(TI,18)      S(TJ,19)      S(TK,20)
#define CFARGTA27(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
 F(T1,A1,1,0)  F(T2,A2,2,1)  F(T3,A3,3,1)  F(T4,A4,4,1)  F(T5,A5,5,1)  F(T6,A6,6,1)  \
 F(T7,A7,7,1)  F(T8,A8,8,1)  F(T9,A9,9,1)  F(TA,AA,10,1) F(TB,AB,11,1) F(TC,AC,12,1) \
 F(TD,AD,13,1) F(TE,AE,14,1) F(TF,AF,15,1) F(TG,AG,16,1) F(TH,AH,17,1) F(TI,AI,18,1) \
 F(TJ,AJ,19,1) F(TK,AK,20,1) F(TL,AL,21,1) F(TM,AM,22,1) F(TN,AN,23,1) F(TO,AO,24,1) \
 F(TP,AP,25,1) F(TQ,AQ,26,1) F(TR,AR,27,1) S(T1,1)       S(T2,2)       S(T3,3)       \
 S(T4,4)       S(T5,5)       S(T6,6)       S(T7,7)       S(T8,8)       S(T9,9)       \
 S(TA,10)      S(TB,11)      S(TC,12)      S(TD,13)      S(TE,14)      S(TF,15)      \
 S(TG,16)      S(TH,17)      S(TI,18)      S(TJ,19)      S(TK,20)      S(TL,21)      \
 S(TM,22)      S(TN,23)      S(TO,24)      S(TP,25)      S(TQ,26)      S(TR,27)
#endif
#else
#define  CFARGT14(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)             \
 F(T1,1,0) S(T1,1) F(T2,2,1)  S(T2,2)  F(T3,3,1)  S(T3,3)  F(T4,4,1)  S(T4,4)  \
 F(T5,5,1) S(T5,5) F(T6,6,1)  S(T6,6)  F(T7,7,1)  S(T7,7)  F(T8,8,1)  S(T8,8)  \
 F(T9,9,1) S(T9,9) F(TA,10,1) S(TA,10) F(TB,11,1) S(TB,11) F(TC,12,1) S(TC,12) \
 F(TD,13,1) S(TD,13) F(TE,14,1) S(TE,14)
#define  CFARGT27(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
 F(T1,1,0)  S(T1,1)  F(T2,2,1)  S(T2,2)  F(T3,3,1)  S(T3,3)  F(T4,4,1)  S(T4,4)  \
 F(T5,5,1)  S(T5,5)  F(T6,6,1)  S(T6,6)  F(T7,7,1)  S(T7,7)  F(T8,8,1)  S(T8,8)  \
 F(T9,9,1)  S(T9,9)  F(TA,10,1) S(TA,10) F(TB,11,1) S(TB,11) F(TC,12,1) S(TC,12) \
 F(TD,13,1) S(TD,13) F(TE,14,1) S(TE,14) F(TF,15,1) S(TF,15) F(TG,16,1) S(TG,16) \
 F(TH,17,1) S(TH,17) F(TI,18,1) S(TI,18) F(TJ,19,1) S(TJ,19) F(TK,20,1) S(TK,20) \
 F(TL,21,1) S(TL,21) F(TM,22,1) S(TM,22) F(TN,23,1) S(TN,23) F(TO,24,1) S(TO,24) \
 F(TP,25,1) S(TP,25) F(TQ,26,1) S(TQ,26) F(TR,27,1) S(TR,27)

#define  CFARGT20(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
 F(T1,1,0)  S(T1,1)  F(T2,2,1)  S(T2,2)  F(T3,3,1)  S(T3,3)  F(T4,4,1)  S(T4,4)  \
 F(T5,5,1)  S(T5,5)  F(T6,6,1)  S(T6,6)  F(T7,7,1)  S(T7,7)  F(T8,8,1)  S(T8,8)  \
 F(T9,9,1)  S(T9,9)  F(TA,10,1) S(TA,10) F(TB,11,1) S(TB,11) F(TC,12,1) S(TC,12) \
 F(TD,13,1) S(TD,13) F(TE,14,1) S(TE,14) F(TF,15,1) S(TF,15) F(TG,16,1) S(TG,16) \
 F(TH,17,1) S(TH,17) F(TI,18,1) S(TI,18) F(TJ,19,1) S(TJ,19) F(TK,20,1) S(TK,20)
#define CFARGTA14(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE) \
 F(T1,A1,1,0)  S(T1,1)  F(T2,A2,2,1)  S(T2,2)  F(T3,A3,3,1)  S(T3,3)           \
 F(T4,A4,4,1)  S(T4,4)  F(T5,A5,5,1)  S(T5,5)  F(T6,A6,6,1)  S(T6,6)           \
 F(T7,A7,7,1)  S(T7,7)  F(T8,A8,8,1)  S(T8,8)  F(T9,A9,9,1)  S(T9,9)           \
 F(TA,AA,10,1) S(TA,10) F(TB,AB,11,1) S(TB,11) F(TC,AC,12,1) S(TC,12)          \
 F(TD,AD,13,1) S(TD,13) F(TE,AE,14,1) S(TE,14)
#if MAX_PREPRO_ARGS>31
#define CFARGTA20(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
 F(T1,A1,1,0)  S(T1,1)  F(T2,A2,2,1)  S(T2,2)  F(T3,A3,3,1)  S(T3,3)           \
 F(T4,A4,4,1)  S(T4,4)  F(T5,A5,5,1)  S(T5,5)  F(T6,A6,6,1)  S(T6,6)           \
 F(T7,A7,7,1)  S(T7,7)  F(T8,A8,8,1)  S(T8,8)  F(T9,A9,9,1)  S(T9,9)           \
 F(TA,AA,10,1) S(TA,10) F(TB,AB,11,1) S(TB,11) F(TC,AC,12,1) S(TC,12)          \
 F(TD,AD,13,1) S(TD,13) F(TE,AE,14,1) S(TE,14) F(TF,AF,15,1) S(TF,15)          \
 F(TG,AG,16,1) S(TG,16) F(TH,AH,17,1) S(TH,17) F(TI,AI,18,1) S(TI,18)          \
 F(TJ,AJ,19,1) S(TJ,19) F(TK,AK,20,1) S(TK,20)                
#define CFARGTA27(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
 F(T1,A1,1,0)  S(T1,1)  F(T2,A2,2,1)  S(T2,2)  F(T3,A3,3,1)  S(T3,3)           \
 F(T4,A4,4,1)  S(T4,4)  F(T5,A5,5,1)  S(T5,5)  F(T6,A6,6,1)  S(T6,6)           \
 F(T7,A7,7,1)  S(T7,7)  F(T8,A8,8,1)  S(T8,8)  F(T9,A9,9,1)  S(T9,9)           \
 F(TA,AA,10,1) S(TA,10) F(TB,AB,11,1) S(TB,11) F(TC,AC,12,1) S(TC,12)          \
 F(TD,AD,13,1) S(TD,13) F(TE,AE,14,1) S(TE,14) F(TF,AF,15,1) S(TF,15)          \
 F(TG,AG,16,1) S(TG,16) F(TH,AH,17,1) S(TH,17) F(TI,AI,18,1) S(TI,18)          \
 F(TJ,AJ,19,1) S(TJ,19) F(TK,AK,20,1) S(TK,20) F(TL,AL,21,1) S(TL,21)          \
 F(TM,AM,22,1) S(TM,22) F(TN,AN,23,1) S(TN,23) F(TO,AO,24,1) S(TO,24)          \
 F(TP,AP,25,1) S(TP,25) F(TQ,AQ,26,1) S(TQ,26) F(TR,AR,27,1) S(TR,27)
#endif
#endif


#define PROTOCCALLSFSUB1( UN,LN,T1) \
        PROTOCCALLSFSUB14(UN,LN,T1,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB2( UN,LN,T1,T2) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB3( UN,LN,T1,T2,T3) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB4( UN,LN,T1,T2,T3,T4) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB5( UN,LN,T1,T2,T3,T4,T5) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB6( UN,LN,T1,T2,T3,T4,T5,T6) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB7( UN,LN,T1,T2,T3,T4,T5,T6,T7) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB8( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB9( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB11(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB12(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0)
#define PROTOCCALLSFSUB13(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD) \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0)


#define PROTOCCALLSFSUB15(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF) \
        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB16(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG) \
        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB17(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH) \
        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB18(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI) \
        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,CF_0,CF_0)
#define PROTOCCALLSFSUB19(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ) \
        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,CF_0)

#define PROTOCCALLSFSUB21(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB22(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB23(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB24(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,CF_0,CF_0,CF_0)
#define PROTOCCALLSFSUB25(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,CF_0,CF_0)
#define PROTOCCALLSFSUB26(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,CF_0)


#ifndef FCALLSC_QUALIFIER
#ifdef VISUAL_CPLUSPLUS
#define FCALLSC_QUALIFIER __stdcall
#else
#define FCALLSC_QUALIFIER
#endif
#endif

#ifdef __cplusplus
#define CFextern extern "C"
#else
#define CFextern extern
#endif


#ifdef CFSUBASFUN
#define PROTOCCALLSFSUB0(UN,LN) \
   PROTOCCALLSFFUN0( VOID,UN,LN)
#define PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
   PROTOCCALLSFFUN14(VOID,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
#define PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)\
   PROTOCCALLSFFUN20(VOID,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
#define PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)\
   PROTOCCALLSFFUN27(VOID,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
#else
/* Note: Prevent compiler warnings, null #define PROTOCCALLSFSUB14/20 after 
   #include-ing cfortran.h if calling the FORTRAN wrapper within the same 
   source code where the wrapper is created. */
#define PROTOCCALLSFSUB0(UN,LN)     _(VOID,_cfPU)(CFC_(UN,LN))();
#ifndef __CF__KnR
#define PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
 _(VOID,_cfPU)(CFC_(UN,LN))( CFARGT14(NCF,KCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) );
#define PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)\
 _(VOID,_cfPU)(CFC_(UN,LN))( CFARGT20(NCF,KCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) );
#define PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)\
 _(VOID,_cfPU)(CFC_(UN,LN))( CFARGT27(NCF,KCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) );
#else
#define PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)     \
         PROTOCCALLSFSUB0(UN,LN)
#define PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
         PROTOCCALLSFSUB0(UN,LN)
#define PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
         PROTOCCALLSFSUB0(UN,LN)
#endif
#endif


#ifdef OLD_VAXC                                  /* Allow %CC-I-PARAMNOTUSED. */
#pragma standard
#endif


#define CCALLSFSUB1( UN,LN,T1,                        A1)         \
        CCALLSFSUB5 (UN,LN,T1,CF_0,CF_0,CF_0,CF_0,A1,0,0,0,0)
#define CCALLSFSUB2( UN,LN,T1,T2,                     A1,A2)      \
        CCALLSFSUB5 (UN,LN,T1,T2,CF_0,CF_0,CF_0,A1,A2,0,0,0)
#define CCALLSFSUB3( UN,LN,T1,T2,T3,                  A1,A2,A3)   \
        CCALLSFSUB5 (UN,LN,T1,T2,T3,CF_0,CF_0,A1,A2,A3,0,0)
#define CCALLSFSUB4( UN,LN,T1,T2,T3,T4,               A1,A2,A3,A4)\
        CCALLSFSUB5 (UN,LN,T1,T2,T3,T4,CF_0,A1,A2,A3,A4,0)
#define CCALLSFSUB5( UN,LN,T1,T2,T3,T4,T5,            A1,A2,A3,A4,A5)          \
        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,0,0,0,0,0)
#define CCALLSFSUB6( UN,LN,T1,T2,T3,T4,T5,T6,         A1,A2,A3,A4,A5,A6)       \
        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,0,0,0,0)
#define CCALLSFSUB7( UN,LN,T1,T2,T3,T4,T5,T6,T7,      A1,A2,A3,A4,A5,A6,A7)    \
        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,0,0,0)
#define CCALLSFSUB8( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,   A1,A2,A3,A4,A5,A6,A7,A8) \
        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,0,0)
#define CCALLSFSUB9( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,A1,A2,A3,A4,A5,A6,A7,A8,A9)\
        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,0)
#define CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA)\
        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,0,0,0,0)
#define CCALLSFSUB11(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB)\
        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,0,0,0)
#define CCALLSFSUB12(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC)\
        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,0,0)
#define CCALLSFSUB13(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD)\
        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,0)

#ifdef __cplusplus
#define CPPPROTOCLSFSUB0( UN,LN)
#define CPPPROTOCLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
#define CPPPROTOCLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
#define CPPPROTOCLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
#else
#define CPPPROTOCLSFSUB0(UN,LN) \
        PROTOCCALLSFSUB0(UN,LN)
#define CPPPROTOCLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)     \
        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
#define CPPPROTOCLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
#define CPPPROTOCLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
#endif

#ifdef CFSUBASFUN
#define CCALLSFSUB0(UN,LN) CCALLSFFUN0(UN,LN)
#define CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)\
        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)
#else
/* do{...}while(0) allows if(a==b) FORT(); else BORT(); */
#define CCALLSFSUB0( UN,LN) do{CPPPROTOCLSFSUB0(UN,LN) CFC_(UN,LN)();}while(0)
#define CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)\
do{VVCF(T1,A1,B1) VVCF(T2,A2,B2) VVCF(T3,A3,B3) VVCF(T4,A4,B4) VVCF(T5,A5,B5)  \
   VVCF(T6,A6,B6) VVCF(T7,A7,B7) VVCF(T8,A8,B8) VVCF(T9,A9,B9) VVCF(TA,AA,B10) \
   VVCF(TB,AB,B11) VVCF(TC,AC,B12) VVCF(TD,AD,B13) VVCF(TE,AE,B14)             \
   CPPPROTOCLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)          \
   ACF(LN,T1,A1,1)  ACF(LN,T2,A2,2)  ACF(LN,T3,A3,3)                           \
   ACF(LN,T4,A4,4)  ACF(LN,T5,A5,5)  ACF(LN,T6,A6,6)  ACF(LN,T7,A7,7)          \
   ACF(LN,T8,A8,8)  ACF(LN,T9,A9,9)  ACF(LN,TA,AA,10) ACF(LN,TB,AB,11)         \
   ACF(LN,TC,AC,12) ACF(LN,TD,AD,13) ACF(LN,TE,AE,14)                          \
   CFC_(UN,LN)( CFARGTA14(AACF,JCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE) );\
   WCF(T1,A1,1)  WCF(T2,A2,2)  WCF(T3,A3,3)  WCF(T4,A4,4)  WCF(T5,A5,5)        \
   WCF(T6,A6,6)  WCF(T7,A7,7)  WCF(T8,A8,8)  WCF(T9,A9,9)  WCF(TA,AA,10)       \
   WCF(TB,AB,11) WCF(TC,AC,12) WCF(TD,AD,13) WCF(TE,AE,14)      }while(0)
#endif


#if MAX_PREPRO_ARGS>31
#define CCALLSFSUB15(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF)\
        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,0,0,0,0,0)
#define CCALLSFSUB16(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG)\
        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,0,0,0,0)
#define CCALLSFSUB17(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH)\
        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,0,0,0)
#define CCALLSFSUB18(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI)\
        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,0,0)
#define CCALLSFSUB19(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ)\
        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,0)

#ifdef CFSUBASFUN
#define CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH, \
        TI,TJ,TK, A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
        CCALLSFFUN20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH, \
        TI,TJ,TK, A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK)
#else
#define CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH, \
        TI,TJ,TK, A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
do{VVCF(T1,A1,B1)  VVCF(T2,A2,B2)  VVCF(T3,A3,B3)  VVCF(T4,A4,B4)  VVCF(T5,A5,B5)   \
   VVCF(T6,A6,B6)  VVCF(T7,A7,B7)  VVCF(T8,A8,B8)  VVCF(T9,A9,B9)  VVCF(TA,AA,B10)  \
   VVCF(TB,AB,B11) VVCF(TC,AC,B12) VVCF(TD,AD,B13) VVCF(TE,AE,B14) VVCF(TF,AF,B15)  \
   VVCF(TG,AG,B16) VVCF(TH,AH,B17) VVCF(TI,AI,B18) VVCF(TJ,AJ,B19) VVCF(TK,AK,B20)  \
   CPPPROTOCLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)  \
   ACF(LN,T1,A1,1)  ACF(LN,T2,A2,2)  ACF(LN,T3,A3,3)  ACF(LN,T4,A4,4)          \
   ACF(LN,T5,A5,5)  ACF(LN,T6,A6,6)  ACF(LN,T7,A7,7)  ACF(LN,T8,A8,8)          \
   ACF(LN,T9,A9,9)  ACF(LN,TA,AA,10) ACF(LN,TB,AB,11) ACF(LN,TC,AC,12)         \
   ACF(LN,TD,AD,13) ACF(LN,TE,AE,14) ACF(LN,TF,AF,15) ACF(LN,TG,AG,16)         \
   ACF(LN,TH,AH,17) ACF(LN,TI,AI,18) ACF(LN,TJ,AJ,19) ACF(LN,TK,AK,20)         \
   CFC_(UN,LN)( CFARGTA20(AACF,JCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) ); \
 WCF(T1,A1,1)  WCF(T2,A2,2)  WCF(T3,A3,3)  WCF(T4,A4,4)  WCF(T5,A5,5)  WCF(T6,A6,6)  \
 WCF(T7,A7,7)  WCF(T8,A8,8)  WCF(T9,A9,9)  WCF(TA,AA,10) WCF(TB,AB,11) WCF(TC,AC,12) \
 WCF(TD,AD,13) WCF(TE,AE,14) WCF(TF,AF,15) WCF(TG,AG,16) WCF(TH,AH,17) WCF(TI,AI,18) \
 WCF(TJ,AJ,19) WCF(TK,AK,20) }while(0)
#endif
#endif         /* MAX_PREPRO_ARGS */

#if MAX_PREPRO_ARGS>31
#define CCALLSFSUB21(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL)\
        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,0,0,0,0,0,0)
#define CCALLSFSUB22(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM)\
        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,0,0,0,0,0)
#define CCALLSFSUB23(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN)\
        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,0,0,0,0)
#define CCALLSFSUB24(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO)\
        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,0,0,0)
#define CCALLSFSUB25(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP)\
        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,0,0)
#define CCALLSFSUB26(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ)\
        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,0)

#ifdef CFSUBASFUN
#define CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR, \
                           A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
        CCALLSFFUN27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR, \
                           A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR)
#else
#define CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR, \
                           A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
do{VVCF(T1,A1,B1)  VVCF(T2,A2,B2)  VVCF(T3,A3,B3)  VVCF(T4,A4,B4)  VVCF(T5,A5,B5)   \
   VVCF(T6,A6,B6)  VVCF(T7,A7,B7)  VVCF(T8,A8,B8)  VVCF(T9,A9,B9)  VVCF(TA,AA,B10)  \
   VVCF(TB,AB,B11) VVCF(TC,AC,B12) VVCF(TD,AD,B13) VVCF(TE,AE,B14) VVCF(TF,AF,B15)  \
   VVCF(TG,AG,B16) VVCF(TH,AH,B17) VVCF(TI,AI,B18) VVCF(TJ,AJ,B19) VVCF(TK,AK,B20)  \
   VVCF(TL,AL,B21) VVCF(TM,AM,B22) VVCF(TN,AN,B23) VVCF(TO,AO,B24) VVCF(TP,AP,B25)  \
   VVCF(TQ,AQ,B26) VVCF(TR,AR,B27)                                                  \
   CPPPROTOCLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
   ACF(LN,T1,A1,1)  ACF(LN,T2,A2,2)  ACF(LN,T3,A3,3)  ACF(LN,T4,A4,4)          \
   ACF(LN,T5,A5,5)  ACF(LN,T6,A6,6)  ACF(LN,T7,A7,7)  ACF(LN,T8,A8,8)          \
   ACF(LN,T9,A9,9)  ACF(LN,TA,AA,10) ACF(LN,TB,AB,11) ACF(LN,TC,AC,12)         \
   ACF(LN,TD,AD,13) ACF(LN,TE,AE,14) ACF(LN,TF,AF,15) ACF(LN,TG,AG,16)         \
   ACF(LN,TH,AH,17) ACF(LN,TI,AI,18) ACF(LN,TJ,AJ,19) ACF(LN,TK,AK,20)         \
   ACF(LN,TL,AL,21) ACF(LN,TM,AM,22) ACF(LN,TN,AN,23) ACF(LN,TO,AO,24)         \
   ACF(LN,TP,AP,25) ACF(LN,TQ,AQ,26) ACF(LN,TR,AR,27)                          \
   CFC_(UN,LN)( CFARGTA27(AACF,JCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,\
                                   A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) ); \
 WCF(T1,A1,1)  WCF(T2,A2,2)  WCF(T3,A3,3)  WCF(T4,A4,4)  WCF(T5,A5,5)  WCF(T6,A6,6)  \
 WCF(T7,A7,7)  WCF(T8,A8,8)  WCF(T9,A9,9)  WCF(TA,AA,10) WCF(TB,AB,11) WCF(TC,AC,12) \
 WCF(TD,AD,13) WCF(TE,AE,14) WCF(TF,AF,15) WCF(TG,AG,16) WCF(TH,AH,17) WCF(TI,AI,18) \
 WCF(TJ,AJ,19) WCF(TK,AK,20) WCF(TL,AL,21) WCF(TM,AM,22) WCF(TN,AN,23) WCF(TO,AO,24) \
 WCF(TP,AP,25) WCF(TQ,AQ,26) WCF(TR,AR,27) }while(0)
#endif
#endif         /* MAX_PREPRO_ARGS */

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

/*               UTILITIES FOR C TO CALL FORTRAN FUNCTIONS                 */

/*N.B. PROTOCCALLSFFUNn(..) generates code, whether or not the FORTRAN
  function is called. Therefore, especially for creator's of C header files
  for large FORTRAN libraries which include many functions, to reduce
  compile time and object code size, it may be desirable to create
  preprocessor directives to allow users to create code for only those
  functions which they use.                                                */

/* The following defines the maximum length string that a function can return.
   Of course it may be undefine-d and re-define-d before individual
   PROTOCCALLSFFUNn(..) as required. It would also be nice to have this derived
   from the individual machines' limits.                                      */
#define MAX_LEN_FORTRAN_FUNCTION_STRING 0x4FE

/* The following defines a character used by CFORTRAN.H to flag the end of a
   string coming out of a FORTRAN routine.                                 */
#define CFORTRAN_NON_CHAR 0x7F

#ifdef OLD_VAXC                                /* Prevent %CC-I-PARAMNOTUSED. */
#pragma nostandard
#endif

#define _SEP_(TN,C,cfCOMMA)     _(__SEP_,C)(TN,cfCOMMA)
#define __SEP_0(TN,cfCOMMA)  
#define __SEP_1(TN,cfCOMMA)     _Icf(2,SEP,TN,cfCOMMA,0)
#define        INT_cfSEP(T,B) _(A,B)
#define       INTV_cfSEP(T,B) INT_cfSEP(T,B)
#define      INTVV_cfSEP(T,B) INT_cfSEP(T,B)
#define     INTVVV_cfSEP(T,B) INT_cfSEP(T,B)
#define    INTVVVV_cfSEP(T,B) INT_cfSEP(T,B)
#define   INTVVVVV_cfSEP(T,B) INT_cfSEP(T,B)
#define  INTVVVVVV_cfSEP(T,B) INT_cfSEP(T,B)
#define INTVVVVVVV_cfSEP(T,B) INT_cfSEP(T,B)
#define       PINT_cfSEP(T,B) INT_cfSEP(T,B)
#define      PVOID_cfSEP(T,B) INT_cfSEP(T,B)
#define    ROUTINE_cfSEP(T,B) INT_cfSEP(T,B)
#define     SIMPLE_cfSEP(T,B) INT_cfSEP(T,B)
#define       VOID_cfSEP(T,B) INT_cfSEP(T,B)    /* For FORTRAN calls C subr.s.*/
#define     STRING_cfSEP(T,B) INT_cfSEP(T,B)
#define    STRINGV_cfSEP(T,B) INT_cfSEP(T,B)
#define    PSTRING_cfSEP(T,B) INT_cfSEP(T,B)
#define   PSTRINGV_cfSEP(T,B) INT_cfSEP(T,B)
#define   PNSTRING_cfSEP(T,B) INT_cfSEP(T,B)
#define   PPSTRING_cfSEP(T,B) INT_cfSEP(T,B)
#define    ZTRINGV_cfSEP(T,B) INT_cfSEP(T,B)
#define   PZTRINGV_cfSEP(T,B) INT_cfSEP(T,B)
                         
#if defined(SIGNED_BYTE) || !defined(UNSIGNED_BYTE)
#ifdef OLD_VAXC
#define INTEGER_BYTE               char    /* Old VAXC barfs on 'signed char' */
#else
#define INTEGER_BYTE        signed char    /* default */
#endif
#else
#define INTEGER_BYTE        unsigned char
#endif
#define    BYTEVVVVVVV_cfTYPE INTEGER_BYTE
#define  DOUBLEVVVVVVV_cfTYPE DOUBLE_PRECISION 
#define   FLOATVVVVVVV_cfTYPE FORTRAN_REAL
#define     INTVVVVVVV_cfTYPE int
#define LOGICALVVVVVVV_cfTYPE int
#define    LONGVVVVVVV_cfTYPE long
#define LONGLONGVVVVVVV_cfTYPE LONGLONG   /* added by MR December 2005 */
#define   SHORTVVVVVVV_cfTYPE short
#define          PBYTE_cfTYPE INTEGER_BYTE
#define        PDOUBLE_cfTYPE DOUBLE_PRECISION 
#define         PFLOAT_cfTYPE FORTRAN_REAL
#define           PINT_cfTYPE int
#define       PLOGICAL_cfTYPE int
#define          PLONG_cfTYPE long
#define      PLONGLONG_cfTYPE LONGLONG  /* added by MR December 2005 */
#define         PSHORT_cfTYPE short

#define CFARGS0(A,T,V,W,X,Y,Z) _3(T,_cf,A)
#define CFARGS1(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V)
#define CFARGS2(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W)
#define CFARGS3(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W,X)
#define CFARGS4(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W,X,Y)
#define CFARGS5(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W,X,Y,Z)

#define  _Icf(N,T,I,X,Y)                 _(I,_cfINT)(N,T,I,X,Y,0)
#define _Icf4(N,T,I,X,Y,Z)               _(I,_cfINT)(N,T,I,X,Y,Z)
#define           BYTE_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
#define         DOUBLE_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INT,B,X,Y,Z,0)
#define          FLOAT_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
#define            INT_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
#define        LOGICAL_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
#define           LONG_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
#define       LONGLONG_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define          SHORT_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
#define          PBYTE_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
#define        PDOUBLE_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,PINT,B,X,Y,Z,0)
#define         PFLOAT_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
#define           PINT_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
#define       PLOGICAL_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
#define          PLONG_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
#define      PLONGLONG_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define         PSHORT_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
#define          BYTEV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
#define         BYTEVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
#define        BYTEVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
#define       BYTEVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
#define      BYTEVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
#define     BYTEVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
#define    BYTEVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
#define        DOUBLEV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTV,B,X,Y,Z,0)
#define       DOUBLEVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVV,B,X,Y,Z,0)
#define      DOUBLEVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVV,B,X,Y,Z,0)
#define     DOUBLEVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVV,B,X,Y,Z,0)
#define    DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVVV,B,X,Y,Z,0)
#define   DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVVVV,B,X,Y,Z,0)
#define  DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVVVVV,B,X,Y,Z,0)
#define         FLOATV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
#define        FLOATVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
#define       FLOATVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
#define      FLOATVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
#define     FLOATVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
#define    FLOATVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
#define   FLOATVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
#define           INTV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
#define          INTVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
#define         INTVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
#define        INTVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
#define       INTVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
#define      INTVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
#define     INTVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
#define       LOGICALV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
#define      LOGICALVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
#define     LOGICALVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
#define    LOGICALVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
#define   LOGICALVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
#define  LOGICALVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
#define LOGICALVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
#define          LONGV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
#define         LONGVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
#define        LONGVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
#define       LONGVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
#define      LONGVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
#define     LONGVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
#define    LONGVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
#define      LONGLONGV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define     LONGLONGVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define    LONGLONGVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define   LONGLONGVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define  LONGLONGVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define LONGLONGVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define LONGLONGVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
#define         SHORTV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
#define        SHORTVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
#define       SHORTVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
#define      SHORTVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
#define     SHORTVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
#define    SHORTVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
#define   SHORTVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
#define          PVOID_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,B,B,X,Y,Z,0)
#define        ROUTINE_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
/*CRAY coughs on the first,
  i.e. the usual trouble of not being able to
  define macros to macros with arguments. 
  New ultrix is worse, it coughs on all such uses.
 */
/*#define       SIMPLE_cfINT                    PVOID_cfINT*/
#define         SIMPLE_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define           VOID_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define         STRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define        STRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define        PSTRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define       PSTRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define       PNSTRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define       PPSTRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define        ZTRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define       PZTRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
#define           CF_0_cfINT(N,A,B,X,Y,Z)
                         

#define   UCF(TN,I,C)  _SEP_(TN,C,cfCOMMA) _Icf(2,U,TN,_(A,I),0)
#define  UUCF(TN,I,C)  _SEP_(TN,C,cfCOMMA) _SEP_(TN,1,I) 
#define UUUCF(TN,I,C)  _SEP_(TN,C,cfCOLON) _Icf(2,U,TN,_(A,I),0)
#define        INT_cfU(T,A) _(T,VVVVVVV_cfTYPE)   A
#define       INTV_cfU(T,A) _(T,VVVVVV_cfTYPE)  * A
#define      INTVV_cfU(T,A) _(T,VVVVV_cfTYPE)   * A
#define     INTVVV_cfU(T,A) _(T,VVVV_cfTYPE)    * A
#define    INTVVVV_cfU(T,A) _(T,VVV_cfTYPE)     * A
#define   INTVVVVV_cfU(T,A) _(T,VV_cfTYPE)      * A
#define  INTVVVVVV_cfU(T,A) _(T,V_cfTYPE)       * A
#define INTVVVVVVV_cfU(T,A) _(T,_cfTYPE)        * A
#define       PINT_cfU(T,A) _(T,_cfTYPE)        * A
#define      PVOID_cfU(T,A) void  *A 
#define    ROUTINE_cfU(T,A) void (*A)(CF_NULL_PROTO) 
#define       VOID_cfU(T,A) void   A    /* Needed for C calls FORTRAN sub.s.  */
#define     STRING_cfU(T,A) char  *A    /*            via VOID and wrapper.   */
#define    STRINGV_cfU(T,A) char  *A
#define    PSTRING_cfU(T,A) char  *A
#define   PSTRINGV_cfU(T,A) char  *A
#define    ZTRINGV_cfU(T,A) char  *A
#define   PZTRINGV_cfU(T,A) char  *A

/* VOID breaks U into U and UU. */
#define       INT_cfUU(T,A) _(T,VVVVVVV_cfTYPE) A
#define      VOID_cfUU(T,A)             /* Needed for FORTRAN calls C sub.s.  */
#define    STRING_cfUU(T,A) const char *A


#define      BYTE_cfPU(A)   CFextern INTEGER_BYTE      FCALLSC_QUALIFIER A
#define    DOUBLE_cfPU(A)   CFextern DOUBLE_PRECISION  FCALLSC_QUALIFIER A
#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
#if defined (f2cFortran) && ! defined (gFortran)
/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
#define     FLOAT_cfPU(A)   CFextern DOUBLE_PRECISION  FCALLSC_QUALIFIER A
#else
#define     FLOAT_cfPU(A)   CFextern FORTRAN_REAL      FCALLSC_QUALIFIER A
#endif
#else				   	                   
#define     FLOAT_cfPU(A)   CFextern FLOATFUNCTIONTYPE FCALLSC_QUALIFIER A
#endif				   	                   
#define       INT_cfPU(A)   CFextern int   FCALLSC_QUALIFIER   A
#define   LOGICAL_cfPU(A)   CFextern int   FCALLSC_QUALIFIER   A
#define      LONG_cfPU(A)   CFextern long  FCALLSC_QUALIFIER   A
#define     SHORT_cfPU(A)   CFextern short FCALLSC_QUALIFIER   A
#define    STRING_cfPU(A)   CFextern void  FCALLSC_QUALIFIER   A
#define      VOID_cfPU(A)   CFextern void  FCALLSC_QUALIFIER   A

#define    BYTE_cfE INTEGER_BYTE     A0;
#define  DOUBLE_cfE DOUBLE_PRECISION A0;
#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
#define   FLOAT_cfE FORTRAN_REAL  A0;
#else
#define   FLOAT_cfE FORTRAN_REAL AA0;   FLOATFUNCTIONTYPE A0;
#endif
#define     INT_cfE int    A0;
#define LOGICAL_cfE int    A0;
#define    LONG_cfE long   A0;
#define   SHORT_cfE short  A0;
#define    VOID_cfE
#ifdef vmsFortran
#define  STRING_cfE static char AA0[1+MAX_LEN_FORTRAN_FUNCTION_STRING];        \
                       static fstring A0 =                                     \
             {MAX_LEN_FORTRAN_FUNCTION_STRING,DSC$K_DTYPE_T,DSC$K_CLASS_S,AA0};\
               memset(AA0, CFORTRAN_NON_CHAR, MAX_LEN_FORTRAN_FUNCTION_STRING);\
                                    *(AA0+MAX_LEN_FORTRAN_FUNCTION_STRING)='\0';
#else
#ifdef CRAYFortran
#define  STRING_cfE static char AA0[1+MAX_LEN_FORTRAN_FUNCTION_STRING];        \
                   static _fcd A0; *(AA0+MAX_LEN_FORTRAN_FUNCTION_STRING)='\0';\
                memset(AA0,CFORTRAN_NON_CHAR, MAX_LEN_FORTRAN_FUNCTION_STRING);\
                            A0 = _cptofcd(AA0,MAX_LEN_FORTRAN_FUNCTION_STRING);
#else
/* 'cc: SC3.0.1 13 Jul 1994' barfs on char A0[0x4FE+1]; 
 * char A0[0x4FE +1]; char A0[1+0x4FE]; are both OK.     */
#define STRING_cfE static char A0[1+MAX_LEN_FORTRAN_FUNCTION_STRING];          \
                       memset(A0, CFORTRAN_NON_CHAR,                           \
                              MAX_LEN_FORTRAN_FUNCTION_STRING);                \
                       *(A0+MAX_LEN_FORTRAN_FUNCTION_STRING)='\0';
#endif
#endif
/* ESTRING must use static char. array which is guaranteed to exist after
   function returns.                                                     */

/* N.B.i) The diff. for 0 (Zero) and >=1 arguments.
       ii)That the following create an unmatched bracket, i.e. '(', which
          must of course be matched in the call.
       iii)Commas must be handled very carefully                         */
#define    INT_cfGZ(T,UN,LN) A0=CFC_(UN,LN)(
#define   VOID_cfGZ(T,UN,LN)    CFC_(UN,LN)(
#ifdef vmsFortran
#define STRING_cfGZ(T,UN,LN)    CFC_(UN,LN)(&A0
#else
#if defined(CRAYFortran) || defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
#define STRING_cfGZ(T,UN,LN)    CFC_(UN,LN)( A0
#else
#define STRING_cfGZ(T,UN,LN)    CFC_(UN,LN)( A0,MAX_LEN_FORTRAN_FUNCTION_STRING
#endif
#endif

#define     INT_cfG(T,UN,LN)    INT_cfGZ(T,UN,LN)
#define    VOID_cfG(T,UN,LN)   VOID_cfGZ(T,UN,LN)
#define  STRING_cfG(T,UN,LN) STRING_cfGZ(T,UN,LN), /*, is only diff. from _cfG*/

#define    BYTEVVVVVVV_cfPP
#define     INTVVVVVVV_cfPP     /* These complement FLOATVVVVVVV_cfPP. */
#define  DOUBLEVVVVVVV_cfPP
#define LOGICALVVVVVVV_cfPP
#define    LONGVVVVVVV_cfPP
#define   SHORTVVVVVVV_cfPP
#define          PBYTE_cfPP
#define           PINT_cfPP
#define        PDOUBLE_cfPP
#define       PLOGICAL_cfPP
#define          PLONG_cfPP
#define         PSHORT_cfPP
#define         PFLOAT_cfPP FLOATVVVVVVV_cfPP

#define BCF(TN,AN,C)        _SEP_(TN,C,cfCOMMA) _Icf(2,B,TN,AN,0)
#define        INT_cfB(T,A) (_(T,VVVVVVV_cfTYPE)) A
#define       INTV_cfB(T,A)            A
#define      INTVV_cfB(T,A)           (A)[0]
#define     INTVVV_cfB(T,A)           (A)[0][0]
#define    INTVVVV_cfB(T,A)           (A)[0][0][0]
#define   INTVVVVV_cfB(T,A)           (A)[0][0][0][0]
#define  INTVVVVVV_cfB(T,A)           (A)[0][0][0][0][0]
#define INTVVVVVVV_cfB(T,A)           (A)[0][0][0][0][0][0]
#define       PINT_cfB(T,A) _(T,_cfPP)&A
#define     STRING_cfB(T,A) (char *)   A
#define    STRINGV_cfB(T,A) (char *)   A
#define    PSTRING_cfB(T,A) (char *)   A
#define   PSTRINGV_cfB(T,A) (char *)   A
#define      PVOID_cfB(T,A) (void *)   A
#define    ROUTINE_cfB(T,A) (cfCAST_FUNCTION)A
#define    ZTRINGV_cfB(T,A) (char *)   A
#define   PZTRINGV_cfB(T,A) (char *)   A
                                                              	
#define SCF(TN,NAME,I,A)    _(TN,_cfSTR)(3,S,NAME,I,A,0,0)
#define  DEFAULT_cfS(M,I,A)
#define  LOGICAL_cfS(M,I,A)
#define PLOGICAL_cfS(M,I,A)
#define   STRING_cfS(M,I,A) ,sizeof(A)
#define  STRINGV_cfS(M,I,A) ,( (unsigned)0xFFFF*firstindexlength(A) \
                              +secondindexlength(A))
#define  PSTRING_cfS(M,I,A) ,sizeof(A)
#define PSTRINGV_cfS(M,I,A) STRINGV_cfS(M,I,A)
#define  ZTRINGV_cfS(M,I,A)
#define PZTRINGV_cfS(M,I,A)

#define   HCF(TN,I)         _(TN,_cfSTR)(3,H,cfCOMMA, H,_(C,I),0,0)
#define  HHCF(TN,I)         _(TN,_cfSTR)(3,H,cfCOMMA,HH,_(C,I),0,0)
#define HHHCF(TN,I)         _(TN,_cfSTR)(3,H,cfCOLON, H,_(C,I),0,0)
#define  H_CF_SPECIAL       unsigned
#define HH_CF_SPECIAL
#define  DEFAULT_cfH(M,I,A)
#define  LOGICAL_cfH(S,U,B)
#define PLOGICAL_cfH(S,U,B)
#define   STRING_cfH(S,U,B) _(A,S) _(U,_CF_SPECIAL) B
#define  STRINGV_cfH(S,U,B) STRING_cfH(S,U,B)
#define  PSTRING_cfH(S,U,B) STRING_cfH(S,U,B)
#define PSTRINGV_cfH(S,U,B) STRING_cfH(S,U,B)
#define PNSTRING_cfH(S,U,B) STRING_cfH(S,U,B)
#define PPSTRING_cfH(S,U,B) STRING_cfH(S,U,B)
#define  ZTRINGV_cfH(S,U,B)
#define PZTRINGV_cfH(S,U,B)

/* Need VOID_cfSTR because Absoft forced function types go through _cfSTR. */
/* No spaces inside expansion. They screws up macro catenation kludge.     */
#define           VOID_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define           BYTE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         DOUBLE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define          FLOAT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define            INT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        LOGICAL_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,LOGICAL,A,B,C,D,E)
#define           LONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       LONGLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define          SHORT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define          BYTEV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         BYTEVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        BYTEVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       BYTEVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      BYTEVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     BYTEVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define    BYTEVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        DOUBLEV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       DOUBLEVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      DOUBLEVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     DOUBLEVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define    DOUBLEVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define   DOUBLEVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define  DOUBLEVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         FLOATV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        FLOATVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       FLOATVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      FLOATVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     FLOATVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define    FLOATVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define   FLOATVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define           INTV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define          INTVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         INTVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        INTVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       INTVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      INTVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     INTVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       LOGICALV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      LOGICALVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     LOGICALVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define    LOGICALVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define   LOGICALVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define  LOGICALVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define LOGICALVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define          LONGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         LONGVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        LONGVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       LONGVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      LONGVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     LONGVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define    LONGVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      LONGLONGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define     LONGLONGVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define    LONGLONGVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define   LONGLONGVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define  LONGLONGVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define LONGLONGVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define LONGLONGVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define         SHORTV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        SHORTVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       SHORTVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      SHORTVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define     SHORTVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define    SHORTVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define   SHORTVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define          PBYTE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        PDOUBLE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         PFLOAT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define           PINT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define       PLOGICAL_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PLOGICAL,A,B,C,D,E)
#define          PLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define      PLONGLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
#define         PSHORT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         STRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,STRING,A,B,C,D,E)
#define        PSTRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PSTRING,A,B,C,D,E)
#define        STRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,STRINGV,A,B,C,D,E)
#define       PSTRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PSTRINGV,A,B,C,D,E)
#define       PNSTRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PNSTRING,A,B,C,D,E)
#define       PPSTRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PPSTRING,A,B,C,D,E)
#define          PVOID_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        ROUTINE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define         SIMPLE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
#define        ZTRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,ZTRINGV,A,B,C,D,E)
#define       PZTRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PZTRINGV,A,B,C,D,E)
#define           CF_0_cfSTR(N,T,A,B,C,D,E)

/* See ACF table comments, which explain why CCF was split into two. */
#define CCF(NAME,TN,I)     _(TN,_cfSTR)(5,C,NAME,I,_(A,I),_(B,I),_(C,I))
#define  DEFAULT_cfC(M,I,A,B,C)
#define  LOGICAL_cfC(M,I,A,B,C)  A=C2FLOGICAL( A);
#define PLOGICAL_cfC(M,I,A,B,C) *A=C2FLOGICAL(*A);
#ifdef vmsFortran
#define   STRING_cfC(M,I,A,B,C) (B.clen=strlen(A),B.f.dsc$a_pointer=A,         \
        C==sizeof(char*)||C==(unsigned)(B.clen+1)?B.f.dsc$w_length=B.clen:     \
          (memset((A)+B.clen,' ',C-B.clen-1),A[B.f.dsc$w_length=C-1]='\0'));
      /* PSTRING_cfC to beware of array A which does not contain any \0.      */
#define  PSTRING_cfC(M,I,A,B,C) (B.dsc$a_pointer=A, C==sizeof(char*) ?         \
             B.dsc$w_length=strlen(A):  (A[C-1]='\0',B.dsc$w_length=strlen(A), \
       (unsigned)memset((A)+B.dsc$w_length,' ',C-B.dsc$w_length-1), B.dsc$w_length=C-1));
#else
#define   STRING_cfC(M,I,A,B,C) (B.nombre=A,B.clen=(unsigned)strlen(A),                             \
                C==sizeof(char*)||C==(unsigned)(B.clen+1)?B.flen=B.clen:       \
                        (unsigned)(memset(B.nombre+B.clen,' ',C-B.clen-1),B.nombre[B.flen=C-1]='\0'));
#define  PSTRING_cfC(M,I,A,B,C) (C==sizeof(char*)? B=strlen(A):                \
                    (A[C-1]='\0',B=strlen(A),memset((A)+B,' ',C-B-1),B=C-1));
#endif
          /* For CRAYFortran for (P)STRINGV_cfC, B.fs is set, but irrelevant. */
#define  STRINGV_cfC(M,I,A,B,C) \
        AATRINGV_cfA(    A,B,(C/0xFFFF)*(C%0xFFFF),C/0xFFFF,C%0xFFFF)
#define PSTRINGV_cfC(M,I,A,B,C) \
       APATRINGV_cfA(    A,B,(C/0xFFFF)*(C%0xFFFF),C/0xFFFF,C%0xFFFF)
#define  ZTRINGV_cfC(M,I,A,B,C) \
        AATRINGV_cfA(    A,B, (_3(M,_ELEMS_,I))*((_3(M,_ELEMLEN_,I))+1),       \
                              (_3(M,_ELEMS_,I)), (_3(M,_ELEMLEN_,I))+1   )
#define PZTRINGV_cfC(M,I,A,B,C) \
       APATRINGV_cfA(    A,B, (_3(M,_ELEMS_,I))*((_3(M,_ELEMLEN_,I))+1),       \
                              (_3(M,_ELEMS_,I)), (_3(M,_ELEMLEN_,I))+1   )

#define     BYTE_cfCCC(A,B) &A
#define   DOUBLE_cfCCC(A,B) &A
#if !defined(__CF__KnR)
#define    FLOAT_cfCCC(A,B) &A
                               /* Although the VAX doesn't, at least the      */
#else                          /* HP and K&R mips promote float arg.'s of     */
#define    FLOAT_cfCCC(A,B) &B /* unprototyped functions to double. Cannot    */
#endif                         /* use A here to pass the argument to FORTRAN. */
#define      INT_cfCCC(A,B) &A
#define  LOGICAL_cfCCC(A,B) &A
#define     LONG_cfCCC(A,B) &A
#define    SHORT_cfCCC(A,B) &A
#define    PBYTE_cfCCC(A,B)  A
#define  PDOUBLE_cfCCC(A,B)  A
#define   PFLOAT_cfCCC(A,B)  A
#define     PINT_cfCCC(A,B)  A
#define PLOGICAL_cfCCC(A,B)  B=A       /* B used to keep a common W table. */
#define    PLONG_cfCCC(A,B)  A
#define   PSHORT_cfCCC(A,B)  A

#define CCCF(TN,I,M)           _SEP_(TN,M,cfCOMMA) _Icf(3,CC,TN,_(A,I),_(B,I))
#define        INT_cfCC(T,A,B) _(T,_cfCCC)(A,B) 
#define       INTV_cfCC(T,A,B)  A
#define      INTVV_cfCC(T,A,B)  A
#define     INTVVV_cfCC(T,A,B)  A
#define    INTVVVV_cfCC(T,A,B)  A
#define   INTVVVVV_cfCC(T,A,B)  A
#define  INTVVVVVV_cfCC(T,A,B)  A
#define INTVVVVVVV_cfCC(T,A,B)  A
#define       PINT_cfCC(T,A,B) _(T,_cfCCC)(A,B) 
#define      PVOID_cfCC(T,A,B)  A
#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
#define    ROUTINE_cfCC(T,A,B) &A
#else
#define    ROUTINE_cfCC(T,A,B)  A
#endif
#define     SIMPLE_cfCC(T,A,B)  A
#ifdef vmsFortran
#define     STRING_cfCC(T,A,B) &B.f
#define    STRINGV_cfCC(T,A,B) &B
#define    PSTRING_cfCC(T,A,B) &B
#define   PSTRINGV_cfCC(T,A,B) &B
#else
#ifdef CRAYFortran
#define     STRING_cfCC(T,A,B) _cptofcd(A,B.flen)
#define    STRINGV_cfCC(T,A,B) _cptofcd(B.s,B.flen)
#define    PSTRING_cfCC(T,A,B) _cptofcd(A,B)
#define   PSTRINGV_cfCC(T,A,B) _cptofcd(A,B.flen)
#else
#define     STRING_cfCC(T,A,B)  A
#define    STRINGV_cfCC(T,A,B)  B.fs
#define    PSTRING_cfCC(T,A,B)  A
#define   PSTRINGV_cfCC(T,A,B)  B.fs
#endif
#endif
#define    ZTRINGV_cfCC(T,A,B)   STRINGV_cfCC(T,A,B)
#define   PZTRINGV_cfCC(T,A,B)  PSTRINGV_cfCC(T,A,B)

#define    BYTE_cfX  return A0;
#define  DOUBLE_cfX  return A0;
#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
#define   FLOAT_cfX  return A0;
#else
#define   FLOAT_cfX  ASSIGNFLOAT(AA0,A0); return AA0;
#endif
#define     INT_cfX  return A0;
#define LOGICAL_cfX  return F2CLOGICAL(A0);
#define    LONG_cfX  return A0;
#define   SHORT_cfX  return A0;
#define    VOID_cfX  return   ;
#if defined(vmsFortran) || defined(CRAYFortran)
#define  STRING_cfX  return kill_trailing(                                     \
                                      kill_trailing(AA0,CFORTRAN_NON_CHAR),' ');
#else
#define  STRING_cfX  return kill_trailing(                                     \
                                      kill_trailing( A0,CFORTRAN_NON_CHAR),' ');
#endif

#define CFFUN(NAME) _(__cf__,NAME)

/* Note that we don't use LN here, but we keep it for consistency. */
#define CCALLSFFUN0(UN,LN) CFFUN(UN)()

#ifdef OLD_VAXC                                  /* Allow %CC-I-PARAMNOTUSED. */
#pragma standard
#endif

#define CCALLSFFUN1( UN,LN,T1,                        A1)         \
        CCALLSFFUN5 (UN,LN,T1,CF_0,CF_0,CF_0,CF_0,A1,0,0,0,0)
#define CCALLSFFUN2( UN,LN,T1,T2,                     A1,A2)      \
        CCALLSFFUN5 (UN,LN,T1,T2,CF_0,CF_0,CF_0,A1,A2,0,0,0)
#define CCALLSFFUN3( UN,LN,T1,T2,T3,                  A1,A2,A3)   \
        CCALLSFFUN5 (UN,LN,T1,T2,T3,CF_0,CF_0,A1,A2,A3,0,0)
#define CCALLSFFUN4( UN,LN,T1,T2,T3,T4,               A1,A2,A3,A4)\
        CCALLSFFUN5 (UN,LN,T1,T2,T3,T4,CF_0,A1,A2,A3,A4,0)
#define CCALLSFFUN5( UN,LN,T1,T2,T3,T4,T5,            A1,A2,A3,A4,A5)          \
        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,0,0,0,0,0)
#define CCALLSFFUN6( UN,LN,T1,T2,T3,T4,T5,T6,         A1,A2,A3,A4,A5,A6)       \
        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,0,0,0,0)
#define CCALLSFFUN7( UN,LN,T1,T2,T3,T4,T5,T6,T7,      A1,A2,A3,A4,A5,A6,A7)    \
        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,0,0,0)
#define CCALLSFFUN8( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,   A1,A2,A3,A4,A5,A6,A7,A8) \
        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,0,0)
#define CCALLSFFUN9( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,A1,A2,A3,A4,A5,A6,A7,A8,A9)\
        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,0)
#define CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA)\
        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,0,0,0,0)
#define CCALLSFFUN11(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB)\
        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,0,0,0)
#define CCALLSFFUN12(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC)\
        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,0,0)
#define CCALLSFFUN13(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD)\
        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,0)

#define CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)\
((CFFUN(UN)(  BCF(T1,A1,0) BCF(T2,A2,1) BCF(T3,A3,1) BCF(T4,A4,1) BCF(T5,A5,1) \
              BCF(T6,A6,1) BCF(T7,A7,1) BCF(T8,A8,1) BCF(T9,A9,1) BCF(TA,AA,1) \
              BCF(TB,AB,1) BCF(TC,AC,1) BCF(TD,AD,1) BCF(TE,AE,1)              \
           SCF(T1,LN,1,A1)  SCF(T2,LN,2,A2)  SCF(T3,LN,3,A3)  SCF(T4,LN,4,A4)  \
           SCF(T5,LN,5,A5)  SCF(T6,LN,6,A6)  SCF(T7,LN,7,A7)  SCF(T8,LN,8,A8)  \
           SCF(T9,LN,9,A9)  SCF(TA,LN,10,AA) SCF(TB,LN,11,AB) SCF(TC,LN,12,AC) \
           SCF(TD,LN,13,AD) SCF(TE,LN,14,AE))))

/*  N.B. Create a separate function instead of using (call function, function
value here) because in order to create the variables needed for the input
arg.'s which may be const.'s one has to do the creation within {}, but these
can never be placed within ()'s. Therefore one must create wrapper functions.
gcc, on the other hand may be able to avoid the wrapper functions. */

/* Prototypes are needed to correctly handle the value returned correctly. N.B.
Can only have prototype arg.'s with difficulty, a la G... table since FORTRAN
functions returning strings have extra arg.'s. Don't bother, since this only
causes a compiler warning to come up when one uses FCALLSCFUNn and CCALLSFFUNn
for the same function in the same source code. Something done by the experts in
debugging only.*/    

#define PROTOCCALLSFFUN0(F,UN,LN)                                              \
_(F,_cfPU)( CFC_(UN,LN))(CF_NULL_PROTO);                                       \
static _Icf(2,U,F,CFFUN(UN),0)() {_(F,_cfE) _Icf(3,GZ,F,UN,LN) ABSOFT_cf1(F));_(F,_cfX)}

#define PROTOCCALLSFFUN1( T0,UN,LN,T1)                                         \
        PROTOCCALLSFFUN5 (T0,UN,LN,T1,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN2( T0,UN,LN,T1,T2)                                      \
        PROTOCCALLSFFUN5 (T0,UN,LN,T1,T2,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN3( T0,UN,LN,T1,T2,T3)                                   \
        PROTOCCALLSFFUN5 (T0,UN,LN,T1,T2,T3,CF_0,CF_0)
#define PROTOCCALLSFFUN4( T0,UN,LN,T1,T2,T3,T4)                                \
        PROTOCCALLSFFUN5 (T0,UN,LN,T1,T2,T3,T4,CF_0)
#define PROTOCCALLSFFUN5( T0,UN,LN,T1,T2,T3,T4,T5)                             \
        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN6( T0,UN,LN,T1,T2,T3,T4,T5,T6)                          \
        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN7( T0,UN,LN,T1,T2,T3,T4,T5,T6,T7)                       \
        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN8( T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8)                    \
        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0)
#define PROTOCCALLSFFUN9( T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9)                 \
        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0)
#define PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA)              \
        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN11(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB)           \
        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0)
#define PROTOCCALLSFFUN12(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC)        \
        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0)
#define PROTOCCALLSFFUN13(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD)     \
        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0)

/* HP/UX 9.01 cc requires the blank between '_Icf(3,G,T0,UN,LN) CCCF(T1,1,0)' */

#ifndef __CF__KnR
#define PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  \
 _(T0,_cfPU)(CFC_(UN,LN))(CF_NULL_PROTO); static _Icf(2,U,T0,CFFUN(UN),0)(     \
   CFARGT14FS(UCF,HCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) )          \
{       CFARGT14S(VCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    _(T0,_cfE) \
 CCF(LN,T1,1)  CCF(LN,T2,2)  CCF(LN,T3,3)  CCF(LN,T4,4)  CCF(LN,T5,5)          \
 CCF(LN,T6,6)  CCF(LN,T7,7)  CCF(LN,T8,8)  CCF(LN,T9,9)  CCF(LN,TA,10)         \
 CCF(LN,TB,11) CCF(LN,TC,12) CCF(LN,TD,13) CCF(LN,TE,14)    _Icf(3,G,T0,UN,LN) \
 CFARGT14(CCCF,JCF,ABSOFT_cf1(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)); \
 WCF(T1,A1,1)   WCF(T2,A2,2)   WCF(T3,A3,3)   WCF(T4,A4,4)  WCF(T5,A5,5)       \
 WCF(T6,A6,6)   WCF(T7,A7,7)   WCF(T8,A8,8)   WCF(T9,A9,9)  WCF(TA,A10,10)     \
 WCF(TB,A11,11) WCF(TC,A12,12) WCF(TD,A13,13) WCF(TE,A14,14) _(T0,_cfX)}
#else
#define PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  \
 _(T0,_cfPU)(CFC_(UN,LN))(CF_NULL_PROTO); static _Icf(2,U,T0,CFFUN(UN),0)(     \
   CFARGT14FS(UUCF,HHCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) )        \
 CFARGT14FS(UUUCF,HHHCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) ;        \
{       CFARGT14S(VCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    _(T0,_cfE) \
 CCF(LN,T1,1)  CCF(LN,T2,2)  CCF(LN,T3,3)  CCF(LN,T4,4)  CCF(LN,T5,5)          \
 CCF(LN,T6,6)  CCF(LN,T7,7)  CCF(LN,T8,8)  CCF(LN,T9,9)  CCF(LN,TA,10)         \
 CCF(LN,TB,11) CCF(LN,TC,12) CCF(LN,TD,13) CCF(LN,TE,14)    _Icf(3,G,T0,UN,LN) \
 CFARGT14(CCCF,JCF,ABSOFT_cf1(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)); \
 WCF(T1,A1,1)   WCF(T2,A2,2)   WCF(T3,A3,3)   WCF(T4,A4,4)   WCF(T5,A5,5)      \
 WCF(T6,A6,6)   WCF(T7,A7,7)   WCF(T8,A8,8)   WCF(T9,A9,9)   WCF(TA,A10,10)    \
 WCF(TB,A11,11) WCF(TC,A12,12) WCF(TD,A13,13) WCF(TE,A14,14) _(T0,_cfX)}
#endif

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

/*               UTILITIES FOR FORTRAN TO CALL C ROUTINES                  */

#ifdef OLD_VAXC                                /* Prevent %CC-I-PARAMNOTUSED. */
#pragma nostandard
#endif

#if defined(vmsFortran) || defined(CRAYFortran)
#define   DCF(TN,I)
#define  DDCF(TN,I)
#define DDDCF(TN,I)
#else
#define   DCF(TN,I)          HCF(TN,I)
#define  DDCF(TN,I)         HHCF(TN,I)
#define DDDCF(TN,I)        HHHCF(TN,I)
#endif

#define QCF(TN,I)       _(TN,_cfSTR)(1,Q,_(B,I), 0,0,0,0)
#define  DEFAULT_cfQ(B)
#define  LOGICAL_cfQ(B)
#define PLOGICAL_cfQ(B)
#define  STRINGV_cfQ(B) char *B; unsigned int _(B,N);
#define   STRING_cfQ(B) char *B=NULL;
#define  PSTRING_cfQ(B) char *B=NULL;
#define PSTRINGV_cfQ(B) STRINGV_cfQ(B)
#define PNSTRING_cfQ(B) char *B=NULL;
#define PPSTRING_cfQ(B)

#ifdef     __sgi   /* Else SGI gives warning 182 contrary to its C LRM A.17.7 */
#define ROUTINE_orig    *(void**)& 
#else
#define ROUTINE_orig     (void *)  
#endif

#define ROUTINE_1     ROUTINE_orig   
#define ROUTINE_2     ROUTINE_orig   
#define ROUTINE_3     ROUTINE_orig   
#define ROUTINE_4     ROUTINE_orig   
#define ROUTINE_5     ROUTINE_orig   
#define ROUTINE_6     ROUTINE_orig   
#define ROUTINE_7     ROUTINE_orig   
#define ROUTINE_8     ROUTINE_orig   
#define ROUTINE_9     ROUTINE_orig   
#define ROUTINE_10    ROUTINE_orig   
#define ROUTINE_11    ROUTINE_orig   
#define ROUTINE_12    ROUTINE_orig   
#define ROUTINE_13    ROUTINE_orig   
#define ROUTINE_14    ROUTINE_orig   
#define ROUTINE_15    ROUTINE_orig   
#define ROUTINE_16    ROUTINE_orig   
#define ROUTINE_17    ROUTINE_orig   
#define ROUTINE_18    ROUTINE_orig   
#define ROUTINE_19    ROUTINE_orig   
#define ROUTINE_20    ROUTINE_orig   
#define ROUTINE_21    ROUTINE_orig   
#define ROUTINE_22    ROUTINE_orig   
#define ROUTINE_23    ROUTINE_orig   
#define ROUTINE_24    ROUTINE_orig   
#define ROUTINE_25    ROUTINE_orig   
#define ROUTINE_26    ROUTINE_orig   
#define ROUTINE_27    ROUTINE_orig   

#define TCF(NAME,TN,I,M)              _SEP_(TN,M,cfCOMMA) _(TN,_cfT)(NAME,I,_(A,I),_(B,I),_(C,I))
#define           BYTE_cfT(M,I,A,B,D) *A
#define         DOUBLE_cfT(M,I,A,B,D) *A
#define          FLOAT_cfT(M,I,A,B,D) *A
#define            INT_cfT(M,I,A,B,D) *A
#define        LOGICAL_cfT(M,I,A,B,D)  F2CLOGICAL(*A)
#define           LONG_cfT(M,I,A,B,D) *A
#define       LONGLONG_cfT(M,I,A,B,D) *A /* added by MR December 2005 */
#define          SHORT_cfT(M,I,A,B,D) *A
#define          BYTEV_cfT(M,I,A,B,D)  A
#define        DOUBLEV_cfT(M,I,A,B,D)  A
#define         FLOATV_cfT(M,I,A,B,D)  VOIDP A
#define           INTV_cfT(M,I,A,B,D)  A
#define       LOGICALV_cfT(M,I,A,B,D)  A
#define          LONGV_cfT(M,I,A,B,D)  A
#define      LONGLONGV_cfT(M,I,A,B,D)  A /* added by MR December 2005 */
#define         SHORTV_cfT(M,I,A,B,D)  A
#define         BYTEVV_cfT(M,I,A,B,D)  (void *)A /* We have to cast to void *,*/
#define        BYTEVVV_cfT(M,I,A,B,D)  (void *)A /* since we don't know the   */
#define       BYTEVVVV_cfT(M,I,A,B,D)  (void *)A /* dimensions of the array.  */
#define      BYTEVVVVV_cfT(M,I,A,B,D)  (void *)A /* i.e. Unfortunately, can't */
#define     BYTEVVVVVV_cfT(M,I,A,B,D)  (void *)A /* check that the type       */
#define    BYTEVVVVVVV_cfT(M,I,A,B,D)  (void *)A /* matches the prototype.    */
#define       DOUBLEVV_cfT(M,I,A,B,D)  (void *)A
#define      DOUBLEVVV_cfT(M,I,A,B,D)  (void *)A
#define     DOUBLEVVVV_cfT(M,I,A,B,D)  (void *)A
#define    DOUBLEVVVVV_cfT(M,I,A,B,D)  (void *)A
#define   DOUBLEVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define  DOUBLEVVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define        FLOATVV_cfT(M,I,A,B,D)  (void *)A
#define       FLOATVVV_cfT(M,I,A,B,D)  (void *)A
#define      FLOATVVVV_cfT(M,I,A,B,D)  (void *)A
#define     FLOATVVVVV_cfT(M,I,A,B,D)  (void *)A
#define    FLOATVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define   FLOATVVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define          INTVV_cfT(M,I,A,B,D)  (void *)A  
#define         INTVVV_cfT(M,I,A,B,D)  (void *)A  
#define        INTVVVV_cfT(M,I,A,B,D)  (void *)A  
#define       INTVVVVV_cfT(M,I,A,B,D)  (void *)A
#define      INTVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define     INTVVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define      LOGICALVV_cfT(M,I,A,B,D)  (void *)A
#define     LOGICALVVV_cfT(M,I,A,B,D)  (void *)A
#define    LOGICALVVVV_cfT(M,I,A,B,D)  (void *)A
#define   LOGICALVVVVV_cfT(M,I,A,B,D)  (void *)A
#define  LOGICALVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define LOGICALVVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define         LONGVV_cfT(M,I,A,B,D)  (void *)A
#define        LONGVVV_cfT(M,I,A,B,D)  (void *)A
#define       LONGVVVV_cfT(M,I,A,B,D)  (void *)A
#define      LONGVVVVV_cfT(M,I,A,B,D)  (void *)A
#define     LONGVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define    LONGVVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define     LONGLONGVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
#define    LONGLONGVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
#define   LONGLONGVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
#define  LONGLONGVVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
#define LONGLONGVVVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
#define LONGLONGVVVVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
#define        SHORTVV_cfT(M,I,A,B,D)  (void *)A
#define       SHORTVVV_cfT(M,I,A,B,D)  (void *)A
#define      SHORTVVVV_cfT(M,I,A,B,D)  (void *)A
#define     SHORTVVVVV_cfT(M,I,A,B,D)  (void *)A
#define    SHORTVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define   SHORTVVVVVVV_cfT(M,I,A,B,D)  (void *)A
#define          PBYTE_cfT(M,I,A,B,D)  A
#define        PDOUBLE_cfT(M,I,A,B,D)  A
#define         PFLOAT_cfT(M,I,A,B,D)  VOIDP A
#define           PINT_cfT(M,I,A,B,D)  A
#define       PLOGICAL_cfT(M,I,A,B,D)  ((*A=F2CLOGICAL(*A)),A)
#define          PLONG_cfT(M,I,A,B,D)  A
#define      PLONGLONG_cfT(M,I,A,B,D)  A /* added by MR December 2005 */
#define         PSHORT_cfT(M,I,A,B,D)  A
#define          PVOID_cfT(M,I,A,B,D)  A
#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
#define        ROUTINE_cfT(M,I,A,B,D)  _(ROUTINE_,I)  (*A)
#else
#define        ROUTINE_cfT(M,I,A,B,D)  _(ROUTINE_,I)    A
#endif
/* A == pointer to the characters
   D == length of the string, or of an element in an array of strings
   E == number of elements in an array of strings                             */
#define TTSTR(    A,B,D)                                                       \
           ((B=_cf_malloc(D+1))[D]='\0', memcpy(B,A,D), kill_trailing(B,' '))
#define TTTTSTR(  A,B,D)   (!(D<4||A[0]||A[1]||A[2]||A[3]))?NULL:              \
                            memchr(A,'\0',D)                 ?A   : TTSTR(A,B,D)
#define TTTTSTRV( A,B,D,E) (_(B,N)=E,B=_cf_malloc(_(B,N)*(D+1)), (void *)      \
  vkill_trailing(f2cstrv(A,B,D+1, _(B,N)*(D+1)), D+1,_(B,N)*(D+1),' '))
#ifdef vmsFortran
#define         STRING_cfT(M,I,A,B,D)  TTTTSTR( A->dsc$a_pointer,B,A->dsc$w_length)
#define        STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(A->dsc$a_pointer, B,           \
                                             A->dsc$w_length , A->dsc$l_m[0])
#define        PSTRING_cfT(M,I,A,B,D)    TTSTR( A->dsc$a_pointer,B,A->dsc$w_length)
#define       PPSTRING_cfT(M,I,A,B,D)           A->dsc$a_pointer
#else
#ifdef CRAYFortran
#define         STRING_cfT(M,I,A,B,D)  TTTTSTR( _fcdtocp(A),B,_fcdlen(A))
#define        STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(_fcdtocp(A),B,_fcdlen(A),      \
                              num_elem(_fcdtocp(A),_fcdlen(A),_3(M,_STRV_A,I)))
#define        PSTRING_cfT(M,I,A,B,D)    TTSTR( _fcdtocp(A),B,_fcdlen(A))
#define       PPSTRING_cfT(M,I,A,B,D)           _fcdtocp(A)
#else
#define         STRING_cfT(M,I,A,B,D)  TTTTSTR( A,B,D)
#define        STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(A,B,D, num_elem(A,D,_3(M,_STRV_A,I)))
#define        PSTRING_cfT(M,I,A,B,D)    TTSTR( A,B,D)
#define       PPSTRING_cfT(M,I,A,B,D)           ((void)D, A)
#endif
#endif
#define       PNSTRING_cfT(M,I,A,B,D)    STRING_cfT(M,I,A,B,D)
#define       PSTRINGV_cfT(M,I,A,B,D)   STRINGV_cfT(M,I,A,B,D)
#define           CF_0_cfT(M,I,A,B,D)

#define RCF(TN,I)           _(TN,_cfSTR)(3,R,_(A,I),_(B,I),_(C,I),0,0)
#define  DEFAULT_cfR(A,B,D)
#define  LOGICAL_cfR(A,B,D)
#define PLOGICAL_cfR(A,B,D) *A=C2FLOGICAL(*A);
#define   STRING_cfR(A,B,D) if (B) _cf_free(B);
#define  STRINGV_cfR(A,B,D) _cf_free(B);
/* A and D as defined above for TSTRING(V) */
#define RRRRPSTR( A,B,D)    if (B) memcpy(A,B, _cfMIN(strlen(B),D)),           \
                  (D>strlen(B)?memset(A+strlen(B),' ', D-strlen(B)):0), _cf_free(B);
#define RRRRPSTRV(A,B,D)    c2fstrv(B,A,D+1,(D+1)*_(B,N)), _cf_free(B);
#ifdef vmsFortran
#define  PSTRING_cfR(A,B,D) RRRRPSTR( A->dsc$a_pointer,B,A->dsc$w_length)
#define PSTRINGV_cfR(A,B,D) RRRRPSTRV(A->dsc$a_pointer,B,A->dsc$w_length)
#else
#ifdef CRAYFortran
#define  PSTRING_cfR(A,B,D) RRRRPSTR( _fcdtocp(A),B,_fcdlen(A))
#define PSTRINGV_cfR(A,B,D) RRRRPSTRV(_fcdtocp(A),B,_fcdlen(A))
#else
#define  PSTRING_cfR(A,B,D) RRRRPSTR( A,B,D)
#define PSTRINGV_cfR(A,B,D) RRRRPSTRV(A,B,D)
#endif
#endif
#define PNSTRING_cfR(A,B,D) PSTRING_cfR(A,B,D)
#define PPSTRING_cfR(A,B,D)

#define    BYTE_cfFZ(UN,LN) INTEGER_BYTE     FCALLSC_QUALIFIER fcallsc(UN,LN)(
#define  DOUBLE_cfFZ(UN,LN) DOUBLE_PRECISION FCALLSC_QUALIFIER fcallsc(UN,LN)(
#define     INT_cfFZ(UN,LN) int   FCALLSC_QUALIFIER fcallsc(UN,LN)(
#define LOGICAL_cfFZ(UN,LN) int   FCALLSC_QUALIFIER fcallsc(UN,LN)(
#define    LONG_cfFZ(UN,LN) long  FCALLSC_QUALIFIER fcallsc(UN,LN)(
#define LONGLONG_cfFZ(UN,LN) LONGLONG FCALLSC_QUALIFIER fcallsc(UN,LN)( /* added by MR December 2005 */
#define   SHORT_cfFZ(UN,LN) short FCALLSC_QUALIFIER fcallsc(UN,LN)(
#define    VOID_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(
#ifndef __CF__KnR
/* The void is req'd by the Apollo, to make this an ANSI function declaration.
   The Apollo promotes K&R float functions to double. */
#if defined (f2cFortran) && ! defined (gFortran)
/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
#define FLOAT_cfFZ(UN,LN) DOUBLE_PRECISION FCALLSC_QUALIFIER fcallsc(UN,LN)(void
#else
#define FLOAT_cfFZ(UN,LN) FORTRAN_REAL FCALLSC_QUALIFIER fcallsc(UN,LN)(void
#endif
#ifdef vmsFortran
#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(fstring *AS
#else
#ifdef CRAYFortran
#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(_fcd     AS
#else
#if  defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(char    *AS
#else
#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(char    *AS, unsigned D0
#endif
#endif
#endif
#else
#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
#if defined (f2cFortran) && ! defined (gFortran)
/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
#define   FLOAT_cfFZ(UN,LN) DOUBLE_PRECISION  FCALLSC_QUALIFIER fcallsc(UN,LN)(
#else
#define   FLOAT_cfFZ(UN,LN) FORTRAN_REAL      FCALLSC_QUALIFIER fcallsc(UN,LN)(
#endif
#else
#define   FLOAT_cfFZ(UN,LN) FLOATFUNCTIONTYPE FCALLSC_QUALIFIER fcallsc(UN,LN)(
#endif
#if defined(vmsFortran) || defined(CRAYFortran) || defined(AbsoftUNIXFortran)
#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(AS
#else
#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(AS, D0
#endif
#endif

#define    BYTE_cfF(UN,LN)     BYTE_cfFZ(UN,LN)
#define  DOUBLE_cfF(UN,LN)   DOUBLE_cfFZ(UN,LN)
#ifndef __CF_KnR
#if defined (f2cFortran) && ! defined (gFortran)
/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
#define   FLOAT_cfF(UN,LN)  DOUBLE_PRECISION FCALLSC_QUALIFIER fcallsc(UN,LN)(
#else
#define   FLOAT_cfF(UN,LN)  FORTRAN_REAL FCALLSC_QUALIFIER fcallsc(UN,LN)(
#endif
#else
#define   FLOAT_cfF(UN,LN)    FLOAT_cfFZ(UN,LN)
#endif
#define     INT_cfF(UN,LN)      INT_cfFZ(UN,LN)
#define LOGICAL_cfF(UN,LN)  LOGICAL_cfFZ(UN,LN)
#define    LONG_cfF(UN,LN)     LONG_cfFZ(UN,LN)
#define LONGLONG_cfF(UN,LN) LONGLONG_cfFZ(UN,LN) /* added by MR December 2005 */
#define   SHORT_cfF(UN,LN)    SHORT_cfFZ(UN,LN)
#define    VOID_cfF(UN,LN)     VOID_cfFZ(UN,LN)
#define  STRING_cfF(UN,LN)   STRING_cfFZ(UN,LN),

#define     INT_cfFF
#define    VOID_cfFF
#ifdef vmsFortran
#define  STRING_cfFF           fstring *AS; 
#else
#ifdef CRAYFortran
#define  STRING_cfFF           _fcd     AS;
#else
#define  STRING_cfFF           char    *AS; unsigned D0;
#endif
#endif

#define     INT_cfL            A0=
#define  STRING_cfL            A0=
#define    VOID_cfL                        

#define    INT_cfK
#define   VOID_cfK
/* KSTRING copies the string into the position provided by the caller. */
#ifdef vmsFortran
#define STRING_cfK                                                             \
 memcpy(AS->dsc$a_pointer,A0,_cfMIN(AS->dsc$w_length,(A0==NULL?0:strlen(A0))));\
 AS->dsc$w_length>(A0==NULL?0:strlen(A0))?                                     \
  memset(AS->dsc$a_pointer+(A0==NULL?0:strlen(A0)),' ',                        \
         AS->dsc$w_length-(A0==NULL?0:strlen(A0))):0;
#else
#ifdef CRAYFortran
#define STRING_cfK                                                             \
 memcpy(_fcdtocp(AS),A0, _cfMIN(_fcdlen(AS),(A0==NULL?0:strlen(A0))) );        \
 _fcdlen(AS)>(A0==NULL?0:strlen(A0))?                                          \
  memset(_fcdtocp(AS)+(A0==NULL?0:strlen(A0)),' ',                             \
         _fcdlen(AS)-(A0==NULL?0:strlen(A0))):0;
#else
#define STRING_cfK         memcpy(AS,A0, _cfMIN(D0,(A0==NULL?0:strlen(A0))) ); \
                 D0>(A0==NULL?0:strlen(A0))?memset(AS+(A0==NULL?0:strlen(A0)), \
                                            ' ', D0-(A0==NULL?0:strlen(A0))):0;
#endif
#endif

/* Note that K.. and I.. can't be combined since K.. has to access data before
R.., in order for functions returning strings which are also passed in as
arguments to work correctly. Note that R.. frees and hence may corrupt the
string. */
#define    BYTE_cfI  return A0;
#define  DOUBLE_cfI  return A0;
#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
#define   FLOAT_cfI  return A0;
#else
#define   FLOAT_cfI  RETURNFLOAT(A0);
#endif
#define     INT_cfI  return A0;
#ifdef hpuxFortran800
/* Incredibly, functions must return true as 1, elsewhere .true.==0x01000000. */
#define LOGICAL_cfI  return ((A0)?1:0);
#else
#define LOGICAL_cfI  return C2FLOGICAL(A0);
#endif
#define    LONG_cfI  return A0;
#define LONGLONG_cfI  return A0; /* added by MR December 2005 */
#define   SHORT_cfI  return A0;
#define  STRING_cfI  return   ;
#define    VOID_cfI  return   ;

#ifdef OLD_VAXC                                  /* Allow %CC-I-PARAMNOTUSED. */
#pragma standard
#endif

#define FCALLSCSUB0( CN,UN,LN)             FCALLSCFUN0(VOID,CN,UN,LN)
#define FCALLSCSUB1( CN,UN,LN,T1)          FCALLSCFUN1(VOID,CN,UN,LN,T1)
#define FCALLSCSUB2( CN,UN,LN,T1,T2)       FCALLSCFUN2(VOID,CN,UN,LN,T1,T2)
#define FCALLSCSUB3( CN,UN,LN,T1,T2,T3)    FCALLSCFUN3(VOID,CN,UN,LN,T1,T2,T3)
#define FCALLSCSUB4( CN,UN,LN,T1,T2,T3,T4) \
    FCALLSCFUN4(VOID,CN,UN,LN,T1,T2,T3,T4)
#define FCALLSCSUB5( CN,UN,LN,T1,T2,T3,T4,T5) \
    FCALLSCFUN5(VOID,CN,UN,LN,T1,T2,T3,T4,T5)
#define FCALLSCSUB6( CN,UN,LN,T1,T2,T3,T4,T5,T6) \
    FCALLSCFUN6(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6)       
#define FCALLSCSUB7( CN,UN,LN,T1,T2,T3,T4,T5,T6,T7) \
    FCALLSCFUN7(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7)
#define FCALLSCSUB8( CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8) \
    FCALLSCFUN8(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8)
#define FCALLSCSUB9( CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9) \
    FCALLSCFUN9(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9)
#define FCALLSCSUB10(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA) \
   FCALLSCFUN10(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA)
#define FCALLSCSUB11(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB) \
   FCALLSCFUN11(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB)
#define FCALLSCSUB12(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC) \
   FCALLSCFUN12(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC)
#define FCALLSCSUB13(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD) \
   FCALLSCFUN13(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD)
#define FCALLSCSUB14(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
   FCALLSCFUN14(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
#define FCALLSCSUB15(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF) \
   FCALLSCFUN15(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF)
#define FCALLSCSUB16(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG) \
   FCALLSCFUN16(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG)
#define FCALLSCSUB17(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH) \
   FCALLSCFUN17(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH)
#define FCALLSCSUB18(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI) \
   FCALLSCFUN18(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI)
#define FCALLSCSUB19(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ) \
   FCALLSCFUN19(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ)
#define FCALLSCSUB20(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
   FCALLSCFUN20(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
#define FCALLSCSUB21(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL) \
   FCALLSCFUN21(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL)
#define FCALLSCSUB22(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM) \
   FCALLSCFUN22(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM)
#define FCALLSCSUB23(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN) \
   FCALLSCFUN23(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN)
#define FCALLSCSUB24(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO) \
   FCALLSCFUN24(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO)
#define FCALLSCSUB25(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP) \
   FCALLSCFUN25(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP)
#define FCALLSCSUB26(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ) \
   FCALLSCFUN26(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ)
#define FCALLSCSUB27(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
   FCALLSCFUN27(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)


#define FCALLSCFUN1( T0,CN,UN,LN,T1) \
        FCALLSCFUN5 (T0,CN,UN,LN,T1,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN2( T0,CN,UN,LN,T1,T2) \
        FCALLSCFUN5 (T0,CN,UN,LN,T1,T2,CF_0,CF_0,CF_0)
#define FCALLSCFUN3( T0,CN,UN,LN,T1,T2,T3) \
        FCALLSCFUN5 (T0,CN,UN,LN,T1,T2,T3,CF_0,CF_0)
#define FCALLSCFUN4( T0,CN,UN,LN,T1,T2,T3,T4) \
        FCALLSCFUN5 (T0,CN,UN,LN,T1,T2,T3,T4,CF_0)
#define FCALLSCFUN5( T0,CN,UN,LN,T1,T2,T3,T4,T5) \
        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN6( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6) \
        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN7( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7) \
        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0)
#define FCALLSCFUN8( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8) \
        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0)
#define FCALLSCFUN9( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9) \
        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0)
#define FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA) \
        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN11(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB) \
        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0)
#define FCALLSCFUN12(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC) \
        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0)
#define FCALLSCFUN13(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD) \
        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0)


#define FCALLSCFUN15(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF) \
        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,CF_0,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN16(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG) \
        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN17(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH) \
        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,CF_0,CF_0,CF_0)
#define FCALLSCFUN18(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI) \
        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,CF_0,CF_0)
#define FCALLSCFUN19(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ) \
        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,CF_0)
#define FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN21(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN22(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,CF_0,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN23(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,CF_0,CF_0,CF_0,CF_0)
#define FCALLSCFUN24(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,CF_0,CF_0,CF_0)
#define FCALLSCFUN25(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,CF_0,CF_0)
#define FCALLSCFUN26(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ) \
        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,CF_0)


#ifndef __CF__KnR
#define FCALLSCFUN0(T0,CN,UN,LN) CFextern _(T0,_cfFZ)(UN,LN) ABSOFT_cf2(T0))   \
        {_Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0) CN(); _Icf(0,K,T0,0,0) _(T0,_cfI)}

#define FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
                                 CFextern _(T0,_cfF)(UN,LN)                    \
 CFARGT14(NCF,DCF,ABSOFT_cf2(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) )  \
 {                 CFARGT14S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(    TCF(LN,T1,1,0)  TCF(LN,T2,2,1) \
    TCF(LN,T3,3,1)  TCF(LN,T4,4,1) TCF(LN,T5,5,1)  TCF(LN,T6,6,1)  TCF(LN,T7,7,1) \
    TCF(LN,T8,8,1)  TCF(LN,T9,9,1) TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
    TCF(LN,TD,13,1) TCF(LN,TE,14,1) );                          _Icf(0,K,T0,0,0) \
                   CFARGT14S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  _(T0,_cfI) }

#define FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)   \
                                 CFextern _(T0,_cfF)(UN,LN)                    \
 CFARGT27(NCF,DCF,ABSOFT_cf2(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) ) \
 {                 CFARGT27S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)   \
  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(     TCF(LN,T1,1,0)  TCF(LN,T2,2,1)  \
    TCF(LN,T3,3,1)  TCF(LN,T4,4,1)  TCF(LN,T5,5,1)  TCF(LN,T6,6,1)  TCF(LN,T7,7,1)  \
    TCF(LN,T8,8,1)  TCF(LN,T9,9,1)  TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
    TCF(LN,TD,13,1) TCF(LN,TE,14,1) TCF(LN,TF,15,1) TCF(LN,TG,16,1) TCF(LN,TH,17,1) \
    TCF(LN,TI,18,1) TCF(LN,TJ,19,1) TCF(LN,TK,20,1) TCF(LN,TL,21,1) TCF(LN,TM,22,1) \
    TCF(LN,TN,23,1) TCF(LN,TO,24,1) TCF(LN,TP,25,1) TCF(LN,TQ,26,1) TCF(LN,TR,27,1) ); _Icf(0,K,T0,0,0) \
                   CFARGT27S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  _(T0,_cfI) }

#else
#define FCALLSCFUN0(T0,CN,UN,LN) CFextern _(T0,_cfFZ)(UN,LN) ABSOFT_cf3(T0)) _Icf(0,FF,T0,0,0)\
        {_Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0) CN(); _Icf(0,K,T0,0,0) _(T0,_cfI)}

#define FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
                                 CFextern _(T0,_cfF)(UN,LN)                    \
 CFARGT14(NNCF,DDCF,ABSOFT_cf3(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)) _Icf(0,FF,T0,0,0) \
       CFARGT14FS(NNNCF,DDDCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE);   \
 {                 CFARGT14S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(  TCF(LN,T1,1,0) TCF(LN,T2,2,1) \
    TCF(LN,T3,3,1) TCF(LN,T4,4,1) TCF(LN,T5,5,1) TCF(LN,T6,6,1) TCF(LN,T7,7,1) \
    TCF(LN,T8,8,1) TCF(LN,T9,9,1) TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
    TCF(LN,TD,13,1) TCF(LN,TE,14,1) );                          _Icf(0,K,T0,0,0) \
                   CFARGT14S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  _(T0,_cfI)}

#define FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  \
                                 CFextern _(T0,_cfF)(UN,LN)                    \
 CFARGT27(NNCF,DDCF,ABSOFT_cf3(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)) _Icf(0,FF,T0,0,0) \
       CFARGT27FS(NNNCF,DDDCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR); \
 {                 CFARGT27S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  \
  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(     TCF(LN,T1,1,0)  TCF(LN,T2,2,1)  \
    TCF(LN,T3,3,1)  TCF(LN,T4,4,1)  TCF(LN,T5,5,1)  TCF(LN,T6,6,1)  TCF(LN,T7,7,1)  \
    TCF(LN,T8,8,1)  TCF(LN,T9,9,1)  TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
    TCF(LN,TD,13,1) TCF(LN,TE,14,1) TCF(LN,TF,15,1) TCF(LN,TG,16,1) TCF(LN,TH,17,1) \
    TCF(LN,TI,18,1) TCF(LN,TJ,19,1) TCF(LN,TK,20,1) TCF(LN,TL,21,1) TCF(LN,TM,22,1) \
    TCF(LN,TN,23,1) TCF(LN,TO,24,1) TCF(LN,TP,25,1) TCF(LN,TQ,26,1) TCF(LN,TR,27,1) ); _Icf(0,K,T0,0,0) \
                   CFARGT27S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  _(T0,_cfI)}

#endif

// clang-format on

#endif /* __CFORTRAN_LOADED */
#endif
// Automatically generated by make_fint.c, don't edit!

// clang-format off

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifndef CDI_H_
#include "cdi.h"
#endif

#ifdef HAVE_CF_INTERFACE

#include <limits.h>
#include <assert.h>

#ifndef __CFORTRAN_LOADED
#  if defined __clang__
#    pragma GCC diagnostic push
#    pragma GCC diagnostic ignored "-Wreserved-id-macro"
#  endif
#  include "cfortran.h"
#  if defined __clang__
#    pragma GCC diagnostic pop
#  endif
#endif
/* These functions are meant to be called from Fortran and don't
 * need an interface declaration in a C header. */
#ifdef __clang__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic ignored "-Wmissing-prototypes"
#endif

#ifdef CDI_H_

static inline
int SizeType_c2f(SizeType value_SizeType)
{
  assert(value_SizeType < INT_MAX);
  return (int) value_SizeType;
}

#endif

/*  Start of fortran interface for the following routines (make_fint.c)  */


/*  Byte order  */


/*  Error identifier  */


/*  File types  */


/*  Compatibility defines for release 1.8.3 (obsolete defines)  */


/*  Protocols (in filename/URI)  */


/*  Compress types  */


/*  external data types  */


/*  Compatibility defines for release 1.8.3 (obsolete defines)  */


/*  internal data types  */


/*  Chunk types  */


/*  GRID types  */


/*  ZAXIS types  */


/*  SUBTYPE types  */


/*  Data structure defining a key-value search, possibly with multiple
   key-value pairs in combination.

   Currently, only multiple pairs combined by AND are supported.  */


/*  TIME types  */


/*  TSTEP types  */


/*  TAXIS types  */


/*  TUNIT types  */


/*  CALENDAR types  */


/*  number of unsigned char needed to store UUID  */


/*  Structs that are used to return data to the user  */


/*  Opaque types  */


/*  CDI control routines  */

FCALLSCSUB0 (cdiReset, CDIRESET, cdireset)
FCALLSCFUN1 (STRING, cdiStringError, CDISTRINGERROR, cdistringerror, INT)
FCALLSCSUB1 (cdiDebug, CDIDEBUG, cdidebug, INT)
FCALLSCFUN0 (STRING, cdiLibraryVersion, CDILIBRARYVERSION, cdilibraryversion)
FCALLSCSUB0 (cdiPrintVersion, CDIPRINTVERSION, cdiprintversion)
FCALLSCFUN1 (INT, cdiHaveFiletype, CDIHAVEFILETYPE, cdihavefiletype, INT)
FCALLSCFUN1 (INT, cdiGetConfig, CDIGETCONFIG, cdigetconfig, INT)
FCALLSCSUB1 (cdiDefMissval, CDIDEFMISSVAL, cdidefmissval, DOUBLE)
FCALLSCFUN0 (DOUBLE, cdiInqMissval, CDIINQMISSVAL, cdiinqmissval)
FCALLSCSUB2 (cdiDefGlobal, CDIDEFGLOBAL, cdidefglobal, STRING, INT)
FCALLSCFUN0 (INT, namespaceNew, NAMESPACENEW, namespacenew)
FCALLSCSUB1 (namespaceSetActive, NAMESPACESETACTIVE, namespacesetactive, INT)
FCALLSCFUN0 (INT, namespaceGetActive, NAMESPACEGETACTIVE, namespacegetactive)
FCALLSCSUB1 (namespaceDelete, NAMESPACEDELETE, namespacedelete, INT)

/*  CDI converter routines  */


/*  parameter  */

FCALLSCSUB3 (cdiParamToString, CDIPARAMTOSTRING, cdiparamtostring, INT, PSTRING, INT)
FCALLSCSUB4 (cdiDecodeParam, CDIDECODEPARAM, cdidecodeparam, INT, PINT, PINT, PINT)
FCALLSCFUN3 (INT, cdiEncodeParam, CDIENCODEPARAM, cdiencodeparam, INT, INT, INT)

/*  date format:  YYYYMMDD  */


/*  time format:    hhmmss  */

FCALLSCSUB4 (cdiDecodeDate, CDIDECODEDATE, cdidecodedate, INT, PINT, PINT, PINT)
FCALLSCFUN3 (INT, cdiEncodeDate, CDIENCODEDATE, cdiencodedate, INT, INT, INT)
FCALLSCSUB4 (cdiDecodeTime, CDIDECODETIME, cdidecodetime, INT, PINT, PINT, PINT)
FCALLSCFUN3 (INT, cdiEncodeTime, CDIENCODETIME, cdiencodetime, INT, INT, INT)

/*  STREAM control routines  */

FCALLSCFUN2 (INT, cdiGetFiletype, CDIGETFILETYPE, cdigetfiletype, STRING, PINT)
FCALLSCFUN1 (INT, streamOpenReadNCMem, STREAMOPENREADNCMEM, streamopenreadncmem, INT)
FCALLSCFUN1 (INT, streamOpenWriteNCMem, STREAMOPENWRITENCMEM, streamopenwritencmem, INT)
FCALLSCSUB1 (streamCloseNCMem, STREAMCLOSENCMEM, streamclosencmem, INT)
FCALLSCFUN1 (INT, streamOpenRead, STREAMOPENREAD, streamopenread, STRING)
FCALLSCFUN2 (INT, streamOpenWrite, STREAMOPENWRITE, streamopenwrite, STRING, INT)
FCALLSCFUN1 (INT, streamOpenAppend, STREAMOPENAPPEND, streamopenappend, STRING)
FCALLSCSUB1 (streamClose, STREAMCLOSE, streamclose, INT)
FCALLSCSUB1 (streamSync, STREAMSYNC, streamsync, INT)
FCALLSCSUB2 (streamDefMaxSteps, STREAMDEFMAXSTEPS, streamdefmaxsteps, INT, INT)
FCALLSCSUB2 (streamDefNumWorker, STREAMDEFNUMWORKER, streamdefnumworker, INT, INT)
FCALLSCFUN1 (INT, streamInqNumSteps, STREAMINQNUMSTEPS, streaminqnumsteps, INT)
FCALLSCSUB2 (streamDefVlist, STREAMDEFVLIST, streamdefvlist, INT, INT)
FCALLSCFUN1 (INT, streamInqVlist, STREAMINQVLIST, streaminqvlist, INT)
FCALLSCFUN1 (INT, streamInqFiletype, STREAMINQFILETYPE, streaminqfiletype, INT)
FCALLSCSUB2 (streamDefByteorder, STREAMDEFBYTEORDER, streamdefbyteorder, INT, INT)
FCALLSCFUN1 (INT, streamInqByteorder, STREAMINQBYTEORDER, streaminqbyteorder, INT)
FCALLSCSUB2 (streamDefShuffle, STREAMDEFSHUFFLE, streamdefshuffle, INT, INT)
FCALLSCSUB2 (streamDefFilter, STREAMDEFFILTER, streamdeffilter, INT, STRING)
FCALLSCSUB2 (streamDefCompType, STREAMDEFCOMPTYPE, streamdefcomptype, INT, INT)
FCALLSCFUN1 (INT, streamInqCompType, STREAMINQCOMPTYPE, streaminqcomptype, INT)
FCALLSCSUB2 (streamDefCompLevel, STREAMDEFCOMPLEVEL, streamdefcomplevel, INT, INT)
FCALLSCFUN1 (INT, streamInqCompLevel, STREAMINQCOMPLEVEL, streaminqcomplevel, INT)
FCALLSCFUN2 (INT, streamDefTimestep, STREAMDEFTIMESTEP, streamdeftimestep, INT, INT)
FCALLSCFUN2 (INT, streamInqTimestep, STREAMINQTIMESTEP, streaminqtimestep, INT, INT)
FCALLSCFUN1 (INT, streamInqCurTimestepID, STREAMINQCURTIMESTEPID, streaminqcurtimestepid, INT)
FCALLSCFUN1 (STRING, streamFilename, STREAMFILENAME, streamfilename, INT)
FCALLSCFUN1 (STRING, streamFilesuffix, STREAMFILESUFFIX, streamfilesuffix, INT)
static int streamNvals_fwrap(int streamID)
{
  SizeType v;
  v = streamNvals(streamID);
  return SizeType_c2f(v);
}
FCALLSCFUN1 (INT, streamNvals_fwrap, STREAMNVALS, streamnvals, INT)
FCALLSCFUN1 (INT, streamInqNvars, STREAMINQNVARS, streaminqnvars, INT)

/*  STREAM var I/O routines (random access)  */

static void streamWriteVar_fwrap(int streamID, int varID, const double data[], int numMissVals)
{
  streamWriteVar(streamID, varID, data, (SizeType)numMissVals);
}
FCALLSCSUB4 (streamWriteVar_fwrap, STREAMWRITEVAR, streamwritevar, INT, INT, DOUBLEV, INT)
static void streamWriteVarF_fwrap(int streamID, int varID, const float data[], int numMissVals)
{
  streamWriteVarF(streamID, varID, data, (SizeType)numMissVals);
}
FCALLSCSUB4 (streamWriteVarF_fwrap, STREAMWRITEVARF, streamwritevarf, INT, INT, FLOATV, INT)
static void streamReadVar_fwrap(int streamID, int varID, double data[], int *numMissVals)
{
  SizeType numMissVals_SizeType;
  streamReadVar(streamID, varID, data, &numMissVals_SizeType);
  assert(numMissVals_SizeType < INT_MAX);
  *numMissVals = numMissVals_SizeType;
}
FCALLSCSUB4 (streamReadVar_fwrap, STREAMREADVAR, streamreadvar, INT, INT, DOUBLEV, PINT)
static void streamReadVarF_fwrap(int streamID, int varID, float data[], int *numMissVals)
{
  SizeType numMissVals_SizeType;
  streamReadVarF(streamID, varID, data, &numMissVals_SizeType);
  assert(numMissVals_SizeType < INT_MAX);
  *numMissVals = numMissVals_SizeType;
}
FCALLSCSUB4 (streamReadVarF_fwrap, STREAMREADVARF, streamreadvarf, INT, INT, FLOATV, PINT)
static void streamWriteVarSlice_fwrap(int streamID, int varID, int levelID, const double data[], int numMissVals)
{
  streamWriteVarSlice(streamID, varID, levelID, data, (SizeType)numMissVals);
}
FCALLSCSUB5 (streamWriteVarSlice_fwrap, STREAMWRITEVARSLICE, streamwritevarslice, INT, INT, INT, DOUBLEV, INT)
static void streamWriteVarSliceF_fwrap(int streamID, int varID, int levelID, const float data[], int numMissVals)
{
  streamWriteVarSliceF(streamID, varID, levelID, data, (SizeType)numMissVals);
}
FCALLSCSUB5 (streamWriteVarSliceF_fwrap, STREAMWRITEVARSLICEF, streamwritevarslicef, INT, INT, INT, FLOATV, INT)
static void streamReadVarSlice_fwrap(int streamID, int varID, int levelID, double data[], int *numMissVals)
{
  SizeType numMissVals_SizeType;
  streamReadVarSlice(streamID, varID, levelID, data, &numMissVals_SizeType);
  assert(numMissVals_SizeType < INT_MAX);
  *numMissVals = numMissVals_SizeType;
}
FCALLSCSUB5 (streamReadVarSlice_fwrap, STREAMREADVARSLICE, streamreadvarslice, INT, INT, INT, DOUBLEV, PINT)
static void streamReadVarSliceF_fwrap(int streamID, int varID, int levelID, float data[], int *numMissVals)
{
  SizeType numMissVals_SizeType;
  streamReadVarSliceF(streamID, varID, levelID, data, &numMissVals_SizeType);
  assert(numMissVals_SizeType < INT_MAX);
  *numMissVals = numMissVals_SizeType;
}
FCALLSCSUB5 (streamReadVarSliceF_fwrap, STREAMREADVARSLICEF, streamreadvarslicef, INT, INT, INT, FLOATV, PINT)
static void streamWriteVarChunk_fwrap(int streamID, int varID, const int rect[][2], const double data[], int numMissVals)
{
  streamWriteVarChunk(streamID, varID, rect, data, (SizeType)numMissVals);
}
FCALLSCSUB5 (streamWriteVarChunk_fwrap, STREAMWRITEVARCHUNK, streamwritevarchunk, INT, INT, INTVV, DOUBLEV, INT)
static void streamWriteVarChunkF_fwrap(int streamID, int varID, const int rect[][2], const float data[], int numMissVals)
{
  streamWriteVarChunkF(streamID, varID, rect, data, (SizeType)numMissVals);
}
FCALLSCSUB5 (streamWriteVarChunkF_fwrap, STREAMWRITEVARCHUNKF, streamwritevarchunkf, INT, INT, INTVV, FLOATV, INT)

/*  STREAM field I/O routines (sequential access)  */

FCALLSCSUB3 (streamDefField, STREAMDEFFIELD, streamdeffield, INT, INT, INT)
FCALLSCSUB3 (streamInqField, STREAMINQFIELD, streaminqfield, INT, PINT, PINT)
static void streamWriteField_fwrap(int streamID, const double data[], int numMissVals)
{
  streamWriteField(streamID, data, (SizeType)numMissVals);
}
FCALLSCSUB3 (streamWriteField_fwrap, STREAMWRITEFIELD, streamwritefield, INT, DOUBLEV, INT)
static void streamWriteFieldF_fwrap(int streamID, const float data[], int numMissVals)
{
  streamWriteFieldF(streamID, data, (SizeType)numMissVals);
}
FCALLSCSUB3 (streamWriteFieldF_fwrap, STREAMWRITEFIELDF, streamwritefieldf, INT, FLOATV, INT)
static void streamReadField_fwrap(int streamID, double data[], int *numMissVals)
{
  SizeType numMissVals_SizeType;
  streamReadField(streamID, data, &numMissVals_SizeType);
  assert(numMissVals_SizeType < INT_MAX);
  *numMissVals = numMissVals_SizeType;
}
FCALLSCSUB3 (streamReadField_fwrap, STREAMREADFIELD, streamreadfield, INT, DOUBLEV, PINT)
static void streamReadFieldF_fwrap(int streamID, float data[], int *numMissVals)
{
  SizeType numMissVals_SizeType;
  streamReadFieldF(streamID, data, &numMissVals_SizeType);
  assert(numMissVals_SizeType < INT_MAX);
  *numMissVals = numMissVals_SizeType;
}
FCALLSCSUB3 (streamReadFieldF_fwrap, STREAMREADFIELDF, streamreadfieldf, INT, FLOATV, PINT)
FCALLSCSUB2 (streamCopyField, STREAMCOPYFIELD, streamcopyfield, INT, INT)

/*  File driven I/O (may yield better performance than using the streamXXX functions)  */


/*  Creation & Destruction  */


/*  Advancing an iterator  */


/*  Introspecting metadata  */


/*  All outXXX arguments to these functions may be NULL.  */


/*  Reading data  */


/*  TODO[NH]: Add functions to read partial fields.  */


/*  Direct access to grib fields  */


/*  Callthroughs to GRIB-API  */


/*  Convenience functions for accessing GRIB-API keys  */


/*  VLIST routines  */

FCALLSCFUN0 (INT, vlistCreate, VLISTCREATE, vlistcreate)
FCALLSCSUB1 (vlistDestroy, VLISTDESTROY, vlistdestroy, INT)
FCALLSCFUN1 (INT, vlistDuplicate, VLISTDUPLICATE, vlistduplicate, INT)
FCALLSCSUB2 (vlistCopyFlag, VLISTCOPYFLAG, vlistcopyflag, INT, INT)
FCALLSCSUB1 (vlistClearFlag, VLISTCLEARFLAG, vlistclearflag, INT)
FCALLSCSUB2 (vlistCat, VLISTCAT, vlistcat, INT, INT)
FCALLSCSUB2 (vlistMerge, VLISTMERGE, vlistmerge, INT, INT)
FCALLSCSUB1 (vlistPrint, VLISTPRINT, vlistprint, INT)
FCALLSCFUN1 (INT, vlistNumber, VLISTNUMBER, vlistnumber, INT)
FCALLSCFUN1 (INT, vlistNvars, VLISTNVARS, vlistnvars, INT)
FCALLSCFUN1 (INT, vlistNumGrids, VLISTNUMGRIDS, vlistnumgrids, INT)
FCALLSCFUN1 (INT, vlistNumZaxis, VLISTNUMZAXIS, vlistnumzaxis, INT)
FCALLSCFUN1 (INT, vlistNsubtypes, VLISTNSUBTYPES, vlistnsubtypes, INT)
FCALLSCSUB2 (vlistDefNtsteps, VLISTDEFNTSTEPS, vlistdefntsteps, INT, INT)
FCALLSCFUN1 (INT, vlistNtsteps, VLISTNTSTEPS, vlistntsteps, INT)
static int vlistGridsizeMax_fwrap(int vlistID)
{
  SizeType v;
  v = vlistGridsizeMax(vlistID);
  return SizeType_c2f(v);
}
FCALLSCFUN1 (INT, vlistGridsizeMax_fwrap, VLISTGRIDSIZEMAX, vlistgridsizemax, INT)
FCALLSCFUN2 (INT, vlistGrid, VLISTGRID, vlistgrid, INT, INT)
FCALLSCFUN2 (INT, vlistGridIndex, VLISTGRIDINDEX, vlistgridindex, INT, INT)
FCALLSCSUB3 (vlistChangeGridIndex, VLISTCHANGEGRIDINDEX, vlistchangegridindex, INT, INT, INT)
FCALLSCSUB3 (vlistChangeGrid, VLISTCHANGEGRID, vlistchangegrid, INT, INT, INT)
FCALLSCFUN2 (INT, vlistZaxis, VLISTZAXIS, vlistzaxis, INT, INT)
FCALLSCFUN2 (INT, vlistZaxisIndex, VLISTZAXISINDEX, vlistzaxisindex, INT, INT)
FCALLSCSUB3 (vlistChangeZaxisIndex, VLISTCHANGEZAXISINDEX, vlistchangezaxisindex, INT, INT, INT)
FCALLSCSUB3 (vlistChangeZaxis, VLISTCHANGEZAXIS, vlistchangezaxis, INT, INT, INT)
FCALLSCFUN1 (INT, vlistNumFields, VLISTNUMFIELDS, vlistnumfields, INT)
FCALLSCFUN2 (INT, vlistSubtype, VLISTSUBTYPE, vlistsubtype, INT, INT)
FCALLSCFUN2 (INT, vlistSubtypeIndex, VLISTSUBTYPEINDEX, vlistsubtypeindex, INT, INT)
FCALLSCSUB2 (vlistDefTaxis, VLISTDEFTAXIS, vlistdeftaxis, INT, INT)
FCALLSCFUN1 (INT, vlistInqTaxis, VLISTINQTAXIS, vlistinqtaxis, INT)
FCALLSCSUB2 (vlistDefTable, VLISTDEFTABLE, vlistdeftable, INT, INT)
FCALLSCFUN1 (INT, vlistInqTable, VLISTINQTABLE, vlistinqtable, INT)
FCALLSCSUB2 (vlistDefInstitut, VLISTDEFINSTITUT, vlistdefinstitut, INT, INT)
FCALLSCFUN1 (INT, vlistInqInstitut, VLISTINQINSTITUT, vlistinqinstitut, INT)
FCALLSCSUB2 (vlistDefModel, VLISTDEFMODEL, vlistdefmodel, INT, INT)
FCALLSCFUN1 (INT, vlistInqModel, VLISTINQMODEL, vlistinqmodel, INT)

/*  VLIST VAR routines  */

FCALLSCFUN5 (INT, vlistDefVarTiles, VLISTDEFVARTILES, vlistdefvartiles, INT, INT, INT, INT, INT)
FCALLSCFUN4 (INT, vlistDefVar, VLISTDEFVAR, vlistdefvar, INT, INT, INT, INT)
FCALLSCSUB3 (vlistChangeVarGrid, VLISTCHANGEVARGRID, vlistchangevargrid, INT, INT, INT)
FCALLSCSUB3 (vlistChangeVarZaxis, VLISTCHANGEVARZAXIS, vlistchangevarzaxis, INT, INT, INT)
FCALLSCSUB5 (vlistInqVar, VLISTINQVAR, vlistinqvar, INT, INT, PINT, PINT, PINT)
FCALLSCFUN2 (INT, vlistInqVarGrid, VLISTINQVARGRID, vlistinqvargrid, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarZaxis, VLISTINQVARZAXIS, vlistinqvarzaxis, INT, INT)

/*  used in MPIOM  */

FCALLSCFUN2 (INT, vlistInqVarID, VLISTINQVARID, vlistinqvarid, INT, INT)
FCALLSCSUB3 (vlistDefVarTimetype, VLISTDEFVARTIMETYPE, vlistdefvartimetype, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarTimetype, VLISTINQVARTIMETYPE, vlistinqvartimetype, INT, INT)
FCALLSCSUB3 (vlistDefVarTsteptype, VLISTDEFVARTSTEPTYPE, vlistdefvartsteptype, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarTsteptype, VLISTINQVARTSTEPTYPE, vlistinqvartsteptype, INT, INT)
FCALLSCSUB3 (vlistDefVarCompType, VLISTDEFVARCOMPTYPE, vlistdefvarcomptype, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarCompType, VLISTINQVARCOMPTYPE, vlistinqvarcomptype, INT, INT)
FCALLSCSUB3 (vlistDefVarCompLevel, VLISTDEFVARCOMPLEVEL, vlistdefvarcomplevel, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarCompLevel, VLISTINQVARCOMPLEVEL, vlistinqvarcomplevel, INT, INT)
FCALLSCSUB3 (vlistDefVarParam, VLISTDEFVARPARAM, vlistdefvarparam, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarParam, VLISTINQVARPARAM, vlistinqvarparam, INT, INT)
FCALLSCSUB3 (vlistDefVarCode, VLISTDEFVARCODE, vlistdefvarcode, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarCode, VLISTINQVARCODE, vlistinqvarcode, INT, INT)
FCALLSCSUB3 (vlistDefVarDatatype, VLISTDEFVARDATATYPE, vlistdefvardatatype, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarDatatype, VLISTINQVARDATATYPE, vlistinqvardatatype, INT, INT)
FCALLSCSUB3 (vlistDefVarXYZ, VLISTDEFVARXYZ, vlistdefvarxyz, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarXYZ, VLISTINQVARXYZ, vlistinqvarxyz, INT, INT)
FCALLSCSUB3 (vlistDefVarNSB, VLISTDEFVARNSB, vlistdefvarnsb, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarNSB, VLISTINQVARNSB, vlistinqvarnsb, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarNumber, VLISTINQVARNUMBER, vlistinqvarnumber, INT, INT)
FCALLSCSUB3 (vlistDefVarInstitut, VLISTDEFVARINSTITUT, vlistdefvarinstitut, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarInstitut, VLISTINQVARINSTITUT, vlistinqvarinstitut, INT, INT)
FCALLSCSUB3 (vlistDefVarModel, VLISTDEFVARMODEL, vlistdefvarmodel, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarModel, VLISTINQVARMODEL, vlistinqvarmodel, INT, INT)
FCALLSCSUB3 (vlistDefVarTable, VLISTDEFVARTABLE, vlistdefvartable, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarTable, VLISTINQVARTABLE, vlistinqvartable, INT, INT)
FCALLSCSUB3 (vlistDefVarName, VLISTDEFVARNAME, vlistdefvarname, INT, INT, STRING)
FCALLSCSUB3 (vlistInqVarName, VLISTINQVARNAME, vlistinqvarname, INT, INT, PSTRING)
FCALLSCFUN2 (STRING, vlistCopyVarName, VLISTCOPYVARNAME, vlistcopyvarname, INT, INT)
FCALLSCSUB3 (vlistDefVarStdname, VLISTDEFVARSTDNAME, vlistdefvarstdname, INT, INT, STRING)
FCALLSCSUB3 (vlistInqVarStdname, VLISTINQVARSTDNAME, vlistinqvarstdname, INT, INT, PSTRING)
FCALLSCSUB3 (vlistDefVarLongname, VLISTDEFVARLONGNAME, vlistdefvarlongname, INT, INT, STRING)
FCALLSCSUB3 (vlistInqVarLongname, VLISTINQVARLONGNAME, vlistinqvarlongname, INT, INT, PSTRING)
FCALLSCSUB3 (vlistDefVarUnits, VLISTDEFVARUNITS, vlistdefvarunits, INT, INT, STRING)
FCALLSCSUB3 (vlistInqVarUnits, VLISTINQVARUNITS, vlistinqvarunits, INT, INT, PSTRING)
FCALLSCSUB3 (vlistDefVarMissval, VLISTDEFVARMISSVAL, vlistdefvarmissval, INT, INT, DOUBLE)
FCALLSCFUN2 (DOUBLE, vlistInqVarMissval, VLISTINQVARMISSVAL, vlistinqvarmissval, INT, INT)
static int vlistInqVarSize_fwrap(int vlistID, int varID)
{
  SizeType v;
  v = vlistInqVarSize(vlistID, varID);
  return SizeType_c2f(v);
}
FCALLSCFUN2 (INT, vlistInqVarSize_fwrap, VLISTINQVARSIZE, vlistinqvarsize, INT, INT)
FCALLSCSUB4 (vlistDefIndex, VLISTDEFINDEX, vlistdefindex, INT, INT, INT, INT)
FCALLSCFUN3 (INT, vlistInqIndex, VLISTINQINDEX, vlistinqindex, INT, INT, INT)
FCALLSCSUB4 (vlistDefFlag, VLISTDEFFLAG, vlistdefflag, INT, INT, INT, INT)
FCALLSCFUN3 (INT, vlistInqFlag, VLISTINQFLAG, vlistinqflag, INT, INT, INT)
FCALLSCFUN2 (INT, vlistFindVar, VLISTFINDVAR, vlistfindvar, INT, INT)
FCALLSCFUN3 (INT, vlistFindLevel, VLISTFINDLEVEL, vlistfindlevel, INT, INT, INT)
FCALLSCFUN2 (INT, vlistMergedVar, VLISTMERGEDVAR, vlistmergedvar, INT, INT)
FCALLSCFUN3 (INT, vlistMergedLevel, VLISTMERGEDLEVEL, vlistmergedlevel, INT, INT, INT)
FCALLSCSUB0 (cdiClearAdditionalKeys, CDICLEARADDITIONALKEYS, cdiclearadditionalkeys)
FCALLSCSUB1 (cdiDefAdditionalKey, CDIDEFADDITIONALKEY, cdidefadditionalkey, STRING)
FCALLSCSUB4 (vlistDefVarIntKey, VLISTDEFVARINTKEY, vlistdefvarintkey, INT, INT, STRING, INT)
FCALLSCSUB4 (vlistDefVarDblKey, VLISTDEFVARDBLKEY, vlistdefvardblkey, INT, INT, STRING, DOUBLE)
FCALLSCSUB5 (vlistDefVarIntArrKey, VLISTDEFVARINTARRKEY, vlistdefvarintarrkey, INT, INT, STRING, PINT, INT)
FCALLSCSUB5 (vlistDefVarDblArrKey, VLISTDEFVARDBLARRKEY, vlistdefvardblarrkey, INT, INT, STRING, PDOUBLE, INT)
FCALLSCFUN3 (INT, vlistHasVarKey, VLISTHASVARKEY, vlisthasvarkey, INT, INT, STRING)
FCALLSCFUN3 (DOUBLE, vlistInqVarDblKey, VLISTINQVARDBLKEY, vlistinqvardblkey, INT, INT, STRING)
FCALLSCFUN3 (INT, vlistInqVarIntKey, VLISTINQVARINTKEY, vlistinqvarintkey, INT, INT, STRING)

/*  CDI attributes  */

FCALLSCFUN3 (INT, cdiInqNatts, CDIINQNATTS, cdiinqnatts, INT, INT, PINT)
FCALLSCFUN6 (INT, cdiInqAtt, CDIINQATT, cdiinqatt, INT, INT, INT, PSTRING, PINT, PINT)
FCALLSCFUN3 (INT, cdiInqAttLen, CDIINQATTLEN, cdiinqattlen, INT, INT, STRING)
FCALLSCFUN3 (INT, cdiInqAttType, CDIINQATTTYPE, cdiinqatttype, INT, INT, STRING)
FCALLSCFUN3 (INT, cdiDelAtt, CDIDELATT, cdidelatt, INT, INT, STRING)
FCALLSCFUN4 (INT, cdiCopyAtts, CDICOPYATTS, cdicopyatts, INT, INT, INT, INT)
FCALLSCFUN6 (INT, cdiDefAttInt, CDIDEFATTINT, cdidefattint, INT, INT, STRING, INT, INT, INTV)
FCALLSCFUN6 (INT, cdiDefAttFlt, CDIDEFATTFLT, cdidefattflt, INT, INT, STRING, INT, INT, DOUBLEV)
FCALLSCFUN5 (INT, cdiDefAttTxt, CDIDEFATTTXT, cdidefatttxt, INT, INT, STRING, INT, PPSTRING)
FCALLSCFUN5 (INT, cdiInqAttInt, CDIINQATTINT, cdiinqattint, INT, INT, STRING, INT, INTV)
FCALLSCFUN5 (INT, cdiInqAttFlt, CDIINQATTFLT, cdiinqattflt, INT, INT, STRING, INT, DOUBLEV)
FCALLSCFUN5 (INT, cdiInqAttTxt, CDIINQATTTXT, cdiinqatttxt, INT, INT, STRING, INT, PPSTRING)

/*  GRID routines  */

FCALLSCSUB2 (gridName, GRIDNAME, gridname, INT, PSTRING)
FCALLSCFUN1 (STRING, gridNamePtr, GRIDNAMEPTR, gridnameptr, INT)
FCALLSCSUB1 (gridCompress, GRIDCOMPRESS, gridcompress, INT)
FCALLSCSUB2 (gridDefMaskGME, GRIDDEFMASKGME, griddefmaskgme, INT, INTV)
FCALLSCFUN2 (INT, gridInqMaskGME, GRIDINQMASKGME, gridinqmaskgme, INT, INTV)
FCALLSCSUB2 (gridDefMask, GRIDDEFMASK, griddefmask, INT, INTV)
FCALLSCFUN2 (INT, gridInqMask, GRIDINQMASK, gridinqmask, INT, INTV)
static int gridCreate_fwrap(int gridtype, int size)
{
  int v;
  v = gridCreate(gridtype, (SizeType)size);
  return v;
}
FCALLSCFUN2 (INT, gridCreate_fwrap, GRIDCREATE, gridcreate, INT, INT)
FCALLSCSUB1 (gridDestroy, GRIDDESTROY, griddestroy, INT)
FCALLSCFUN1 (INT, gridDuplicate, GRIDDUPLICATE, gridduplicate, INT)
FCALLSCSUB2 (gridDefProj, GRIDDEFPROJ, griddefproj, INT, INT)
FCALLSCFUN1 (INT, gridInqProj, GRIDINQPROJ, gridinqproj, INT)
FCALLSCFUN1 (INT, gridInqProjType, GRIDINQPROJTYPE, gridinqprojtype, INT)
FCALLSCFUN1 (INT, gridInqType, GRIDINQTYPE, gridinqtype, INT)
static int gridInqSize_fwrap(int gridID)
{
  SizeType v;
  v = gridInqSize(gridID);
  return SizeType_c2f(v);
}
FCALLSCFUN1 (INT, gridInqSize_fwrap, GRIDINQSIZE, gridinqsize, INT)
static void gridDefXsize_fwrap(int gridID, int xsize)
{
  gridDefXsize(gridID, (SizeType)xsize);
}
FCALLSCSUB2 (gridDefXsize_fwrap, GRIDDEFXSIZE, griddefxsize, INT, INT)
static int gridInqXsize_fwrap(int gridID)
{
  SizeType v;
  v = gridInqXsize(gridID);
  return SizeType_c2f(v);
}
FCALLSCFUN1 (INT, gridInqXsize_fwrap, GRIDINQXSIZE, gridinqxsize, INT)
static void gridDefYsize_fwrap(int gridID, int ysize)
{
  gridDefYsize(gridID, (SizeType)ysize);
}
FCALLSCSUB2 (gridDefYsize_fwrap, GRIDDEFYSIZE, griddefysize, INT, INT)
static int gridInqYsize_fwrap(int gridID)
{
  SizeType v;
  v = gridInqYsize(gridID);
  return SizeType_c2f(v);
}
FCALLSCFUN1 (INT, gridInqYsize_fwrap, GRIDINQYSIZE, gridinqysize, INT)
FCALLSCSUB2 (gridDefNP, GRIDDEFNP, griddefnp, INT, INT)
FCALLSCFUN1 (INT, gridInqNP, GRIDINQNP, gridinqnp, INT)
FCALLSCSUB2 (gridDefXvals, GRIDDEFXVALS, griddefxvals, INT, DOUBLEV)
static int gridInqXvals_fwrap(int gridID, double xvals[])
{
  SizeType v;
  v = gridInqXvals(gridID, xvals);
  return SizeType_c2f(v);
}
FCALLSCFUN2 (INT, gridInqXvals_fwrap, GRIDINQXVALS, gridinqxvals, INT, DOUBLEV)
static int gridInqXvalsPart_fwrap(int gridID, int start, int size, double xvals[])
{
  SizeType v;
  v = gridInqXvalsPart(gridID, start, (SizeType)size, xvals);
  return SizeType_c2f(v);
}
FCALLSCFUN4 (INT, gridInqXvalsPart_fwrap, GRIDINQXVALSPART, gridinqxvalspart, INT, INT, INT, DOUBLEV)
FCALLSCFUN1 (INT, gridInqXIsc, GRIDINQXISC, gridinqxisc, INT)
FCALLSCSUB2 (gridDefYvals, GRIDDEFYVALS, griddefyvals, INT, DOUBLEV)
static int gridInqYvals_fwrap(int gridID, double yvals[])
{
  SizeType v;
  v = gridInqYvals(gridID, yvals);
  return SizeType_c2f(v);
}
FCALLSCFUN2 (INT, gridInqYvals_fwrap, GRIDINQYVALS, gridinqyvals, INT, DOUBLEV)
static int gridInqYvalsPart_fwrap(int gridID, int start, int size, double yvals[])
{
  SizeType v;
  v = gridInqYvalsPart(gridID, start, (SizeType)size, yvals);
  return SizeType_c2f(v);
}
FCALLSCFUN4 (INT, gridInqYvalsPart_fwrap, GRIDINQYVALSPART, gridinqyvalspart, INT, INT, INT, DOUBLEV)
FCALLSCFUN1 (INT, gridInqYIsc, GRIDINQYISC, gridinqyisc, INT)

/*  CDI var keys  */


/*  String keys  */


/*  Integer keys  */


/*  Floating point keys  */


/*  Byte array keys  */

FCALLSCFUN4 (INT, cdiDefKeyInt, CDIDEFKEYINT, cdidefkeyint, INT, INT, INT, INT)
FCALLSCFUN4 (INT, cdiInqKeyInt, CDIINQKEYINT, cdiinqkeyint, INT, INT, INT, PINT)
FCALLSCFUN4 (INT, cdiDefKeyFloat, CDIDEFKEYFLOAT, cdidefkeyfloat, INT, INT, INT, DOUBLE)

/*  cdiInqKeyFloat Get a float value from a key  */

FCALLSCFUN4 (INT, cdiInqKeyFloat, CDIINQKEYFLOAT, cdiinqkeyfloat, INT, INT, INT, PDOUBLE)
FCALLSCFUN4 (INT, cdiDefKeyString, CDIDEFKEYSTRING, cdidefkeystring, INT, INT, INT, STRING)
FCALLSCFUN5 (INT, cdiInqKeyString, CDIINQKEYSTRING, cdiinqkeystring, INT, INT, INT, PSTRING, PINT)
FCALLSCFUN4 (INT, cdiInqKeyLen, CDIINQKEYLEN, cdiinqkeylen, INT, INT, INT, PINT)
FCALLSCFUN4 (INT, cdiCopyKeys, CDICOPYKEYS, cdicopykeys, INT, INT, INT, INT)
FCALLSCFUN4 (INT, cdiCopyKey, CDICOPYKEY, cdicopykey, INT, INT, INT, INT)
FCALLSCFUN3 (INT, cdiDeleteKey, CDIDELETEKEY, cdideletekey, INT, INT, INT)

/*  GRID routines  */

FCALLSCSUB2 (gridDefXname, GRIDDEFXNAME, griddefxname, INT, STRING)
FCALLSCSUB2 (gridInqXname, GRIDINQXNAME, gridinqxname, INT, PSTRING)
FCALLSCSUB2 (gridDefXlongname, GRIDDEFXLONGNAME, griddefxlongname, INT, STRING)
FCALLSCSUB2 (gridInqXlongname, GRIDINQXLONGNAME, gridinqxlongname, INT, PSTRING)
FCALLSCSUB2 (gridDefXunits, GRIDDEFXUNITS, griddefxunits, INT, STRING)
FCALLSCSUB2 (gridInqXunits, GRIDINQXUNITS, gridinqxunits, INT, PSTRING)
FCALLSCSUB2 (gridDefYname, GRIDDEFYNAME, griddefyname, INT, STRING)
FCALLSCSUB2 (gridInqYname, GRIDINQYNAME, gridinqyname, INT, PSTRING)
FCALLSCSUB2 (gridDefYlongname, GRIDDEFYLONGNAME, griddefylongname, INT, STRING)
FCALLSCSUB2 (gridInqYlongname, GRIDINQYLONGNAME, gridinqylongname, INT, PSTRING)
FCALLSCSUB2 (gridDefYunits, GRIDDEFYUNITS, griddefyunits, INT, STRING)
FCALLSCSUB2 (gridInqYunits, GRIDINQYUNITS, gridinqyunits, INT, PSTRING)
FCALLSCSUB2 (gridDefDatatype, GRIDDEFDATATYPE, griddefdatatype, INT, INT)
FCALLSCFUN1 (INT, gridInqDatatype, GRIDINQDATATYPE, gridinqdatatype, INT)
static double gridInqXval_fwrap(int gridID, int index)
{
  double v;
  v = gridInqXval(gridID, (SizeType)index);
  return v;
}
FCALLSCFUN2 (DOUBLE, gridInqXval_fwrap, GRIDINQXVAL, gridinqxval, INT, INT)
static double gridInqYval_fwrap(int gridID, int index)
{
  double v;
  v = gridInqYval(gridID, (SizeType)index);
  return v;
}
FCALLSCFUN2 (DOUBLE, gridInqYval_fwrap, GRIDINQYVAL, gridinqyval, INT, INT)
FCALLSCFUN1 (DOUBLE, gridInqXinc, GRIDINQXINC, gridinqxinc, INT)
FCALLSCFUN1 (DOUBLE, gridInqYinc, GRIDINQYINC, gridinqyinc, INT)
FCALLSCFUN1 (INT, gridIsCyclic, GRIDISCYCLIC, gridiscyclic, INT)
FCALLSCFUN1 (INT, gridInqTrunc, GRIDINQTRUNC, gridinqtrunc, INT)
FCALLSCSUB2 (gridDefTrunc, GRIDDEFTRUNC, griddeftrunc, INT, INT)

/*  Reference of an unstructured grid  */

FCALLSCSUB2 (gridDefNumber, GRIDDEFNUMBER, griddefnumber, INT, INT)
FCALLSCFUN1 (INT, gridInqNumber, GRIDINQNUMBER, gridinqnumber, INT)
FCALLSCSUB2 (gridDefPosition, GRIDDEFPOSITION, griddefposition, INT, INT)
FCALLSCFUN1 (INT, gridInqPosition, GRIDINQPOSITION, gridinqposition, INT)
FCALLSCSUB2 (gridDefReference, GRIDDEFREFERENCE, griddefreference, INT, STRING)
FCALLSCFUN2 (INT, gridInqReference, GRIDINQREFERENCE, gridinqreference, INT, PSTRING)
FCALLSCSUB2 (gridDefUUID, GRIDDEFUUID, griddefuuid, INT, PVOID)
FCALLSCSUB2 (gridInqUUID, GRIDINQUUID, gridinquuid, INT, PVOID)

/*  Rotated Lon/Lat grid  */

FCALLSCSUB4 (gridDefParamRLL, GRIDDEFPARAMRLL, griddefparamrll, INT, DOUBLE, DOUBLE, DOUBLE)
FCALLSCSUB4 (gridInqParamRLL, GRIDINQPARAMRLL, gridinqparamrll, INT, PDOUBLE, PDOUBLE, PDOUBLE)

/*  Hexagonal GME grid  */

FCALLSCSUB5 (gridDefParamGME, GRIDDEFPARAMGME, griddefparamgme, INT, INT, INT, INT, INT)
FCALLSCSUB5 (gridInqParamGME, GRIDINQPARAMGME, gridinqparamgme, INT, PINT, PINT, PINT, PINT)
FCALLSCSUB2 (gridDefArea, GRIDDEFAREA, griddefarea, INT, DOUBLEV)
FCALLSCSUB2 (gridInqArea, GRIDINQAREA, gridinqarea, INT, DOUBLEV)
FCALLSCFUN1 (INT, gridHasArea, GRIDHASAREA, gridhasarea, INT)
FCALLSCSUB2 (gridDefNvertex, GRIDDEFNVERTEX, griddefnvertex, INT, INT)
FCALLSCFUN1 (INT, gridInqNvertex, GRIDINQNVERTEX, gridinqnvertex, INT)
FCALLSCSUB2 (gridDefXbounds, GRIDDEFXBOUNDS, griddefxbounds, INT, DOUBLEV)
static int gridInqXbounds_fwrap(int gridID, double xbounds[])
{
  SizeType v;
  v = gridInqXbounds(gridID, xbounds);
  return SizeType_c2f(v);
}
FCALLSCFUN2 (INT, gridInqXbounds_fwrap, GRIDINQXBOUNDS, gridinqxbounds, INT, DOUBLEV)
static int gridInqXboundsPart_fwrap(int gridID, int start, int size, double xbounds[])
{
  SizeType v;
  v = gridInqXboundsPart(gridID, start, (SizeType)size, xbounds);
  return SizeType_c2f(v);
}
FCALLSCFUN4 (INT, gridInqXboundsPart_fwrap, GRIDINQXBOUNDSPART, gridinqxboundspart, INT, INT, INT, DOUBLEV)
FCALLSCSUB2 (gridDefYbounds, GRIDDEFYBOUNDS, griddefybounds, INT, DOUBLEV)
static int gridInqYbounds_fwrap(int gridID, double ybounds[])
{
  SizeType v;
  v = gridInqYbounds(gridID, ybounds);
  return SizeType_c2f(v);
}
FCALLSCFUN2 (INT, gridInqYbounds_fwrap, GRIDINQYBOUNDS, gridinqybounds, INT, DOUBLEV)
static int gridInqYboundsPart_fwrap(int gridID, int start, int size, double ybounds[])
{
  SizeType v;
  v = gridInqYboundsPart(gridID, start, (SizeType)size, ybounds);
  return SizeType_c2f(v);
}
FCALLSCFUN4 (INT, gridInqYboundsPart_fwrap, GRIDINQYBOUNDSPART, gridinqyboundspart, INT, INT, INT, DOUBLEV)
FCALLSCSUB3 (gridDefReducedPoints, GRIDDEFREDUCEDPOINTS, griddefreducedpoints, INT, INT, INTV)
FCALLSCSUB2 (gridInqReducedPoints, GRIDINQREDUCEDPOINTS, gridinqreducedpoints, INT, INTV)
FCALLSCSUB2 (gridChangeType, GRIDCHANGETYPE, gridchangetype, INT, INT)
FCALLSCSUB2 (gridDefComplexPacking, GRIDDEFCOMPLEXPACKING, griddefcomplexpacking, INT, INT)
FCALLSCFUN1 (INT, gridInqComplexPacking, GRIDINQCOMPLEXPACKING, gridinqcomplexpacking, INT)

/*  ZAXIS routines  */

FCALLSCSUB2 (zaxisName, ZAXISNAME, zaxisname, INT, PSTRING)
FCALLSCFUN1 (STRING, zaxisNamePtr, ZAXISNAMEPTR, zaxisnameptr, INT)
FCALLSCFUN2 (INT, zaxisCreate, ZAXISCREATE, zaxiscreate, INT, INT)
FCALLSCSUB1 (zaxisDestroy, ZAXISDESTROY, zaxisdestroy, INT)
FCALLSCFUN1 (INT, zaxisInqType, ZAXISINQTYPE, zaxisinqtype, INT)
FCALLSCFUN1 (INT, zaxisInqSize, ZAXISINQSIZE, zaxisinqsize, INT)
FCALLSCFUN1 (INT, zaxisDuplicate, ZAXISDUPLICATE, zaxisduplicate, INT)
FCALLSCSUB2 (zaxisDefLevels, ZAXISDEFLEVELS, zaxisdeflevels, INT, DOUBLEV)
FCALLSCFUN2 (INT, zaxisInqLevels, ZAXISINQLEVELS, zaxisinqlevels, INT, DOUBLEV)
FCALLSCFUN1 (INT, zaxisInqCLen, ZAXISINQCLEN, zaxisinqclen, INT)
FCALLSCSUB3 (zaxisDefLevel, ZAXISDEFLEVEL, zaxisdeflevel, INT, INT, DOUBLE)
FCALLSCFUN2 (DOUBLE, zaxisInqLevel, ZAXISINQLEVEL, zaxisinqlevel, INT, INT)
FCALLSCSUB2 (zaxisDefNlevRef, ZAXISDEFNLEVREF, zaxisdefnlevref, INT, INT)
FCALLSCFUN1 (INT, zaxisInqNlevRef, ZAXISINQNLEVREF, zaxisinqnlevref, INT)
FCALLSCSUB2 (zaxisDefNumber, ZAXISDEFNUMBER, zaxisdefnumber, INT, INT)
FCALLSCFUN1 (INT, zaxisInqNumber, ZAXISINQNUMBER, zaxisinqnumber, INT)
FCALLSCSUB2 (zaxisDefUUID, ZAXISDEFUUID, zaxisdefuuid, INT, PVOID)
FCALLSCSUB2 (zaxisInqUUID, ZAXISINQUUID, zaxisinquuid, INT, PVOID)
FCALLSCSUB2 (zaxisDefName, ZAXISDEFNAME, zaxisdefname, INT, STRING)
FCALLSCSUB2 (zaxisInqName, ZAXISINQNAME, zaxisinqname, INT, PSTRING)
FCALLSCSUB2 (zaxisDefLongname, ZAXISDEFLONGNAME, zaxisdeflongname, INT, STRING)
FCALLSCSUB2 (zaxisInqLongname, ZAXISINQLONGNAME, zaxisinqlongname, INT, PSTRING)
FCALLSCSUB2 (zaxisDefUnits, ZAXISDEFUNITS, zaxisdefunits, INT, STRING)
FCALLSCSUB2 (zaxisInqUnits, ZAXISINQUNITS, zaxisinqunits, INT, PSTRING)
FCALLSCSUB2 (zaxisInqStdname, ZAXISINQSTDNAME, zaxisinqstdname, INT, PSTRING)
FCALLSCSUB2 (zaxisDefDatatype, ZAXISDEFDATATYPE, zaxisdefdatatype, INT, INT)
FCALLSCFUN1 (INT, zaxisInqDatatype, ZAXISINQDATATYPE, zaxisinqdatatype, INT)
FCALLSCSUB2 (zaxisDefPositive, ZAXISDEFPOSITIVE, zaxisdefpositive, INT, INT)
FCALLSCFUN1 (INT, zaxisInqPositive, ZAXISINQPOSITIVE, zaxisinqpositive, INT)
FCALLSCSUB1 (zaxisDefScalar, ZAXISDEFSCALAR, zaxisdefscalar, INT)
FCALLSCFUN1 (INT, zaxisInqScalar, ZAXISINQSCALAR, zaxisinqscalar, INT)
FCALLSCSUB3 (zaxisDefVct, ZAXISDEFVCT, zaxisdefvct, INT, INT, DOUBLEV)
FCALLSCSUB2 (zaxisInqVct, ZAXISINQVCT, zaxisinqvct, INT, DOUBLEV)
FCALLSCFUN1 (INT, zaxisInqVctSize, ZAXISINQVCTSIZE, zaxisinqvctsize, INT)
FCALLSCSUB2 (zaxisDefLbounds, ZAXISDEFLBOUNDS, zaxisdeflbounds, INT, DOUBLEV)
FCALLSCFUN2 (INT, zaxisInqLbounds, ZAXISINQLBOUNDS, zaxisinqlbounds, INT, DOUBLEV)
FCALLSCFUN2 (DOUBLE, zaxisInqLbound, ZAXISINQLBOUND, zaxisinqlbound, INT, INT)
FCALLSCSUB2 (zaxisDefUbounds, ZAXISDEFUBOUNDS, zaxisdefubounds, INT, DOUBLEV)
FCALLSCFUN2 (INT, zaxisInqUbounds, ZAXISINQUBOUNDS, zaxisinqubounds, INT, DOUBLEV)
FCALLSCFUN2 (DOUBLE, zaxisInqUbound, ZAXISINQUBOUND, zaxisinqubound, INT, INT)
FCALLSCSUB2 (zaxisDefWeights, ZAXISDEFWEIGHTS, zaxisdefweights, INT, DOUBLEV)
FCALLSCFUN2 (INT, zaxisInqWeights, ZAXISINQWEIGHTS, zaxisinqweights, INT, DOUBLEV)
FCALLSCSUB2 (zaxisChangeType, ZAXISCHANGETYPE, zaxischangetype, INT, INT)

/*  TAXIS routines  */

FCALLSCFUN1 (INT, taxisCreate, TAXISCREATE, taxiscreate, INT)
FCALLSCSUB1 (taxisDestroy, TAXISDESTROY, taxisdestroy, INT)
FCALLSCFUN1 (INT, taxisDuplicate, TAXISDUPLICATE, taxisduplicate, INT)
FCALLSCSUB2 (taxisCopyTimestep, TAXISCOPYTIMESTEP, taxiscopytimestep, INT, INT)
FCALLSCSUB2 (taxisDefType, TAXISDEFTYPE, taxisdeftype, INT, INT)
FCALLSCFUN1 (INT, taxisInqType, TAXISINQTYPE, taxisinqtype, INT)
FCALLSCSUB2 (taxisDefVdate, TAXISDEFVDATE, taxisdefvdate, INT, INT)
FCALLSCSUB2 (taxisDefVtime, TAXISDEFVTIME, taxisdefvtime, INT, INT)
FCALLSCFUN1 (INT, taxisInqVdate, TAXISINQVDATE, taxisinqvdate, INT)
FCALLSCFUN1 (INT, taxisInqVtime, TAXISINQVTIME, taxisinqvtime, INT)
FCALLSCSUB2 (taxisDefRdate, TAXISDEFRDATE, taxisdefrdate, INT, INT)
FCALLSCSUB2 (taxisDefRtime, TAXISDEFRTIME, taxisdefrtime, INT, INT)
FCALLSCFUN1 (INT, taxisInqRdate, TAXISINQRDATE, taxisinqrdate, INT)
FCALLSCFUN1 (INT, taxisInqRtime, TAXISINQRTIME, taxisinqrtime, INT)
FCALLSCFUN1 (INT, taxisHasBounds, TAXISHASBOUNDS, taxishasbounds, INT)
FCALLSCSUB1 (taxisWithBounds, TAXISWITHBOUNDS, taxiswithbounds, INT)
FCALLSCSUB1 (taxisDeleteBounds, TAXISDELETEBOUNDS, taxisdeletebounds, INT)
FCALLSCSUB3 (taxisDefVdateBounds, TAXISDEFVDATEBOUNDS, taxisdefvdatebounds, INT, INT, INT)
FCALLSCSUB3 (taxisDefVtimeBounds, TAXISDEFVTIMEBOUNDS, taxisdefvtimebounds, INT, INT, INT)
FCALLSCSUB3 (taxisInqVdateBounds, TAXISINQVDATEBOUNDS, taxisinqvdatebounds, INT, PINT, PINT)
FCALLSCSUB3 (taxisInqVtimeBounds, TAXISINQVTIMEBOUNDS, taxisinqvtimebounds, INT, PINT, PINT)
FCALLSCSUB2 (taxisDefCalendar, TAXISDEFCALENDAR, taxisdefcalendar, INT, INT)
FCALLSCFUN1 (INT, taxisInqCalendar, TAXISINQCALENDAR, taxisinqcalendar, INT)
FCALLSCSUB2 (taxisDefTunit, TAXISDEFTUNIT, taxisdeftunit, INT, INT)
FCALLSCFUN1 (INT, taxisInqTunit, TAXISINQTUNIT, taxisinqtunit, INT)
FCALLSCSUB2 (taxisDefForecastTunit, TAXISDEFFORECASTTUNIT, taxisdefforecasttunit, INT, INT)
FCALLSCFUN1 (INT, taxisInqForecastTunit, TAXISINQFORECASTTUNIT, taxisinqforecasttunit, INT)
FCALLSCSUB2 (taxisDefForecastPeriod, TAXISDEFFORECASTPERIOD, taxisdefforecastperiod, INT, DOUBLE)
FCALLSCFUN1 (DOUBLE, taxisInqForecastPeriod, TAXISINQFORECASTPERIOD, taxisinqforecastperiod, INT)
FCALLSCSUB2 (taxisDefNumavg, TAXISDEFNUMAVG, taxisdefnumavg, INT, INT)
FCALLSCFUN1 (INT, taxisInqNumavg, TAXISINQNUMAVG, taxisinqnumavg, INT)
FCALLSCFUN1 (STRING, taxisNamePtr, TAXISNAMEPTR, taxisnameptr, INT)
FCALLSCFUN1 (STRING, tunitNamePtr, TUNITNAMEPTR, tunitnameptr, INT)

/*  Institut routines  */

FCALLSCFUN4 (INT, institutDef, INSTITUTDEF, institutdef, INT, INT, STRING, STRING)
FCALLSCFUN4 (INT, institutInq, INSTITUTINQ, institutinq, INT, INT, STRING, STRING)
FCALLSCFUN0 (INT, institutInqNumber, INSTITUTINQNUMBER, institutinqnumber)
FCALLSCFUN1 (INT, institutInqCenter, INSTITUTINQCENTER, institutinqcenter, INT)
FCALLSCFUN1 (INT, institutInqSubcenter, INSTITUTINQSUBCENTER, institutinqsubcenter, INT)
FCALLSCFUN1 (STRING, institutInqNamePtr, INSTITUTINQNAMEPTR, institutinqnameptr, INT)
FCALLSCFUN1 (STRING, institutInqLongnamePtr, INSTITUTINQLONGNAMEPTR, institutinqlongnameptr, INT)

/*  Model routines  */

FCALLSCFUN3 (INT, modelDef, MODELDEF, modeldef, INT, INT, STRING)
FCALLSCFUN3 (INT, modelInq, MODELINQ, modelinq, INT, INT, STRING)
FCALLSCFUN1 (INT, modelInqInstitut, MODELINQINSTITUT, modelinqinstitut, INT)
FCALLSCFUN1 (INT, modelInqGribID, MODELINQGRIBID, modelinqgribid, INT)
FCALLSCFUN1 (STRING, modelInqNamePtr, MODELINQNAMEPTR, modelinqnameptr, INT)

/*  Table routines  */

FCALLSCSUB2 (tableWrite, TABLEWRITE, tablewrite, STRING, INT)
FCALLSCFUN1 (INT, tableRead, TABLEREAD, tableread, STRING)
FCALLSCFUN3 (INT, tableDef, TABLEDEF, tabledef, INT, INT, STRING)
FCALLSCFUN1 (STRING, tableInqNamePtr, TABLEINQNAMEPTR, tableinqnameptr, INT)
FCALLSCFUN3 (INT, tableInq, TABLEINQ, tableinq, INT, INT, STRING)
FCALLSCFUN0 (INT, tableInqNumber, TABLEINQNUMBER, tableinqnumber)
FCALLSCFUN1 (INT, tableInqNum, TABLEINQNUM, tableinqnum, INT)
FCALLSCFUN1 (INT, tableInqModel, TABLEINQMODEL, tableinqmodel, INT)
FCALLSCSUB6 (tableInqEntry, TABLEINQENTRY, tableinqentry, INT, INT, INT, PSTRING, PSTRING, PSTRING)

/*  Subtype routines  */

FCALLSCFUN1 (INT, subtypeCreate, SUBTYPECREATE, subtypecreate, INT)

/*  Gives a textual summary of the variable subtype  */

FCALLSCSUB1 (subtypePrint, SUBTYPEPRINT, subtypeprint, INT)

/*  Compares two subtype data structures  */

FCALLSCFUN2 (INT, subtypeCompare, SUBTYPECOMPARE, subtypecompare, INT, INT)
FCALLSCFUN1 (INT, subtypeInqSize, SUBTYPEINQSIZE, subtypeinqsize, INT)
FCALLSCFUN1 (INT, subtypeInqActiveIndex, SUBTYPEINQACTIVEINDEX, subtypeinqactiveindex, INT)
FCALLSCSUB2 (subtypeDefActiveIndex, SUBTYPEDEFACTIVEINDEX, subtypedefactiveindex, INT, INT)

/*  Generate a "query object" out of a key-value pair  */


/*  Generate an AND-combined "query object" out of two previous query objects  */

FCALLSCFUN3 (INT, subtypeInqTile, SUBTYPEINQTILE, subtypeinqtile, INT, INT, INT)
FCALLSCFUN4 (INT, subtypeInqAttribute, SUBTYPEINQATTRIBUTE, subtypeinqattribute, INT, INT, STRING, PINT)
FCALLSCFUN2 (INT, vlistInqVarSubtype, VLISTINQVARSUBTYPE, vlistinqvarsubtype, INT, INT)
FCALLSCSUB3 (gribapiLibraryVersion, GRIBAPILIBRARYVERSION, gribapilibraryversion, PINT, PINT, PINT)

/*  Compatibility functions for release 1.8.3 (obsolete functions)  */

FCALLSCSUB2 (zaxisDefLtype, ZAXISDEFLTYPE, zaxisdefltype, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarTypeOfGeneratingProcess, VLISTINQVARTYPEOFGENERATINGPROCESS, vlistinqvartypeofgeneratingprocess, INT, INT)
FCALLSCSUB3 (vlistDefVarTypeOfGeneratingProcess, VLISTDEFVARTYPEOFGENERATINGPROCESS, vlistdefvartypeofgeneratingprocess, INT, INT, INT)
FCALLSCSUB3 (vlistDefVarProductDefinitionTemplate, VLISTDEFVARPRODUCTDEFINITIONTEMPLATE, vlistdefvarproductdefinitiontemplate, INT, INT, INT)

/*  Compatibility functions for ParaView vtkCDIReader (obsolete functions)  */

FCALLSCFUN1 (INT, vlistNgrids, VLISTNGRIDS, vlistngrids, INT)
FCALLSCFUN1 (INT, vlistNzaxis, VLISTNZAXIS, vlistnzaxis, INT)

/*  End of fortran interface  */


#if defined __clang__
#  pragma GCC diagnostic pop
#endif

// clang-format on

#endif
