
/* Automatically generated by m214003 at 2020-11-18, do not edit */

/* CDILIB_VERSION="1.9.9" */

#if defined(_WIN32) || defined(_WIN64)
#define restrict
#define ssize_t long
#else
#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
/*
  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_

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

#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_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

// 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

// external data types

#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

// Chunks

#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
#define  GRID_FOURIER               6  // Fourier coefficients
#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_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

// 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_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

// 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);

void    cdiDefMissval(double missval);
double  cdiInqMissval(void);
double  cdiInqGridMissval(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(int64_t date, int *year, int *month, int *day);
int64_t 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 *path, int *byteorder);

//      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    streamDefNumWorker(int streamID, int numWorker);

//      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);

//      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);

size_t  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[], size_t nmiss);
void    streamWriteVarF(int streamID, int varID, const float data[], size_t nmiss);

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

//      streamWriteVarSlice: Write a horizontal slice of a variable
void    streamWriteVarSlice(int streamID, int varID, int levelID, const double data[], size_t nmiss);
void    streamWriteVarSliceF(int streamID, int varID, int levelID, const float data[], size_t nmiss);
void    streamReadVarSlicePart(int streamID, int varID, int levelID, int varType, int start, size_t size, void *data, size_t *nmiss, int memtype);

//      streamReadVarSlice: Read a horizontal slice of a variable
void    streamReadVarSlice(int streamID, int varID, int levelID, double data[], size_t *nmiss);
void    streamReadVarSliceF(int streamID, int varID, int levelID, float data[], size_t *nmiss);

void    streamWriteVarChunk(int streamID, int varID, const int rect[3][2], const double data[], size_t nmiss);


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

void    streamDefRecord(int streamID, int  varID, int  levelID);
void    streamInqRecord(int streamID, int *varID, int *levelID);
void    streamWriteRecord(int streamID, const double data[], size_t nmiss);
void    streamWriteRecordF(int streamID, const float data[], size_t nmiss);
void    streamReadRecord(int streamID, double data[], size_t *nmiss);
void    streamReadRecordF(int streamID, float data[], size_t *nmiss);
void    streamCopyRecord(int streamIDdest, int streamIDsrc);

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[], size_t *nmiss_optional);
void cdiIterator_readFieldF(CdiIterator *me, float data[], size_t *nmiss_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);

//      vlistCopy: Copy a variable list
void    vlistCopy(int vlistID2, int vlistID1);

//      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);

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

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

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

void    vlistDefNtsteps(int vlistID, int nts);
int     vlistNtsteps(int vlistID);
size_t  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     vlistNrecs(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    vlistDefVarChunkType(int vlistID, int varID, int chunktype);
int     vlistInqVarChunkType(int vlistID, int varID);

void    vlistDefVarXYZ(int vlistID, int varID, int xyz);
int     vlistInqVarXYZ(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);

//      vlistDefVarExtra: Define extra information of a Variable
void    vlistDefVarExtra(int vlistID, int varID, const char *extra);

//      vlistInqVarExtra: Get extra information of a Variable
void    vlistInqVarExtra(int vlistID, int varID, char *extra);

void    vlistDefVarScalefactor(int vlistID, int varID, double scalefactor);
double  vlistInqVarScalefactor(int vlistID, int varID);
void    vlistDefVarAddoffset(int vlistID, int varID, double addoffset);
double  vlistInqVarAddoffset(int vlistID, int varID);

size_t  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);

//      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);

//      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);

// 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, size_t 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
size_t  gridInqSize(int gridID);

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

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

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

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

//      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
size_t  gridInqXvals(int gridID, double xvals[]);
size_t  gridInqXvalsPart(int gridID, int start, size_t size, double xvals[]);

//      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
size_t  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
size_t  gridInqYvals(int gridID, double yvals[]);
size_t  gridInqYvalsPart(int gridID, int start, size_t size, double yvals[]);

//      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
size_t  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_REFERENCEURI                  965  // Reference URI to grid file

// Integer keys
#define  CDI_KEY_NUMBEROFGRIDUSED              963  // GRIB2 numberOfGridUsed
#define  CDI_KEY_NUMBEROFGRIDINREFERENCE       964  // GRIB2 numberOfGridInReference
#define  CDI_KEY_NUMBEROFVGRIDUSED             961  // GRIB2 numberOfVGridUsed
#define  CDI_KEY_NLEV                          962  // GRIB2 nlev

// Floating point keys
#define  CDI_KEY_MISSVAL                       701  // Missing value

// 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);

//      gridDefDatatype: Define the data type of a Grid
void    gridDefDatatype(int gridID, int prec);

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

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

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

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

int     gridIsCircular(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);

// Lambert Conformal Conic grid
void    gridDefParamLCC(int gridID, double missval, double lon_0, double lat_0, double lat_1, double lat_2, double a, double rf, double xval_0, double yval_0, double x_0, double y_0);
int     gridInqParamLCC(int gridID, double missval, double *lon_0, double *lat_0, double *lat_1, double *lat_2, double *a, double *rf, double *xval_0, double *yval_0, double *x_0, double *y_0);

// Polar stereographic grid
void    gridDefParamSTERE(int gridID, double missval, double lon_0, double lat_ts, double lat_0, double a, double xval_0, double yval_0, double x_0, double y_0);
int     gridInqParamSTERE(int gridID, double missval, double *lon_0, double *lat_ts, double *lat_0, double *a, double *xval_0, double *yval_0, double *x_0, double *y_0);

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
size_t  gridInqXbounds(int gridID, double xbounds[]);
size_t  gridInqXboundsPart(int gridID, int start, size_t size, double xbounds[]);

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

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

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 prec);
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, int64_t date);

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

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

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

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

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

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

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

//      taxisDefFdate: Define the forecast reference date
void    taxisDefFdate(int taxisID, int64_t date);

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

//      taxisInqFdate: Get the forecast reference date
int64_t taxisInqFdate(int taxisID);

//      taxisInqFtime: Get the forecast reference time
int     taxisInqFtime(int taxisID);

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

void    taxisDeleteBounds(int taxisID);

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

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

void    taxisInqVdateBounds(int taxisID, int64_t *vdate_lb, int64_t *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 *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);

#ifdef __cplusplus
}
#endif

//FINT_OFF  <--- don't change or remove this line!!!

#ifdef __cplusplus
extern "C" {
#endif

void gaussianLatitudes(double *latitudes, double *weights, size_t nlat);

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

#ifdef __cplusplus
}
#endif

#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 ERROR_H
#define ERROR_H

#include <stdarg.h>
#include <stdlib.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, ...);

#if  defined  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));

#if defined (__cplusplus)
}
#endif

#endif  /* ERROR_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

#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 = 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
  *jobManager = malloc(sizeof(AsyncManager));
  if(!*jobManager) return CDI_ESYSTEM;
  (*jobManager)->workerCount = threadCount;
  (*jobManager)->communicators = malloc(threadCount*sizeof(AsyncJob));
  if (!(*jobManager)->communicators) xabort("memory allocation failure");

  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>

//#define USE_TIMECACHE 1
#define MAX_TIMECACHE_SIZE 1024*1024

typedef struct {
  size_t size;
  size_t startid;
  size_t maxvals;
  double cache[MAX_TIMECACHE_SIZE];
}
timecache_t;

typedef struct {
  int   ncvarid;
  int   ncdimid;
  int   ncvarboundsid;
  int   leadtimeid;
  bool  lunits;
  bool  lwrf;     // true for time axis in WRF format
  timecache_t *timevar_cache;
}
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:
 */
#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->lunits        = false;
      basetime->lwrf          = false;
      basetime->timevar_cache = 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 _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  DTYPES_H
#define  DTYPES_H

#include <stdio.h>
#include <limits.h>

/* INT32 */

#ifndef  INT_MAX
#error INT_MAX undefined
#endif

#undef  INT32
#if  INT_MAX == 2147483647L
#  define  INT32  int
#elif LONG_MAX == 2147483647L
#  define  INT32  long
#endif

/* INT64 */

#ifndef  LONG_MAX
#  error LONG_MAX undefined
#endif

#undef  INT64
#if  LONG_MAX > 2147483647L
#  define  INT64  long
#else
#  define  INT64  long long
#endif

/* FLT32 */

#undef   FLT32
#define  FLT32  float

/* FLT64 */

#undef   FLT64
#define  FLT64  double

/* UINT32 and UINT64 */

#define  UINT32   unsigned INT32
#define  UINT64   unsigned INT64

#endif  /* DTYPES_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 BINARY_H
#define BINARY_H

#ifdef HAVE_CONFIG_H
#endif

#include <inttypes.h>


#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


UINT32 get_UINT32(unsigned char *x);
UINT32 get_SUINT32(unsigned char *x);
UINT64 get_UINT64(unsigned char *x);
UINT64 get_SUINT64(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 *ptr);
int binReadInt64(int fileID, int byteswap, size_t size, INT64 *ptr);

int binWriteInt32(int fileID, int byteswap, size_t size, INT32 *ptr);
int binWriteInt64(int fileID, int byteswap, size_t size, INT64 *ptr);

int binWriteFlt32(int fileID, int byteswap, size_t size, FLT32 *ptr);
int binWriteFlt64(int fileID, int byteswap, size_t size, FLT64 *ptr);

#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:
 */


UINT32 get_UINT32(unsigned char *x)
{
  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      return (((UINT32)x[0])<<24) + (((UINT32)x[1])<<16) + (((UINT32)x[2])<< 8) + (UINT32)x[3];
    case CDI_LITTLEENDIAN:
      return (((UINT32)x[3])<<24) + (((UINT32)x[2])<<16) + (((UINT32)x[1])<< 8) + (UINT32)x[0];
    default:
      Error("Unhandled endianness %d", HOST_ENDIANNESS);
      return UINT32_C(0xFFFFFFFF);
    }
}


UINT32 get_SUINT32(unsigned char *x)
{
  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      return (((UINT32)x[3])<<24) + (((UINT32)x[2])<<16) + (((UINT32)x[1])<< 8) + (UINT32)x[0];
    case CDI_LITTLEENDIAN:
      return (((UINT32)x[0])<<24) + (((UINT32)x[1])<<16) + (((UINT32)x[2])<< 8) + (UINT32)x[3];
    default:
      Error("Unhandled endianness %d", HOST_ENDIANNESS);
      return UINT32_C(0xFFFFFFFF);
    }
}


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


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


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

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

  return blocklen;
}


void binWriteF77Block(int fileID, int byteswap, size_t blocksize)
{
  static unsigned int s[4] = {0, 8, 16, 24};
  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 *ptr)
{
  if ( sizeof(INT32) != 4 ) Error("Not implemented for %d byte integer!", sizeof(INT32));

  fileRead(fileID, (void *) ptr, 4*size);
  if ( byteswap ) swap4byte(ptr, size);

  return 0;
}


int binReadInt64(int fileID, int byteswap, size_t size, INT64 *ptr)
{
  if ( sizeof(INT64) != 8 ) Error("Not implemented for %d byte integer!", sizeof(INT64));

  fileRead(fileID, (void *) ptr, 8*size);
  if ( byteswap ) swap8byte(ptr, size);

  return 0;
}


int binWriteInt32(int fileID, int byteswap, size_t size, INT32 *ptr)
{
  if ( sizeof(INT32) != 4 ) Error("Not implemented for %d byte integer!", sizeof(INT32));

  if ( byteswap ) swap4byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 4*size);

  return 0;
}


int binWriteInt64(int fileID, int byteswap, size_t size, INT64 *ptr)
{
  if ( sizeof(INT64) != 8 ) Error("Not implemented for %d byte integer!", sizeof(INT64));

  if ( byteswap ) swap8byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 8*size);

  return 0;
}


int binWriteFlt32(int fileID, int byteswap, size_t size, FLT32 *ptr)
{
  if ( sizeof(FLT32) != 4 ) Error("Not implemented for %d byte float!", sizeof(FLT32));

  if ( byteswap ) swap4byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 4*size);

  return 0;
}


int binWriteFlt64(int fileID, int byteswap, size_t size, FLT64 *ptr)
{
  if ( sizeof(FLT64) != 8 ) Error("Not implemented for %d byte float!", sizeof(FLT64));

  if ( byteswap ) swap8byte(ptr, size);
  fileWrite(fileID, (void *) ptr, 8*size);

  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 CALENDAR_H
#define CALENDAR_H

#include <inttypes.h>

#ifdef __cplusplus
extern "C" {
#endif

void encode_caldaysec(int calendar, int year, int month, int day, int hour, int minute, int second,
		      int64_t *julday, int *secofday);
void decode_caldaysec(int calendar, int64_t julday, int secofday,
		      int *year, int *month, int *day, int *hour, int *minute, int *second);

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

#ifdef __cplusplus
}
#endif

#endif  /* CALENDAR_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 TIMEBASE_H
#define TIMEBASE_H

#include <inttypes.h>

#ifdef __cplusplus
extern "C" {
#endif

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

void decode_julday(int calendar, int64_t julday, int *year, int *mon, int *day);
int64_t encode_julday(int calendar, int year, int month, int day);

int64_t date_to_julday(int calendar, int64_t date);
int64_t julday_to_date(int calendar, int64_t julday);

int time_to_sec(int time);
int sec_to_time(int secofday);

void   julday_add_seconds(int64_t seconds, int64_t *julday, int *secofday);
void   julday_add(int days, int secs, int64_t *julday, int *secofday);
double julday_sub(int64_t julday1, int secofday1, int64_t julday2, int secofday2, int64_t *days, int *secs);

void encode_juldaysec(int calendar, int year, int month, int day, int hour, int minute, int second, int64_t *julday, int *secofday);
void decode_juldaysec(int calendar, int64_t julday, int secofday, int *year, int *month, int *day, int *hour, int *minute, int *second);

#ifdef __cplusplus
}
#endif

#endif /* TIMEBASE_H */

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#include <limits.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};


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_month(int calendar, int year, int month)
{
  int daysperyear = calendar_dpy(calendar);

  const int *dpm;
  if      ( daysperyear == 360 ) dpm = month_360;
  else if ( daysperyear == 365 ) dpm = month_365;
  else                           dpm = month_366;

  int dayspermonth = 0;
  if ( month >= 1 && month <= 12 )
    dayspermonth = dpm[month-1];
  /*
  else
    fprintf(stderr, "days_per_month: month %d out of range\n", month);
  */
  if ( daysperyear == 0 && month == 2 )
    {
      if ( (year%4 == 0 && year%100 != 0) || year%400 == 0 )
	dayspermonth = 29;
      else
	dayspermonth = 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;
}


static void decode_day(int dpy, int days, int *year, int *month, int *day)
{
  int i = 0;

  *year = (days-1) / dpy;
  days -= (*year*dpy);

  const int *dpm = NULL;
  if      ( dpy == 360 ) dpm = month_360;
  else if ( dpy == 365 ) dpm = month_365;
  else if ( dpy == 366 ) dpm = month_366;

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

  *month = i + 1;
  *day   = days;
}


static int64_t encode_day(int dpy, int year, int month, int day)
{
  int64_t rval = (int64_t)dpy * year + day;

  const int *dpm = NULL;
  if      ( dpy == 360 ) dpm = month_360;
  else if ( dpy == 365 ) dpm = month_365;
  else if ( dpy == 366 ) dpm = month_366;

  if ( dpm ) for ( int i = 0; i < month-1; i++ ) rval += dpm[i];
  if ( rval > LONG_MAX || rval < LONG_MIN ) Error("Unhandled date: %lld", rval);

  return rval;
}


void encode_caldaysec(int calendar, int year, int month, int day, int hour, int minute, int second,
		      int64_t *julday, int *secofday)
{
  int dpy = calendar_dpy(calendar);

  if ( dpy == 360 || dpy == 365 || dpy == 366 )
    *julday = encode_day(dpy, year, month, day);
  else
    *julday = encode_julday(calendar, year, month, day);

  *secofday = hour*3600 + minute*60 + second;
}


void decode_caldaysec(int calendar, int64_t julday, int secofday,
		      int *year, int *month, int *day, int *hour, int *minute, int *second)
{
  int dpy = calendar_dpy(calendar);

  if ( dpy == 360 || dpy == 365 || dpy == 366 )
    decode_day(dpy, julday, year, month, day);
  else
    decode_julday(calendar, julday, year, month, day);

  *hour   = secofday/3600;
  *minute = secofday/60 - *hour*60;
  *second = secofday - *hour*3600 - *minute*60;
}


#ifdef TEST
static int date_to_calday(int calendar, int date)
{
  int dpy = calendar_dpy(calendar);

  int year, month, day;
  cdiDecodeDate(date, &year, &month, &day);

  int calday;
  if ( dpy == 360 || dpy == 365 || dpy == 366 )
    calday = encode_day(dpy, year, month, day);
  else
    calday = encode_julday(calendar, year, month, day);

  return calday;
}


static int calday_to_date(int calendar, int calday)
{
  int year, month, day;
  int dpy = calendar_dpy(calendar);

  if ( dpy == 360 || dpy == 365 || dpy == 366 )
    decode_day(dpy, calday, &year, &month, &day);
  else
    decode_julday(calendar, calday, &year, &month, &day);

  int date = cdiEncodeDate(year, month, day);

  return date;
}


int main(void)
{
  int calendar = CALENDAR_STANDARD;
  int nmin;
  int64_t vdate0, vdate;
  int vtime0, vtime;
  int ijulinc;
  int i, j = 0;
  int year, mon, day, hour, minute, second;
  int calday, secofday;

  /* 1 - Check valid range of years */

  nmin = 11000;
  vdate0 = -80001201;
  vtime0 = 120500;

  printf("start time: %8ld %4d\n", vdate0, vtime0);

  for ( i = 0; i < nmin; i++ )
    {
      cdiDecodeDate(vdate0, &year, &mon, &day);
      cdiDecodeTime(vtime0, &hour, &minute, &second);

      calday  = date_to_calday(calendar, vdate0);
      secofday = time_to_sec(vtime0);

      vdate = calday_to_date(calendar, calday);
      vtime = sec_to_time(secofday);

      if ( vdate0 != vdate || vtime0 != vtime )
	printf("%4d %8ld %4d %8ld %4d %9d %9d\n",
	       ++j, vdate0, vtime0, vdate, vtime, calday, secofday);

      year++;
      vdate0 = cdiEncodeDate(year, mon, day);
      vtime0 = cdiEncodeTime(hour, minute, second);
    }

  printf("stop time: %8ld %4d\n", vdate0, vtime0);

  /* 2 - Check time increment of one minute */

  nmin = 120000;
  ijulinc = 60;
  vdate0 = 20001201;
  vtime0 = 0;

  printf("start time: %8ld %4d\n", vdate0, vtime0);

  calday = date_to_calday(calendar, vdate0);
  secofday = time_to_sec(vtime0);
  for ( i = 0; i < nmin; i++ )
    {
      cdiDecodeDate(vdate0, &year, &mon, &day);
      cdiDecodeTime(vtime0, &hour, &minute, &second);

      if ( ++minute >= 60 )
	{
	  minute = 0;
	  if ( ++hour >= 24 )
	    {
	      hour = 0;
	      if ( ++day >= 32 )
		{
		  day = 1;
		  if ( ++mon >= 13 )
		    {
		      mon = 1;
		      year++;
		    }
		}
	    }
	}

      vdate0 = cdiEncodeDate(year, mon, day);
      vtime0 = cdiEncodeTime(hour, minute, second);

      julday_add_seconds(ijulinc, &calday, &secofday);

      vdate = calday_to_date(calendar, calday);
      vtime = sec_to_time(secofday);
      if ( vdate0 != vdate || vtime0 != vtime )
	printf("%4d %8ld %4d %8ld %4d %9d %9d\n",
	       ++j, vdate0, vtime0, vdate, vtime, calday, secofday);
    }

  printf("stop time: %8ld %4d\n", vdate0, vtime0);

  return 0;
}
#endif


#ifdef TEST2
int main(void)
{
  int calendar = CALENDAR_STANDARD;
  int i;
  int calday, secofday;
  int year, month, day, hour, minute, second;
  int value = 30;
  int factor = 86400;

  calendar = CALENDAR_360DAYS;

  year=1979; month=1; day=15; hour=12; minute=30; second = 0;

  printf("calendar = %d\n", calendar);
  printf("%d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);

  encode_caldaysec(calendar, year, month, day, hour, minute, second, &calday, &secofday);

  decode_caldaysec(calendar, calday, secofday, &year, &month, &day, &hour, &minute, &second);
  printf("%d/%02d/%02d %02d:%02d:%02d   %d %d\n", year, month, day, hour, minute, second, calday, secofday);

  for ( i = 0; i < 420; i++ )
    {

      decode_caldaysec(calendar, calday, secofday, &year, &month, &day, &hour, &minute, &second);
      printf("%2d %d/%02d/%02d %02d:%02d:%02d\n", i, year, month, day, hour, minute, second);
      julday_add_seconds(value*factor, &calday, &secofday);
    }

  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  CDF_H
#define  CDF_H

void cdfDebug(int debug);

extern int CDF_Debug;

const char *cdfLibraryVersion(void);
const char *hdfLibraryVersion(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  CDF_CONFIG_H_
#define  CDF_CONFIG_H_

#ifdef  HAVE_CONFIG_H
#endif

#ifdef  HAVE_LIBNETCDF

#include  <netcdf.h>

#ifdef  NC_FORMAT_64BIT_DATA
#define  HAVE_NETCDF5  1
#endif

#endif

#endif
#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 {
  /* Date format  YYYYMMDD */
  /* Time format    hhmmss */
  int     self;
  bool    used;
  short   has_bounds;
  int     datatype;       // datatype
  int     type;           // time type
  int64_t sdate;          // start date
  int     stime;          // start time
  int64_t vdate;          // verification date
  int     vtime;          // verification time
  int64_t rdate;          // reference date
  int     rtime;          // reference time
  int64_t fdate;          // forecast reference date
  int     ftime;          // forecast reference time
  int     calendar;
  int     unit;           // time unit
  int     numavg;
  bool    climatology;
  int64_t vdate_lb;       // lower bounds of vdate
  int     vtime_lb;       // lower bounds of vtime
  int64_t vdate_ub;       // upper bounds of vdate
  int     vtime_ub;       // upper bounds of vtime
  int     fc_unit;        // forecast time unit
  double  fc_period;      // forecast time period
  char   *name;
  char   *longname;
  char   *units;
}
taxis_t;

//      taxisInqSdate: Get the start date
int64_t taxisInqSdate(int taxisID);

//      taxisInqStime: Get the start time
int     taxisInqStime(int taxisID);

void     ptaxisInit(taxis_t* taxis);
void     ptaxisCopy(taxis_t* dest, taxis_t* source);
taxis_t* taxisPtr(int taxisID);
void     cdiSetForecastPeriod(double timevalue, taxis_t *taxis);
void     cdiDecodeTimeval(double timevalue, taxis_t* taxis, int64_t* date, int* time);
double   cdiEncodeTimeval(int64_t date, int time, taxis_t* taxis);
void     timeval2vtime(double timevalue, taxis_t* taxis, int64_t* vdate, int* vtime);
double   vtime2timeval(int64_t vdate, int vtime, 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);
void    taxisDestroyKernel(taxis_t *taxisptr);
#if !defined (SX)
extern const resOps taxisOps;
#endif

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

#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  CDI_LIMITS_H
#define  CDI_LIMITS_H

#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 _SERVICE_H
#define _SERVICE_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  srvInqDataSP(void *srv, float *data);
int  srvInqDataDP(void *srv, double *data);

int  srvDefHeader(void *srv, const int *header);
int  srvDefDataSP(void *srv, const float *data);
int  srvDefDataDP(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

#define  EXT_REAL   1
#define  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  extInqDataSP(void *ext, float *data);
int  extInqDataDP(void *ext, double *data);

int  extDefHeader(void *ext, const int *header);
int  extDefDataSP(void *ext, const float *data);
int  extDefDataDP(void *ext, const double *data);

#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

/* 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                            */


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  iegInqDataSP(void *ieg, float *data);
int  iegInqDataDP(void *ieg, double *data);

int  iegDefHeader(void *ieg, const int *header);
int  iegDefDataSP(void *ieg, const float *data);
int  iegDefDataDP(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

#ifdef HAVE_CONFIG_H
#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>


// 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

#ifndef strdupx
#ifndef strdup
char *strdup(const char *s);
#endif
#define strdupx  strdup
/*
#define strdupx(s)			          \
({					      	  \
   const char *__old = (s);			  \
   size_t __len = strlen(__old) + 1;		  \
   char *__new = (char *) Malloc(__len);	  \
   (char *) memcpy(__new, __old, __len);	  \
})
*/
#endif

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

static inline bool
strIsEqual(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 TIMEBASE_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!")

#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 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

#define  FALSE  0
#define  TRUE   1

#define  TYPE_REC  0
#define  TYPE_VAR  1

#define  MEMTYPE_DOUBLE  1
#define  MEMTYPE_FLOAT   2

typedef struct
{
  void     *buffer;             // gribapi, cgribex
  size_t    buffersize;         // gribapi, cgribex
  off_t     position;           // file position
  int       param;
  int       level;
  int       date;
  int       time;
  int       gridID;
  int       varID;
  int       levelID;
  int       prec;               // ext, srv
  void     *exsep;              // ieg, ext, srv container
  void     *cgribexp;           // cgribex container
}
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
{
  off_t     position;
  size_t    size;
  size_t    gridsize;
  int       zip;
  int       param;
  int       ilevel;
  int       ilevel2;
  int       ltype;
  short     tsteptype;
  short     varID;
  short     levelID;
  short     used;
  char      varname[32]; // needed for grib decoding with GRIB_API
  VarScanKeys scanKeys;
  var_tile_t tiles;      // tile-related meta-data, currently for GRIB-API only.
}
record_t;


typedef struct {
  record_t *records;
  int      *recIDs;      // IDs of non constant 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
  bool      next;
  off_t     position;    // timestep file position
  taxis_t   taxis;
}
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            ncvarid;
  int            subtypeSize;
  sleveltable_t *recordTable; // record IDs for each subtype
  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       ilev;
  int       mlev;
  int       ilevID;
  int       mlevID;
}
VCT;

#ifdef HAVE_LIBNETCDF
enum {
  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_ncIDs,
};
typedef struct {
  int gridID;
  int ncIDs[CDF_SIZE_ncIDs];
}
ncgrid_t;
#endif

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
  size_t      numvals;
  char       *filename;
  Record     *record;
  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
  tsteps_t   *tsteps;
  int         tstepsTableSize;
  int         tstepsNextID;
  basetime_t  basetime;
  int         ncmode;
  int         vlistID;
#ifdef HAVE_LIBNETCDF
  int         nc_complex_float_id;
  int         nc_complex_double_id;
  ncgrid_t    ncgrid[MAX_GRIDS_PS];
  int         zaxisID[MAX_ZAXES_PS];	// Warning: synchronous array to vlist_to_pointer(vlistID)->zaxisIDs
  int         nczvarID[MAX_ZAXES_PS];
  VCT         vct;
#endif
  int         globalatts;
  int         localatts;
  int         unreduced;
  int         have_missval;
  int         comptype;      // compression type
  int         complevel;     // compression level
  bool        sortname;
  bool        sortparam;
#if defined (GRIBCONTAINER2D)
  void      **gribContainers;
#else
  void       *gribContainers;
#endif

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

  void *gh; // grib handle
}
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
} 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                    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_Chunk_Type;
extern int CDI_Split_Ltype105;
extern int cdiDataUnreduced;
extern int cdiSortName;
extern int cdiSortParam;
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 int CDI_Convention;
extern int CDI_Inventory_Mode;
extern int CDO_version_info;
extern int CDI_Read_Cell_Corners;
extern int CDI_CMOR_Mode;
extern int CDI_Reduce_Dim;
extern size_t CDI_Netcdf_Hdr_Pad;
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);

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

int     recordNewEntry(stream_t *streamptr, int tsID);

void    cdiCreateTimesteps(stream_t *streamptr);

void    recordInitEntry(record_t *record);

void    cdiCheckZaxis(int zaxisID);

void    cdiDefAccesstype(int streamID, int type);
int     cdiInqAccesstype(int streamID);

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);

#define CDI_UNIT_PA   1
#define CDI_UNIT_HPA  2
#define CDI_UNIT_MM   3
#define CDI_UNIT_CM   4
#define CDI_UNIT_DM   5
#define CDI_UNIT_M    6

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, size_t nmiss);

void
cdiStreamWriteVarChunk_(int streamID, int varID, int memtype,
                        const int rect[][2], const void *data, size_t nmiss);
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);

#ifdef __cplusplus
extern "C" {
#endif

// functions used in CDO !!!

void cdiDefTableID(int tableID);

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

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

int cdiBaseFiletype(int filetype);

#if defined (__cplusplus)
}
#endif

#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

#ifdef HAVE_LIBNETCDF

#include <netcdf.h>

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);
void cdf__enddef(const int ncid, const 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_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_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 *dp);

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_long  (int ncid, int varid, const char *name, long *ip);
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);

#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>



const char *cdfLibraryVersion(void)
{
#ifdef  HAVE_LIBNETCDF
  return nc_inq_libvers();
#else
  return "library undefined";
#endif
}

#ifdef  HAVE_H5GET_LIBVERSION
#ifdef  __cplusplus
extern "C" {
#endif
  int H5get_libversion(unsigned *, unsigned *, unsigned *);
#ifdef  __cplusplus
}
#endif
#endif

const char *hdfLibraryVersion(void)
{
#ifdef  HAVE_H5GET_LIBVERSION
  static char hdf_libvers[256];
  static int linit = 0;
  if (!linit)
    {
      linit = 1;
      unsigned majnum, minnum, relnum;
      H5get_libversion(&majnum, &minnum, &relnum);
#ifdef  HAVE_NC4HDF5_THREADSAFE
      sprintf(hdf_libvers, "%u.%u.%u threadsafe", majnum, minnum, relnum);
#else
      sprintf(hdf_libvers, "%u.%u.%u", majnum, minnum, relnum);
#endif
    }

  return hdf_libvers;
#else
  return "library undefined";
#endif
}


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


void cdfDebug(int debug)
{
  CDF_Debug = debug;

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

#ifdef  HAVE_LIBNETCDF
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);
}
#endif

static int cdfOpenFile(const char *filename, const char *mode, int *filetype)
{
  int ncid = -1;
#ifdef  HAVE_LIBNETCDF
  int fmode = tolower(*mode);
  int writemode = NC_CLOBBER;
  int readmode = NC_NOWRITE;

  if ( filename == NULL )
    ncid = CDI_EINVAL;
  else
    {
      switch (fmode)
	{
	case 'r':
          {
            int status = cdf_open(filename, readmode, &ncid);
            if ( status > 0 && ncid < 0 ) ncid = CDI_ESYSTEM;
#ifdef  HAVE_NETCDF4
            else
              {
                int format;
                (void) nc_inq_format(ncid, &format);
                if ( format == NC_FORMAT_NETCDF4_CLASSIC )
                  *filetype = CDI_FILETYPE_NC4C;
              }
#endif
          }
	  break;
	case 'w':
#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
#ifdef  HAVE_NETCDF4
	  if      ( *filetype == CDI_FILETYPE_NC4  ) writemode |= NC_NETCDF4;
	  else if ( *filetype == CDI_FILETYPE_NC4C ) writemode |= NC_NETCDF4 | NC_CLASSIC_MODEL;
#endif
	  cdf_create(filename, writemode, &ncid);
	  if ( CDO_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;
	}
    }
#endif

  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);

#ifdef  HAVE_LIBNETCDF
#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
#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;
}

static
int cdf4CheckLibVersions(void)
{
  int status = 0;
#ifdef HAVE_NETCDF4
#ifdef HAVE_H5GET_LIBVERSION
  static int checked = 0;
  if (!checked)
    {
      checked = 1;
      unsigned majnum, minnum, relnum;

      sscanf(nc_inq_libvers(), "%u.%u.%u", &majnum, &minnum, &relnum);
      //printf("netCDF %u.%u.%u\n", majnum, minnum, relnum);
      const unsigned ncmaxver = 4 * 1000000 + 4 * 1000;
      const unsigned nclibver = majnum * 1000000 + minnum * 1000 + relnum;

      if (nclibver <= ncmaxver)
        {
          H5get_libversion(&majnum, &minnum, &relnum);
          const unsigned hdf5maxver = 1 * 1000000 + 10 * 1000;
          const unsigned hdf5libver = majnum * 1000000 + minnum * 1000 + relnum;

          if (hdf5libver >= hdf5maxver)
            {
              fprintf(stderr, "NetCDF library 4.4.0 or earlier, combined with libhdf5 1.10.0 or greater not supported!\n");
              status = 1;
            }
        }
    }
#endif
#endif
  return status;
}

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

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

#ifdef  HAVE_NETCDF4
  open_file = true;
#endif

  if ( open_file )
    {
      if (cdf4CheckLibVersions() == 0)
        {
          fileID = cdfOpenFile(filename, mode, filetype);
          if ( CDF_Debug ) Message("File %s opened with id %d", filename, fileID);
        }
      else
        {
          fileID = CDI_EUFTYPE;
        }
    }
  else
    {
      fileID = CDI_ELIBNAVAIL;
    }

  return fileID;
}


static void cdfCloseFile(int fileID)
{
#ifdef  HAVE_LIBNETCDF
  cdf_close(fileID);
#endif
}

void cdfClose(int fileID)
{
  cdfCloseFile(fileID);
}
/*
 * 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,
#ifdef HAVE_LIBNETCDF
  NSSWITCH_NC__CREATE,
  NSSWITCH_CDF_DEF_VAR,
  NSSWITCH_CDF_DEF_TIMESTEP,
  NSSWITCH_CDF_STREAM_SETUP,
#endif
  NUM_NAMESPACE_SWITCH,
};

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

#define NSSW_FUNC(p) ((union namespaceSwitchValue){ .func = (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(enum namespaceSwitch sw,
                        union namespaceSwitchValue value);
union namespaceSwitchValue namespaceSwitchGet(enum namespaceSwitch sw);


#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>


#ifdef HAVE_LIBNETCDF

void cdf_create(const char *path, int cmode, int *ncidp)
{
  size_t initialsz = 0, chunksizehint = 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;
  int status = my_nc__create(path, cmode, initialsz, &chunksizehint, ncidp);

  if ( CDF_Debug || status != NC_NOERR )
    Message("ncid = %d  mode = %d  chunksizehint = %zu file = %s", *ncidp, cmode, chunksizehint, 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));
}


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

#ifdef HAVE_LIBNC_DAP
  if ( strncmp(path, "http:", 5) == 0 || strncmp(path, "https:", 6) == 0 ) dapfile = true;
#endif

  if ( dapfile )
    {
      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));

  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));
}


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


void cdf__enddef(const int ncid, const size_t hdr_pad)
{
  const size_t v_align   = 4UL; // [B] Alignment of beginning of data section for fixed variables
  const size_t v_minfree = 0UL; // [B] Pad at end of data section for fixed size variables
  const 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)
  int status = nc__enddef(ncid, 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 = %d", 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 = %d 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 = %d", 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 ) 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_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)
{
  const char *ctype = "unknown";

  if      ( xtype == NC_BYTE   )  ctype = "NC_BYTE";
  else if ( xtype == NC_CHAR   )  ctype = "NC_CHAR";
  else if ( xtype == NC_SHORT  )  ctype = "NC_SHORT";
  else if ( xtype == NC_INT    )  ctype = "NC_INT";
  else if ( xtype == NC_FLOAT  )  ctype = "NC_FLOAT";
  else if ( xtype == NC_DOUBLE )  ctype = "NC_DOUBLE";
#ifdef  HAVE_NETCDF4
  else if ( xtype == NC_UBYTE  )  ctype = "NC_UBYTE";
  else if ( xtype == NC_LONG   )  ctype = "NC_LONG";
  else if ( xtype == NC_USHORT )  ctype = "NC_USHORT";
  else if ( xtype == NC_UINT   )  ctype = "NC_UINT";
  else if ( xtype == NC_INT64  )  ctype = "NC_INT64";
  else if ( xtype == NC_UINT64 )  ctype = "NC_UINT64";
#endif

  return ctype;
}

static
void minmaxval(size_t nvals, const double *array, double *minval, double *maxval)
{
  *minval = array[0];
  *maxval = array[0];
  for ( size_t i = 1; i < nvals; ++i )
    {
      if      ( array[i] > *maxval ) *maxval = array[i];
      else if ( array[i] < *minval ) *minval = array[i];
    }
}

static
void minmaxvalf(size_t nvals, const float *array, double *minval, double *maxval)
{
  *minval = array[0];
  *maxval = array[0];
  for ( size_t i = 1; i < nvals; ++i )
    {
      if      ( array[i] > *maxval ) *maxval = array[i];
      else if ( array[i] < *minval ) *minval = array[i];
    }
}

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, maxval = 0;
      size_t nvals = 1;
      for ( int i = 0; i < ndims; ++i ) nvals *= count[i];
      minmaxval(nvals, dp, &minval, &maxval);
      // Message("ncid = %d varid = %d val0 = %f", ncid, varid, *dp);
      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, maxval = 0;
      size_t nvals = 1;
      for ( int i = 0; i < ndims; ++i ) nvals *= count[i];
      minmaxvalf(nvals, fp, &minval, &maxval);
      // Message("ncid = %d varid = %d val0 = %f", ncid, varid, *dp);
      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 *dp)
{
  int status = nc_get_vara_int(ncid, varid, start, count, 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_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", ncid, varid);

  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", ncid, varid);

  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_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 ( 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)
{
#ifdef  HAVE_NETCDF4
  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));
#endif
}


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 ( 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_long(int ncid, int varid, const char *name, long *ip)
{
#ifdef  HAVE_NETCDF4
  int status = nc_get_att_long(ncid, varid, name, ip);

  if ( CDF_Debug || status != NC_NOERR )
    Message("ncid = %d varid = %d att = %s val = %ld", ncid, varid, name, *ip);

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


void cdf_get_att_double(int ncid, int varid, const char *name, double *dp)
{
  int status;

  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 = %d", 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));
}


#ifdef  HAVE_NETCDF4
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 ( status != NC_NOERR ) Error("%s", nc_strerror(status));
}
#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:
 */
#ifndef  _DMEMORY_H
#define  _DMEMORY_H

#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);

#if  defined  DEBUG_MEMORY

extern void   *memRealloc(void *ptr, size_t size, const char *file, const char *functionname, int line);
extern void   *memCalloc (size_t nmemb, 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  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

#endif /* DEBUG_MEMORY */

#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  CDF_UTIL_H_
#define  CDF_UTIL_H_

#include <stdbool.h>

int get_timeunit(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_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);
void cdf_set_calendar(const char *attstring, int *calendar);

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



char *strToLower(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_timeunit(size_t len, const char *ptu)
{
  int timeunit = -1;

  while (isspace(*ptu) && len) { ptu++; len--; }

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

  return timeunit;
}


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

  bool status = strStartsWith(timeunits, "sec")
             || strStartsWith(timeunits, "minute")
             || strStartsWith(timeunits, "hour")
             || strStartsWith(timeunits, "day")
             || strStartsWith(timeunits, "month")
             || strStartsWith(timeunits, "calendar_month")
             || strStartsWith(timeunits, "year");

  return status;
}


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+1) * sizeof(char));
  char *ptu = tu;

  for (size_t i = 0; i < len; i++) ptu[i] = (char)tolower((int)ptu[i]);

  int timeunit = get_timeunit(len, ptu);
  if (timeunit != -1)
    {
      while (! isspace(*ptu) && *ptu != 0) ptu++;
      if (*ptu)
        {
          while (isspace(*ptu)) ptu++;

          int timetype = strStartsWith(ptu, "as") ? TAXIS_ABSOLUTE :
                         strStartsWith(ptu, "since") ? TAXIS_RELATIVE : -1;

          status = timetype != -1;
        }
    }

  Free(tu);

  return status;
}


bool is_height_units(const char *units)
{
  const int u0 = units[0];

  bool status
    = (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"));

  return status;
}


bool is_pressure_units(const char *units)
{
  bool status = strStartsWith(units, "millibar")
             || strStartsWith(units, "mb")
             || strStartsWith(units, "hectopas")
             || strStartsWith(units, "hPa")
             || strStartsWith(units, "Pa");

  return status;
}


bool is_DBL_axis(const char *longname)
{
  bool status = strIsEqual(longname, "depth below land")
             || strIsEqual(longname, "depth_below_land")
             || strIsEqual(longname, "levels below the surface");

  return status;
}


bool is_depth_axis(const char *stdname, const char *longname)
{
  bool status = strIsEqual(stdname, "depth")
             || strIsEqual(longname, "depth_below_sea")
             || strIsEqual(longname, "depth below sea");

  return status;
}


bool is_height_axis(const char *stdname, const char *longname)
{
  bool status = strIsEqual(stdname, "height")
             || strIsEqual(longname, "height")
             || strIsEqual(longname, "height above the surface");

  return status;
}

bool is_altitude_axis(const char *stdname, const char *longname)
{
  bool status = strIsEqual(stdname, "altitude")
             || strIsEqual(longname, "altitude");

  return status;
}


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;
  strToLower(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;
  strToLower(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 (strIsEqual(stdname, "projection_x_coordinate"));
}


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


void cdf_set_gridtype(const char *attstring, int *gridtype)
{
  // clang-format off
  if      (strIsEqual(attstring, "gaussian_reduced")) *gridtype = GRID_GAUSSIAN_REDUCED;
  else if (strIsEqual(attstring, "gaussian"))         *gridtype = GRID_GAUSSIAN;
  else if (strStartsWith(attstring, "spectral"))      *gridtype = GRID_SPECTRAL;
  else if (strStartsWith(attstring, "fourier"))       *gridtype = GRID_FOURIER;
  else if (strIsEqual(attstring, "trajectory"))       *gridtype = GRID_TRAJECTORY;
  else if (strIsEqual(attstring, "generic"))          *gridtype = GRID_GENERIC;
  else if (strIsEqual(attstring, "cell"))             *gridtype = GRID_UNSTRUCTURED;
  else if (strIsEqual(attstring, "unstructured"))     *gridtype = GRID_UNSTRUCTURED;
  else if (strIsEqual(attstring, "curvilinear")) ;
  else if (strIsEqual(attstring, "characterxy"))      *gridtype = GRID_CHARXY;
  else if (strIsEqual(attstring, "sinusoidal")) ;
  else if (strIsEqual(attstring, "laea")) ;
  else if (strIsEqual(attstring, "lcc2")) ;
  else if (strIsEqual(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      (strIsEqual(attstring, "toa"))              *zaxistype = ZAXIS_TOA;
  else if (strIsEqual(attstring, "tropopause"))       *zaxistype = ZAXIS_TROPOPAUSE;
  else if (strIsEqual(attstring, "cloudbase"))        *zaxistype = ZAXIS_CLOUD_BASE;
  else if (strIsEqual(attstring, "cloudtop"))         *zaxistype = ZAXIS_CLOUD_TOP;
  else if (strIsEqual(attstring, "isotherm0"))        *zaxistype = ZAXIS_ISOTHERM_ZERO;
  else if (strIsEqual(attstring, "seabottom"))        *zaxistype = ZAXIS_SEA_BOTTOM;
  else if (strIsEqual(attstring, "lakebottom"))       *zaxistype = ZAXIS_LAKE_BOTTOM;
  else if (strIsEqual(attstring, "sedimentbottom"))   *zaxistype = ZAXIS_SEDIMENT_BOTTOM;
  else if (strIsEqual(attstring, "sedimentbottomta")) *zaxistype = ZAXIS_SEDIMENT_BOTTOM_TA;
  else if (strIsEqual(attstring, "sedimentbottomtw")) *zaxistype = ZAXIS_SEDIMENT_BOTTOM_TW;
  else if (strIsEqual(attstring, "mixlayer"))         *zaxistype = ZAXIS_MIX_LAYER;
  else if (strIsEqual(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
}


void cdf_set_calendar(const char *attstring, int *calendar)
{
  // clang-format off
  if      (strStartsWith(attstring, "standard"))  *calendar = CALENDAR_STANDARD;
  else if (strStartsWith(attstring, "gregorian")) *calendar = CALENDAR_GREGORIAN;
  else if (strStartsWith(attstring, "none"))      *calendar = CALENDAR_NONE;
  else if (strStartsWith(attstring, "proleptic")) *calendar = CALENDAR_PROLEPTIC;
  else if (strStartsWith(attstring, "360"))       *calendar = CALENDAR_360DAYS;
  else if (strStartsWith(attstring, "365") ||
           strStartsWith(attstring, "noleap"))    *calendar = CALENDAR_365DAYS;
  else if (strStartsWith(attstring, "366") ||
           strStartsWith(attstring, "all_leap"))  *calendar = CALENDAR_366DAYS;
  else Warning("calendar >%s< unsupported!", attstring);
  // clang-format on
}
#ifndef _STREAM_CDF_H
#define _STREAM_CDF_H


void   cdfDefCoordinateVars(stream_t *streamptr);
void   cdfDefTimestep(stream_t *streamptr, int tsID);
int    cdfInqTimestep(stream_t *streamptr, int tsID);
int    cdfInqContents(stream_t *streamptr);

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

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

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

void   cdf_read_record(stream_t *streamptr, int memtype, void *data, size_t *nmiss);
void   cdf_write_record(stream_t *streamptr, int memtype, const void *data, size_t nmiss);

void   cdf_read_var(stream_t *streamptr, int varID, int memtype, void *data, size_t *nmiss);
void   cdf_write_var(stream_t *streamptr, int varID, int memtype, const void *data, size_t nmiss);

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

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

void cdfDefVarDeflate(int ncid, int ncvarid, int deflate_level);
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);

#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_KEY_H
#define  CDI_KEY_H


// CDI key
typedef struct {
  int       key;          // CDI key
  int       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 {
  size_t     nalloc;		// number allocated >= nelems
  size_t     nelems;		// length of the array
  cdi_key_t  value[MAX_KEYS];
} cdi_keys_t;

enum  {KEY_INT = 1, KEY_FLOAT, KEY_BYTES};

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(cdi_keys_t *keysp, int key);

static inline
const char *cdiInqVarKeyString(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  CDI_ATT_H
#define  CDI_ATT_H

#ifdef  HAVE_CONFIG_H
#endif

#ifndef  CDI_LIMITS_H
#endif

// CDI attribute
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;


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


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>


extern int (*proj_lonlat_to_lcc_func)();
extern int (*proj_lcc_to_lonlat_func)();
extern int (*proj_lonlat_to_stere_func)();
extern int (*proj_stere_to_lonlat_func)();

typedef unsigned char mask_t;

typedef struct grid_t grid_t;

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 (*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, size_t index);
  double (*inqYVal)(grid_t *gridptr, size_t index);
  size_t (*inqXVals)(grid_t *gridptr, double *xvals);
  size_t (*inqXValsPart)(grid_t *gridptr, int start, size_t length, double *xvals);
  size_t (*inqYVals)(grid_t *gridptr, double *yvals);
  size_t (*inqYValsPart)(grid_t *gridptr, int start, size_t 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);
  size_t (*inqXCvals)(grid_t *gridptr, char **xcvals);
  size_t (*inqYCvals)(grid_t *gridptr, char **ycvals);
  const char **(*inqXCvalsPtr)(grid_t *gridptr);
  const char **(*inqYCvalsPtr)(grid_t *gridptr);
#endif
  /* return if for both grids, all xval and all yval are equal */
  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);
  int (*hasArea)(grid_t *gridptr);
  size_t (*inqMask)(grid_t *gridptr, int *mask);
  int (*inqMaskGME)(grid_t *gridptr, int *mask_gme);
  size_t (*inqXBounds)(grid_t *gridptr, double *xbounds);
  size_t (*inqYBounds)(grid_t *gridptr, double *ybounds);
  const double *(*inqXBoundsPtr)(grid_t *gridptr);
  const double *(*inqYBoundsPtr)(grid_t *gridptr);
};

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
  int         proj;                   // grid projection
  int         projtype;               // grid projection type
  mask_t     *mask;
  mask_t     *mask_gme;
  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 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);

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 gridGenerate(const grid_t *grid);

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

void cdiGridGetIndexList(unsigned, int * );

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

struct addIfNewRes
{
  int Id;
  int isNew;
};

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

int gridVerifyGribParamLCC(double missval, double *lon_0, double *lat_0, double *lat_1, double *lat_2,
                           double *a, double *rf, double *xval_0, double *yval_0, double *x_0, double *y_0);
int gridVerifyGribParamSTERE(double missval, double *lon_0, double *lat_ts, double *lat_0,
                             double *a, double *xval_0, double *yval_0, double *x_0, double *y_0);

bool isGaussGrid(size_t ysize, double yinc, const double *yvals);

#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)
{
  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, size_t index)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *)grid;
  lock_lazy_load(lazyGrid);
  double rv = cdfLazyGridInqXYVal(grid, index, &lazyGrid->xValsGet,
                                  grid->x.vals, grid->vtable->inqXValsPtr);
  unlock_lazy_load(lazyGrid);
  return rv;
}

static double
cdfLazyGridInqYVal(grid_t *grid, size_t index)
{
  struct cdfLazyGrid *lazyGrid = (struct cdfLazyGrid *)grid;
  lock_lazy_load(lazyGrid);
  double rv = cdfLazyGridInqXYVal(grid, 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) ; else 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>

uint32_t cdiCheckSum(int type, int count, const void *data);

#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 = 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 == KEY_BYTES)
        {
          int length = keyp->length;
          packBuffSize +=
            serializeGetSize(1, CDI_DATATYPE_INT, context)
            + serializeGetSize(length, CDI_DATATYPE_TXT, context);
        }
      else
        {
          packBuffSize += serializeGetSize(1, CDI_DATATYPE_INT, 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 = 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 == KEY_BYTES)
        {
          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 == KEY_INT)
        {
          serializePack(&keyp->v.i, 1, CDI_DATATYPE_INT, buf, buf_size, position, context);
        }
      else if (type == KEY_FLOAT)
        {
          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 == KEY_BYTES)
        {
          int length;
          serializeUnpack(buf, buf_size, position, &length, 1, CDI_DATATYPE_INT, context);
          if (length > buffersize)
            {
              buffersize = length;
              buffer = realloc(buffer, buffersize);
            }
          serializeUnpack(buf, buf_size, position, buffer, length, CDI_DATATYPE_TXT, context);
          cdiDefVarKeyBytes(keysp, key, buffer, length);
          d2 ^= cdiCheckSum(CDI_DATATYPE_TXT, length, buffer);
        }
      else if (type == KEY_INT)
        {
          int ival;
          serializeUnpack(buf, buf_size, position, &ival, 1, CDI_DATATYPE_INT, context);
          cdiDefVarKeyInt(keysp, key, ival);
        }
    }
  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:
 */
#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;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#include <stdio.h>
#include <string.h>
#include <errno.h>

const char *cdiStringError(int cdiErrno)
{
  static const char UnknownError[] = "Unknown Error";
  static const char _ETMOF[]       = "Too many open files";
  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 _ELIMIT[]      = "Internal limits exceeded";

  switch (cdiErrno) {
  case CDI_ESYSTEM:
    {
      const char *cp = strerror(errno);
      if ( cp == NULL ) break;
      return cp;
    }
  case CDI_ETMOF:      return _ETMOF;
  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_ELIMIT:     return _ELIMIT;
  }

  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:
 */
#ifndef  GRIBAPI_H
#define  GRIBAPI_H

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

#ifndef  CDI_INT_H
#endif

#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_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

const char *gribapiLibraryVersionString(void);
void gribContainersNew(stream_t * streamptr);
void gribContainersDelete(stream_t * streamptr);

#ifdef HAVE_LIBGRIB_API
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 ret_val = grib_set_double(h, key, val);
  if (ret_val != 0)
    fprintf(stderr, "!!! failed call to grib_set_double(\tgrib_handle* h, \"%s\", %f) !!!\n", key, val);
  return ret_val;
}

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 ret_val = grib_set_long(h, key, val);
  if (ret_val != 0)
    fprintf(stderr, "!!! failed call to grib_set_long(  \tgrib_handle* h, \"%s\", %ld) !!!\n", key, val);
  return ret_val;
}

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;
}

static inline void *gribHandleNew(int editionNumber)
{
  grib_handle *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;
}

static inline void gribHandleDelete(void *gh)
{
  grib_handle_delete((struct grib_handle *)gh);
}
#else
#define gribHandleNew(editionNumber) (NULL)
#define gribHandleDelete(gh)
#endif

typedef struct {
  bool init;
  void *gribHandle;
}
gribContainer_t;

#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

#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(int *isec1);
int   gribRefTime(int *isec1);
bool  gribTimeIsFC(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, unsigned char *buffer, size_t *buffersize);
int   gribWrite(int fileID, unsigned char *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);

#ifdef  __cplusplus
}
#endif

#endif  /* CGRIBEX_H */ 

#ifdef  HAVE_CONFIG_H
#endif

#include <ctype.h>


#ifdef  HAVE_LIBCGRIBEX
#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_Chunk_Type       = CDI_CHUNK_GRID;
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;
int CDI_Convention           = CDI_CONVENTION_ECHAM;
int CDI_Inventory_Mode       = 1;
int CDO_version_info         = 1;
int CDI_Read_Cell_Corners    = 1;
int CDI_CMOR_Mode            = 0;
int CDI_Reduce_Dim           = 0;
size_t CDI_Netcdf_Hdr_Pad    = 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.;

static const char Filetypes[][9] = {
  "UNKNOWN",
  "GRIB",
  "GRIB2",
  "NetCDF",
  "NetCDF2",
  "NetCDF4",
  "NetCDF4c",
  "NetCDF5",
  "SERVICE",
  "EXTRA",
  "IEG",
  "HDF5",
};

int CDI_Debug   = 0;    // If set to 1, debugging
int CDI_Recopt  = 0;

bool CDI_gribapi_debug  = false;
bool CDI_gribapi_grib1  = false;
int cdiDefaultLeveltype = -1;
int cdiDataUnreduced = 0;
int cdiSortName = 0;
int cdiSortParam = 0;
int cdiHaveMissval = 0;


static
long cdiGetenvInt(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;
		  Message("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);
}

void cdiPrintVersion(void)
{
  fprintf(stderr, "     CDI library version : %s\n", cdiLibraryVersion());
#ifdef  HAVE_LIBCGRIBEX
  fprintf(stderr, " cgribex library version : %s\n", cgribexLibraryVersion());
#endif
#ifdef  HAVE_LIBGRIB_API
  fprintf(stderr, " ecCodes library version : %s\n", gribapiLibraryVersionString());
#endif
#ifdef  HAVE_LIBNETCDF
  fprintf(stderr, "  NetCDF library version : %s\n", cdfLibraryVersion());
#endif
#ifdef  HAVE_NC4HDF5
  fprintf(stderr, "    hdf5 library version : %s\n", hdfLibraryVersion());
#endif
#ifdef  HAVE_LIBSERVICE
  fprintf(stderr, "    exse library version : %s\n", srvLibraryVersion());
#endif
  fprintf(stderr, "    FILE library version : %s\n", fileLibraryVersion());
}

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"
           "+-------------+-----------+\n"
           "| INT32       | %-9s |\n"
           "| INT64       | %-9s |\n"
           "| FLT32       | %-9s |\n"
           "| FLT64       | %-9s |\n"
           "+-------------+-----------+\n"
           "\n  byte ordering is %s\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),
           STRING(INT32), STRING(INT64), STRING(FLT32), STRING(FLT64),
           ((HOST_ENDIANNESS == CDI_BIGENDIAN) ? "BIGENDIAN"
            : ((HOST_ENDIANNESS == CDI_LITTLEENDIAN) ? "LITTLEENDIAN"
               : "Unhandled endianness!")));
#undef STRING
#undef XSTRING
}


void cdiDebug(int level)
{
  unsigned ulevel = (unsigned) level;

  if ( ulevel == 1 || (ulevel &  2) ) CDI_Debug = 1;

  if ( CDI_Debug ) Message("debug level %d", level);

  if ( ulevel == 1 || (ulevel &  4) ) memDebug(1);

  if ( ulevel == 1 || (ulevel &  8) ) fileDebug(1);

  if ( ulevel == 1 || (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)
{
  int status = 0;

  switch (filetype)
    {
#ifdef  HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:  status = 1; break;
#endif
#ifdef  HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:  status = 1; break;
#endif
#ifdef  HAVE_LIBIEG
    case CDI_FILETYPE_IEG:  status = 1; break;
#endif
#ifdef  HAVE_LIBGRIB
#if  defined  HAVE_LIBGRIB_API || defined  HAVE_LIBCGRIBEX
    case CDI_FILETYPE_GRB:  status = 1; break;
#endif
#ifdef  HAVE_LIBGRIB_API
    case CDI_FILETYPE_GRB2: status = 1; break;
#endif
#endif
#ifdef  HAVE_LIBNETCDF
    case CDI_FILETYPE_NC:   status = 1; break;
#ifdef  HAVE_NETCDF2
    case CDI_FILETYPE_NC2:  status = 1; break;
#endif
#ifdef  HAVE_NETCDF4
    case CDI_FILETYPE_NC4:  status = 1; break;
    case CDI_FILETYPE_NC4C: status = 1; break;
#endif
#ifdef  HAVE_NETCDF5
    case CDI_FILETYPE_NC5:  status = 1; break;
#endif
#endif
    default: status = 0; break;
    }

  return status;
}


void cdiDefTableID(int tableID)
{
  CDI_Default_TableID = tableID;
  int modelID = CDI_Default_ModelID = tableInqModel(tableID);
  CDI_Default_InstID = modelInqInstitut(modelID);
}

static
void cdiSetChunk(const char *chunkAlgo)
{
  int algo = -1;

  if      ( strcmp("auto",  chunkAlgo) == 0 ) algo = CDI_CHUNK_AUTO;
  else if ( strcmp("grid",  chunkAlgo) == 0 ) algo = CDI_CHUNK_GRID;
  else if ( strcmp("lines", chunkAlgo) == 0 ) algo = CDI_CHUNK_LINES;
  else
    Warning("Invalid environment variable CDI_CHUNK_ALGO: %s", chunkAlgo);

  if ( algo != -1 )
    {
      CDI_Chunk_Type = algo;
      if ( CDI_Debug ) Message("set ChunkAlgo to %s", chunkAlgo);
    }
}


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;
      char *envstr;
      long value;

#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

      value = cdiGetenvInt("CDI_DEBUG");
      if ( value >= 0 ) CDI_Debug = (int) value;

      value = cdiGetenvInt("CDI_GRIBAPI_DEBUG");
      if ( value >= 0 ) CDI_gribapi_debug = (bool) value;

      value = cdiGetenvInt("CDI_ECCODES_DEBUG");
      if ( value >= 0 ) CDI_gribapi_debug = (bool) value;

      value = cdiGetenvInt("CDI_ECCODES_GRIB1");
      if ( value >= 0 ) cdiSetEccodesGrib1((bool) value);

      value = cdiGetenvInt("CDI_READ_CELL_CORNERS");
      if ( value >= 0 ) CDI_Read_Cell_Corners = (int) value;

      value = cdiGetenvInt("CDI_RECOPT");
      if ( value >= 0 ) CDI_Recopt = (int) value;

      value = cdiGetenvInt("CDI_REGULARGRID");
      if ( value >= 0 ) cdiDataUnreduced = (int) value;

      value = cdiGetenvInt("CDI_SORTNAME");
      if ( value >= 0 ) cdiSortName = (int) value;

      value = cdiGetenvInt("CDI_SORTPARAM");
      if ( value >= 0 ) cdiSortParam = (int) value;

      value = cdiGetenvInt("CDI_HAVE_MISSVAL");
      if ( value >= 0 ) cdiHaveMissval = (int) value;

      value = cdiGetenvInt("CDI_LEVELTYPE");
      if ( value >= 0 ) cdiDefaultLeveltype = (int) value;

      value = cdiGetenvInt("CDI_NETCDF_HDR_PAD");
      if ( value >= 0 ) CDI_Netcdf_Hdr_Pad = (size_t) value;

      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("CDI_CHUNK_ALGO");
      if ( envstr ) cdiSetChunk(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 ( strcmp(envstr, "CF") == 0 || strcmp(envstr, "cf") == 0 )
	    {
	      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_VERSION_INFO");
      if ( envstr )
        {
          int ival = atoi(envstr);
          if ( ival == 0 || ival == 1 )
            {
              CDO_version_info = ival;
              if ( CDI_Debug )
                Message("CDO_version_info = %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)
{
  int size = (int) (sizeof(Filetypes)/sizeof(char *));
  return (filetype > 0 && filetype < size) ? Filetypes[filetype] : Filetypes[0];
}


void cdiDefGlobal(const char *string, int value)
{
  // clang-format off
  if      (strcmp(string, "REGULARGRID")           == 0) cdiDataUnreduced = value;
  else if (strcmp(string, "ECCODES_DEBUG")         == 0) CDI_gribapi_debug = (bool) value;
  else if (strcmp(string, "ECCODES_GRIB1")         == 0) cdiSetEccodesGrib1((bool) value);
  else if (strcmp(string, "SORTNAME")              == 0) cdiSortName = value;
  else if (strcmp(string, "SORTPARAM")             == 0) cdiSortParam = value;
  else if (strcmp(string, "HAVE_MISSVAL")          == 0) cdiHaveMissval = value;
  else if (strcmp(string, "NC_CHUNKSIZEHINT")      == 0) CDI_Netcdf_Chunksizehint = value;
  else if (strcmp(string, "READ_CELL_CORNERS")     == 0) CDI_Read_Cell_Corners = value;
  else if (strcmp(string, "CMOR_MODE")             == 0) CDI_CMOR_Mode = value;
  else if (strcmp(string, "REDUCE_DIM")            == 0) CDI_Reduce_Dim = value;
  else if (strcmp(string, "NETCDF_HDR_PAD")        == 0) CDI_Netcdf_Hdr_Pad = (size_t) value;
  else if (strcmp(string, "NETCDF_LAZY_GRID_LOAD") == 0) CDI_Netcdf_Lazy_Grid_Load = (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;
}


double cdiInqGridMissval(void)
{
  cdiInitialize();

  return CDI_Default_Missval;
}

int cdiBaseFiletype(int filetype)
{
  switch (filetype)
    {
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    case CDI_FILETYPE_NC5:   return CDI_FILETYPE_NETCDF;
    default:                 return filetype;
    }

  return filetype;
}

/*
 * 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_UNISTD_H
#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 cdiDecodeDate(int64_t date, int *year, int *month, int *day)
{
  int64_t iyear = (int)(date / 10000);
  *year = (int)iyear;
  int64_t idate = date - iyear * 10000;
  if ( idate < 0 ) idate = -idate;
  int64_t imonth = idate / 100;
  *month = (int)imonth;
  *day   = (int)(idate - imonth * 100);
}


int64_t cdiEncodeDate(int year, int month, int day)
{
  int64_t iyear = abs(year);
  int64_t 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;
}


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(char *)) };
  const char *name = ( cdi_unit > 0 && cdi_unit < numUnits ) ?
    cdiUnits[cdi_unit] : NULL;

  return name;
}

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;
#  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
#    elif HAVE_DECL__SC_PAGE_SIZE
        _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 2020-04-30, do not edit */

/* CGRIBEXLIB_VERSION="1.9.5" */

#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 <inttypes.h>



#ifndef CGRIBEX_TEMPLATES_H
#define CGRIBEX_TEMPLATES_H

#define CAT(X,Y)      X##_##Y
#define TEMPLATE(X,Y) CAT(X,Y)

#endif 
#ifndef GRIB_INT_H
#define GRIB_INT_H

#if defined (HAVE_CONFIG_H)
#endif

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <float.h>


#if ! defined   (CGRIBEX_H)
#endif
#if ! defined   (ERROR_H)
#endif
#if ! defined   (DTYPES_H)
#endif


#if ! defined   (UCHAR)
#define  UCHAR  unsigned char
#endif


#if defined (CRAY) || defined (SX) || defined (__uxpch__)
#define VECTORCODE
#endif


#ifdef VECTORCODE
#ifdef  INT32
#  define  GRIBPACK     unsigned INT32
#  define  PACK_GRIB    packInt32
#  define  UNPACK_GRIB  unpackInt32
#else
#  define  GRIBPACK     unsigned INT64
#  define  PACK_GRIB    packInt64
#  define  UNPACK_GRIB  unpackInt64
#endif
#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  U_BYTEORDER
#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.
  */
  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(int64_t date, int *year, int *month, int *day);
int64_t 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);

#ifdef  INT32
long   packInt32(unsigned INT32 *up, unsigned char *cp, long bc, long tc);
#endif
long   packInt64(unsigned INT64 *up, unsigned char *cp, long bc, long tc);
#ifdef  INT32
long   unpackInt32(const unsigned char *cp, unsigned INT32 *up, long bc, long tc);
#endif
long   unpackInt64(const unsigned char *cp, unsigned INT64 *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

#endif  /* GRIB_INT_H */
#ifndef GRIBDECODE_H
#define GRIBDECODE_H

#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], GET_UINT3(bds[7], bds[8], bds[9])))
#define  BDS_NumBits        ((int) bds[10])
#define  BDS_RealCoef       (decfp2((int)bds[zoff+11], 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]))

#endif  /* GRIBDECODE_H */
#ifndef CGRIBEX_GRIB_ENCODE_H
#define CGRIBEX_GRIB_ENCODE_H

#include <limits.h>

#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);            \
}

#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
*/
#if defined(_ARCH_PWR6)
#pragma options nostrict
#endif

#if defined(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

#if !defined(TEST_MINMAXVAL)
#define DISABLE_SIMD_MINMAXVAL
#endif

#ifdef DISABLE_SIMD_MINMAXVAL
# if defined(ENABLE_AVX)
#  define _ENABLE_AVX
# endif
# if defined(ENABLE_SSE2)
#  define _ENABLE_SSE2
# endif
#endif

#ifndef DISABLE_SIMD_MINMAXVAL
# if defined(__AVX__)
#  define _ENABLE_AVX
# endif
# if defined(__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 i, j;
    size_t residual =  datasize % __UNROLL_DEPTH_1;
    size_t ofs = datasize - residual;
    double register dmin[__UNROLL_DEPTH_1];
    double register dmax[__UNROLL_DEPTH_1];

    for ( j = 0; j < __UNROLL_DEPTH_1; j++) 
      {
	dmin[j] = data[0];
	dmax[j] = data[0];
      }
    
    for ( i = 0; i < datasize - residual; i += __UNROLL_DEPTH_1 ) 
      {
	for (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 (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 ( 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

#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

#if defined(GNUC_PUSH_POP)
#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)
#pragma GCC pop_options
#endif

// TEST
#if defined(OMP_SIMD)

#if defined(GNUC_PUSH_POP)
#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;

#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;
}

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)
#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  U_BYTEORDER
#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) 
{
  size_t i, j, residual;
  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;  

  residual = datasize % 16;

  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 ( 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) 
{
  size_t i, j, residual;
  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;  

  residual = datasize % 16;

  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 ( 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

#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


#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.                               */
  /* ----------------------------------------------------------------- */
  {
    const double zeps = (kbits != 32) ? 1.0e-12 : 1.0e-8;
    double zref = pval;

    // Sign of value.
    const 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;
  const int isign = (iexp < 128) * 2 - 1;

  iexp -= iexp < 128 ? 0 : 128;

  /*  Decode value. */

  /* pval = isign * pow(2.0, -24.0) * kmant * pow(16.0, (double)(iexp - 64)); */

  iexp -= 64;

  const double pval = ldexp(1.0, 4 * iexp) * isign * POW_2_M24 * kmant;

  /* ----------------------------------------------------------------- */
  /*   Section 9. Return to calling routine.                           */
  /* ----------------------------------------------------------------- */

  return pval;
} /* decfp2 */
#include <stdint.h>
#include <string.h>
#include <stdarg.h>


static
void gribDecodeRefDate(int *isec1, int *year, int *month, int *day)
{
  int century = ISEC1_Century;
  int ryear   = ISEC1_Year;

  if ( century < 0 ) century = -century;
  century -= 1;

  if ( century == -255 && ryear == 127 )
    {
      century = 0;
      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(int *isec1)
{
  int ryear, rmonth, rday;
  gribDecodeRefDate(isec1, &ryear, &rmonth, &rday);
  return cdiEncodeDate(ryear, rmonth, rday);
}

static
void gribDecodeRefTime(int *isec1, int *hour, int *minute, int *second)
{
  *hour = ISEC1_Hour;
  *minute = ISEC1_Minute;
  *second = 0;
}


int gribRefTime(int *isec1)
{
  int rhour, rminute, rsecond;
  gribDecodeRefTime(isec1, &rhour, &rminute, &rsecond);
  return cdiEncodeTime(rhour, rminute, rsecond);
}


bool gribTimeIsFC(int *isec1)
{
  bool isFC = false;

  int time_period = (ISEC1_TimeRange == 10) ? (ISEC1_TimePeriod1<<8) + ISEC1_TimePeriod2 : ISEC1_TimePeriod1;

  if ( time_period > 0 && ISEC1_Day > 0 )
    {
      if ( ISEC1_TimeRange == 0 || ISEC1_TimeRange == 10 ) isFC = true;
    }

  return isFC;
}

static
int getTimeUnitFactor(int timeUnit)
{
  static bool lprint = true;
  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;
    }

  return 0;
}


void gribDateTimeX(int *isec1, int *date, int *time, int *startDate, int *startTime)
{
  *startDate = 0;
  *startTime = 0;

  int ryear, rmonth, rday;
  gribDecodeRefDate(isec1, &ryear, &rmonth, &rday);

  int rhour, rminute, rsecond;
  gribDecodeRefTime(isec1, &rhour, &rminute, &rsecond);

  // printf("ref %d/%d/%d %d:%d\n", ryear, rmonth, rday, rhour, rminute);

  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 && rday > 0 )
    {
      int64_t julday;
      int secofday;
      encode_caldaysec(CGRIBEX_grib_calendar, ryear, rmonth, rday, rhour, rminute, rsecond, &julday, &secofday);

      int time_unit_factor = getTimeUnitFactor(ISEC1_TimeUnit);

      if (time_period_x > 0)
        {
          int64_t julday_x = julday;
          int secofday_x = secofday;
          julday_add_seconds(time_unit_factor * time_period_x, &julday_x, &secofday_x);
          decode_caldaysec(CGRIBEX_grib_calendar, julday_x, secofday_x, &ryear, &rmonth, &rday, &rhour, &rminute, &rsecond);
          *startDate = cdiEncodeDate(ryear, rmonth, rday);
          *startTime = cdiEncodeTime(rhour, rminute, 0);
        }
      
      julday_add_seconds(time_unit_factor * time_period, &julday, &secofday);
      decode_caldaysec(CGRIBEX_grib_calendar, julday, secofday, &ryear, &rmonth, &rday, &rhour, &rminute, &rsecond);
    }

  // printf("new %d/%d/%d %d:%d\n", ryear, rmonth, rday, rhour, rminute);
  *date = cdiEncodeDate(ryear, rmonth, rday);
  *time = cdiEncodeTime(rhour, rminute, 0);

  return;
}


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);
}


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;
    }
}

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 jloop, jiloop;
  float value;

  char hversion[9];
  /*
  char hfirst[121], hsecond[121], hthird[121], hfourth[121];
  */

  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]);

  /*
      IERR = CHKTAB2(ISEC1,HFIRST,HSECOND,HTHIRD,HFOURTH)
      IF( IERR .EQ. 0 ) THEN
       DO JLOOP = 121, 1, -1
          IF( HSECOND(JLOOP:JLOOP).NE.' ' ) THEN
            IOFFSET = JLOOP
            GOTO 110
          ENDIF
        ENDDO
        GOTO 120
 110    CONTINUE
        WRITE(*,'(2H ",A,1H")') HSECOND(1:IOFFSET)
 120    CONTINUE
      ENDIF
  */

  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;
      /* iyear  = ((isec1[20]-1)*100 + isec1[9]); */
      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]);
      sprintf(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 (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 (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 (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 (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 (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 (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 (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 (jloop = 43; jloop <= 54; jloop++)
		{
		  jiloop = jloop + 8;
		  fprintf(grprsm, " ERA section 1 octet %2d.              %9d\n",
			 jiloop, isec1[jloop-1]);
		}
	    }
	  else
	    {
	      for (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 (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]);
	  /*
	  sprintf(hversion, "%8d", isec1[44]);
	  fprintf(grprsm, "   Version number/experiment identifier:   %4s\n", &hversion[4]);
	  */
	  iyear = isec1[45];
	  if ( iyear > 50 )
	    iyear = iyear + 1900;
	  else
	    iyear = iyear + 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 (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 (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 (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 (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 (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 );

  if ( ntos )
    fprintf(grprsm, "  Number of points.   Parallel. (North to South)\n");
  else
    fprintf(grprsm, "  Number of points.   Parallel. (South to North)\n");

  // 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;
      sprintf(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 ) sprintf(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 i, 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 (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 ( 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;
  int j;
  double *fsec2;

  inum = 10 + isec2[11];

  fsec2 = (double*) Malloc((size_t)inum*sizeof(double));
  if ( fsec2 == NULL ) SysError("No Memory!");

  for ( 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;
  int j;

  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 ( 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)
{
  int inum;
  int j;
  double fsec4[20];

  inum = isec4[0];
  if ( inum <  0 ) inum = -inum;
  if ( inum > 20 ) inum = 20;

  for ( 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    jloop;
  int    ntemp[100];
  float *ptemp;

  grsdef();

  /*
    -----------------------------------------------------------------
    Section 1 . Print integer information from isec4.
    -----------------------------------------------------------------
  */
  fprintf(grprsm, " Coefficients defining first dimension coordinates:\n");
  for ( 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 ( jloop = 0; jloop < isec4[54]; jloop++ )
    {
      ntemp[jloop] = isec4[59 + isec4[52] + jloop];
      ptemp = (float *) &ntemp[jloop];
      fprintf(grprsm, "%20.10f\n", *ptemp);
    }
}
#if defined (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 ch;
  int retry = 4096*4096;

  *offset = 0;

  void *fileptr = filePtr(fileID);

  while ( retry-- )
    {
      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 )
            {
              const int 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;
      for ( int i = 0; i < 8; i++ ) rgribsize = (rgribsize << 8) | filePtrGetc(fileptr);
    }
  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;
  else 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, unsigned char *buffer, size_t *buffersize)
{
  long offset;
  int ierr = gribFileSeek(fileID, &offset); // position file pointer after GRIB
  if ( ierr > 0 )
    {
      Warning("GRIB record not found!");
      return -2;
    }

  if      ( ierr == -1 ) { *buffersize = 0; return -1; }
  else if ( ierr ==  1 ) { *buffersize = 0; return -2; }

  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().
  buffer[0] = 'G';
  buffer[1] = 'R';
  buffer[2] = 'I';
  buffer[3] = 'B';

  readSize -= 4;
  // Read the rest of the record into the buffer.
  size_t nread = fileRead(fileID, &buffer[4], readSize);

  if ( nread != readSize ) ierr = 1;

  return ierr;
}


int gribWrite(int fileID, unsigned char *buffer, size_t buffersize)
{
  int nwrite = 0;

  if ( (nwrite = (int)(fileWrite(fileID, buffer, buffersize))) != (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";
	      sprintf(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;
}

/* 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(unsigned INT64 *up, unsigned char *cp, long bc, long tc)
{
#if defined (CRAY)
  (void) _pack(up, cp, bc, tc);
#else
  U_BYTEORDER;
  unsigned char *cp0;
  unsigned INT64 upi, *up0, *ip0, *ip1, *ip2, *ip3, *ip4, *ip5, *ip6, *ip7;
  long head, trail, inner, i, j;
  long ipack = sizeof(INT64);
  
  /* Bytes until first word boundary in destination buffer */

  head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;

  inner = bc - head;

  /* Trailing bytes which do not make a full word */

  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 = (unsigned INT64 *)(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 
   */

  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 ( 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 ( 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 ( 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) cp[i]; */

long unpackInt64(const unsigned char *cp, unsigned INT64 *up, long bc, long tc)
{
  U_BYTEORDER;
  const unsigned char *cp0;
  unsigned INT64 *ip0, *ip1, *ip2, *ip3, *ip4, *ip5, *ip6, *ip7;
  long head, trail, inner, i, j;
  long offset;
  long ipack = sizeof(INT64);

  UNUSED(tc);

  /* Bytes until first word boundary in source buffer */

  head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;
  if ( head > bc ) head = bc;

  inner = bc - head;

  /* Trailing bytes which do not make a full word */
 
  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 unsigned INT64 *up0 = (const unsigned INT64 *)(const void *)(cp + head);

  /* Process any bytes until the first word boundary 
   * of our source buffer 
   */
  for ( i = 0 ; i < head ; i++ ) up[i] = (unsigned INT64) cp[i];

  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 ( 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 ( 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 ( i = 0 ; i < trail ; i++ ) up[i+offset] = (unsigned INT64) 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]; */

#ifdef  INT32
long packInt32(unsigned INT32 *up, unsigned char *cp, long bc, long tc)
{
  U_BYTEORDER;
  unsigned char *cp0;
  unsigned INT32 *up0, *ip0, *ip1, *ip2, *ip3;
  long head, trail, inner, i, j;
  long ipack = sizeof(INT32);
  
  /* Bytes until first word boundary in destination buffer */

  head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;

  inner = bc - head;

  /* Trailing bytes which do not make a full word */

  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 = (unsigned INT32 *)(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 
   */

  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 ( 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 ( 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 ( i = 0 ; i < trail ; i ++ )
	{
	  *cp0 = (unsigned char) ip0[ipack*inner+i];
	  cp0++;
	}
    }

  if ( tc != -1 )
    {
      bc++;
      *cp0 = (unsigned char) tc;
    }

  return (bc);
}
#endif

/* unpack 8-bit bytes from a packed buffer with 32-bit words */
/* same as : for ( int i = 0; i < bc; ++i ) up[i] = (INT32) cp[i]; */

#ifdef  INT32
long unpackInt32(const unsigned char *cp, unsigned INT32 *up, long bc, long tc)
{
  U_BYTEORDER;
  const unsigned char *cp0;
  unsigned INT32 *ip0, *ip1, *ip2, *ip3;
  long head, trail, inner, i, j;
  long offset;
  long ipack = sizeof(INT32);

  UNUSED(tc);

  /* Bytes until first word boundary in source buffer */

  head = ( (long) cp ) & (ipack-1);
  if ( head != 0 ) head = ipack - head;
  if ( head > bc ) head = bc;

  inner = bc - head;

  /* Trailing bytes which do not make a full word */
 
  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 unsigned INT32 *up0 = (const unsigned INT32 *)(const void *)(cp + head);

  /* Process any bytes until the first word boundary 
   * of our source buffer 
   */
  for ( i = 0 ; i < head ; i++ ) up[i] = (unsigned INT32) cp[i];

  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 ( 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 ( 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 ( i = 0 ; i < trail ; i++ ) up[i+offset] = (unsigned INT32) cp0[i];
    }
  /*
  if ( tc != -1 ) {
    bc++;
    *cp0 = (unsigned char) tc;
  }
  */

  return (bc);
}
#endif
#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;
  int j;

  /*
    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 ( 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 = PDS_Level * 100;
  else if ( PDS_LevelType == 99 )
    level = PDS_Level;
  else if ( PDS_LevelType == 109 )
    level = 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   = 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     = 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];
  sprintf(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+PDS_Month*100+PDS_Day;
      century = -century;
    }
  else
    {
      date =    year*10000+PDS_Month*100+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 )
    {
      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; */
  unsigned char *source;
  size_t sourceLen;
  int bds_nbits, bds_flag, lspherc, lcomplex /*, lcompress */;
  int bds_head = 11;
  int bds_ext = 0, bds_ubits;
  int datstart = 0;

  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 = bds_flag & 15;
  lspherc   =  bds_flag >> 7;
  lcomplex  = (bds_flag >> 6)&1;
  /* lcompress = (bds_flag >> 4)&1; */

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

  datstart = bds_head + bds_ext;

  source = bds + datstart;

  sourceLen = (size_t)(((((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

#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

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 = gribLen;

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

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

   int bds_zoffset = 12;

   int 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 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 = 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>


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 jl, 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 ( 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 ( 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 ( 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 ( 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 ( 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, j210, j220, j230, j240, j225;


   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 (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 (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 (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 (j225 = 1; j225 <= i_2; ++j225) {
            ++ilio;
            ++ilii;
            ztemp[ilio - 1] = pfield[ilii];
         }
      }
   }

/* Copy temporary array to user array. */

   i_1 = klon * klat;
   for (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 jl, 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 ( 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 ( 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 ( 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 ( 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 ( 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, j210, j220, j230, j240, j225;

   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 (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 (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 (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 (j225 = 1; j225 <= i_2; ++j225) {
            ++ilio;
            ++ilii;
            ztemp[ilio - 1] = pfield[ilii];
         }
      }
   }

/* Copy temporary array to user array. */

   i_1 = klon * klat;
   for (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 jl, 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 ( 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 ( 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 ( 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 ( 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 ( 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, j210, j220, j230, j240, j225;

   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 (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 (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 (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 (j225 = 1; j225 <= i_2; ++j225) {
            ++ilio;
            ++ilii;
            ztemp[ilio - 1] = pfield[ilii];
         }
      }
   }

/* Copy temporary array to user array. */

   i_1 = klon * klat;
   for (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:
 */
#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 = GET_UINT3(grib[1], grib[2], grib[3]);

  return decfp2(iexp, imant);
}

static 
int 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 = 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;

  int isLen = 4 + 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] = 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 = 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 = 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] =  GET_UINT2(pds[55],pds[56]); /* Experiment identifier                */
  isec1[52] =  GET_UINT2(pds[57],pds[58]); /* Ensemble identification by table     */
  isec1[53] =  GET_UINT2(pds[59],pds[60]); /* Number of ensemble members           */
  isec1[54] =  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] = GET_UINT2(pds[42],pds[43]); /* individual ensemble member      */
  isec1[39] = GET_UINT2(pds[44],pds[45]); /* number of forecasts in ensemble */
}

static 
int decodePDS(unsigned char *pds, int *isec0, int *isec1)
{
  int pdsLen = PDS_Len;

  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 = 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         = 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 )
    {
      int 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 ( int i = 0; i < localextlen; i++ )
                isec1[24+i] = pds[28+i];
	    }
	}
    }

  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);}


#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)
{
  U_BYTEORDER;
  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

  long i;
#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 ( i = 0; i < jlend; i++ )
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for ( i = 0; i < jlend; i++ )
      {
	T dval = (int)lgrib[i];
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 16 )
    for ( 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 ( 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 ( 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 ( i = 0; i < jlend; i++ )
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for ( 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 ( 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 ( 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)
{
  U_BYTEORDER;
  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

  long i;
#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 ( i = 0; i < jlend; i++ )
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for ( i = 0; i < jlend; i++ )
      {
	T dval = (int)lgrib[i];
	fpdata[i] = fmin + zscale * dval;
      }
  else if ( numBits == 16 )
    for ( 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 ( 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 ( 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 ( i = 0; i < jlend; i++ )
	fpdata[i] = fmin;
    }
  else if ( numBits ==  8 )
    for ( 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 ( 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 ( 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
int TEMPLATE(decodeGDS,T)(unsigned char  *gds, int *isec0, int *isec2, T *fsec2, int *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));

  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;
	  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 )
    {
      unsigned locnl = GDS_PVPL - 1 + (VertCoorTab * 4 * GDS_NV);
      unsigned jlenl = (gdsLen - locnl)  >> 1;
      if ( jlenl == GDS_NumLat )
	{
	  *numGridVals = 0;
	  ISEC2_Reduced = true;
	  for ( unsigned i = 0; i < jlenl; i++ )
	    {
	      ISEC2_ReducedPoints(i) = GET_UINT2(gds[locnl+2*i], gds[locnl+2*i+1]);
	      *numGridVals += ISEC2_ReducedPoints(i);
	    }
	}
      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    = GDS_NumLat;
      if ( ! ReducedGrid )
	{
	  ISEC2_NumLon = GDS_NumLon;
	  *numGridVals  = ISEC2_NumLon*ISEC2_NumLat;
	}
      ISEC2_FirstLat  = GDS_FirstLat;
      ISEC2_FirstLon  = GDS_FirstLon;
      ISEC2_ResFlag   = GDS_ResFlag;
      ISEC2_LastLat   = GDS_LastLat;
      ISEC2_LastLon   = GDS_LastLon;
      ISEC2_LonIncr   = GDS_LonIncr;

      ISEC2_NumPar    = 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    = GDS_NumLon;
      ISEC2_NumLat    = GDS_NumLat;
      *numGridVals  = ISEC2_NumLon*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  = GDS_PentaJ; /* Truncation */
      ISEC2_PentaK  = GDS_PentaK;
      ISEC2_PentaM  = GDS_PentaM;
      ISEC2_RepType = GDS_RepType;
      ISEC2_RepMode = GDS_RepMode;
      *numGridVals  = (ISEC2_PentaJ+1)*(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    = GDS_GME_NI2;
      ISEC2_GME_NI3    = GDS_GME_NI3;
      ISEC2_GME_ND     = GDS_GME_ND;
      ISEC2_GME_NI     = 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  = (ISEC2_GME_NI+1)*(ISEC2_GME_NI+1)*10;
      // iret = decodeGDS_TR(gds, gdspos, isec0, isec2, imisng);
    }
  else
    {
      static bool lwarn = true;
      ISEC2_NumLon = GDS_NumLon;
      ISEC2_NumLat = GDS_NumLat;
      *numGridVals  = ISEC2_NumLon*ISEC2_NumLat;
      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 = (gdsLen - 32) >> 2;
	}
      else
	{
	  locnv = 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++ )
	{
	  int iexp  = lgrib[4*i];
	  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++ )
	{
	  int iexp  = gds[locnv+4*i];
	  int imant = 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, int bdsLen, int numGridVals, int *iret)
{
  int 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.

  int bds_flag = BDS_Flag;

  // 0------- grid point
  // 1------- spherical harmonics

  bool lspherc = (bds_flag >> 7)&1;

  if ( lspherc ) isec4[2] = 128;
  else           isec4[2] = 0;

  // -0------  simple packing
  // -1------ complex packing

  bool lcomplex = (bds_flag >> 6)&1;

  if ( lcomplex ) isec4[3] = 64;
  else            isec4[3] =  0;

  // ---0---- No additional flags
  // ---1---- No additional flags

  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)

  int bds_ubits = bds_flag & 0xF;
  
  // scale factor (2 bytes)
  int jscale = BDS_BinScale;

  // check for missing data indicators.

  int iexp  = bds[ 6];
  int imant = GET_UINT3(bds[ 7], bds[ 8], bds[ 9]);

  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.

  ISEC4_NumBits = BDS_NumBits;

  // octet number of start of packed data calculated from start of block 4 - 1

  unsigned 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

	  int jup = bds[zoff+15];
	  int kup = bds[zoff+16];
	  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   = (jup+1)*(jup+2);

	  if ( dfunc != 'J' )
	    for ( int i = 0; i < ioff; i++ )
	      {
		if ( imiss )
		  *fpdata++ = 0.0;
		else
		  {
		    int iexp2  = (bds[locnd+4*i]);
		    int imant2 = GET_UINT3(bds[locnd+4*i+1], bds[locnd+4*i+2], bds[locnd+4*i+3]);
		    *fpdata++ = (T)decfp2(iexp2,imant2);
		  }
	      }
	  
	  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)           */

  int jlend = bdsLen - locnd;

  if ( ISEC4_NumBits == 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;
      jlend -= ioff;
    }
  else
    {
      jlend = (int) (((long)jlend*8 - bds_ubits) / ISEC4_NumBits);
    }

  ISEC4_NumValues        = jlend + ioff;
  ISEC4_NumNonMissValues = 0;

  if ( lcompress )
    {
      size_t len = ((size_t) ((bds[17]<<16)+(bds[18]<<8)+bds[19]));

      ISEC4_NumValues = (int)(len*8/(size_t)ISEC4_NumBits);

      if ( lspherc ) ISEC4_NumValues += lcomplex ? 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, (size_t)jlend*sizeof(T));
  else
    {
      igrib += locnd;

      TEMPLATE(decode_array,T)(igrib, 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) )
	  {
	    T zserr = fsec4[1];
	    for ( int i = 1; i < ISEC4_NumValues; i++ ) fsec4[i] -= zserr;
	  }
      }

  if ( decscale )
    {
      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];
  int isLen = decodeIS(is, isec0, iret);

  int gribLen = 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.

  */
  if ( ISEC0_GRIB_Len == 24 && ISEC0_GRIB_Version == 0 )
    {
      // Set length of GRIB message to missing data value.
      ISEC0_GRIB_Len = 0;
    }

  // ----------------------------------------------------------------
  // PDS Product Definition Section (Section 1)
  // ----------------------------------------------------------------
  UCHAR *pds = is + isLen;
  int pdsLen = decodePDS(pds, isec0, isec1);

  // ----------------------------------------------------------------
  // GDS Grid Description Section (Section 2)
  // ----------------------------------------------------------------
  int numGridVals = 0;
  bool gdsIncluded = ISEC1_Sec2Or3Flag & 128;
  int gdsLen = 0;
  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;
  bool bmsIncluded = ISEC1_Sec2Or3Flag & 64;
  int bmsLen = 0, bitmapSize = 0, imaskSize = 0;
  if ( bmsIncluded )
    {
      bms = is + isLen + pdsLen + gdsLen;
      bmsLen = BMS_Len;

      imaskSize = (bmsLen - 6)<<3;
      bitmapSize = imaskSize - BMS_UnusedBits;
    }

  // ----------------------------------------------------------------
  // BDS Binary Data Section (Section 4)
  // ----------------------------------------------------------------
  UCHAR *bds = is + isLen + pdsLen + gdsLen + bmsLen;
  int 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.
  */
  bool llarge = (gribLen > JP23SET && bdsLen <= 120);
  if ( llarge )
    {
      gribLen &= JP23SET;
      gribLen *= 120;
      ISEC0_GRIB_Len = gribLen;
      bdsLen = correct_bdslen(bdsLen, gribLen, 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 = bitmapSize;

      if ( dfunc != 'J' || bitmapSize == ISEC4_NumNonMissValues )
	{
	  GRIBPACK bitmap;
	  /*
	  unsigned char *bitmap;
	  bitmap = BMS_Bitmap;
	  int j = ISEC4_NumNonMissValues;
	  for ( int i = ISEC4_NumValues-1; i >= 0; i-- )
	    {
	      if ( (bitmap[i/8]>>(7-(i&7)))&1 )
		fsec4[i] = fsec4[--j];
	      else
		fsec4[i] = 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 ( int i = imaskSize/8-1; i >= 0; 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;
  const int esLen = 4;
  gribLen = isLen + pdsLen + gdsLen + bmsLen + bdsLen + esLen;

  if ( !llarge && ISEC0_GRIB_Len && ISEC0_GRIB_Len < gribLen )
    Warning("Inconsistent length of GRIB message (grib_message_size=%d < grib_record_size=%d)!", ISEC0_GRIB_Len, gribLen);

  ISEC0_GRIB_Len = gribLen;

  *kword = (int)(((size_t)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
int TEMPLATE(decodeGDS,T)(unsigned char  *gds, int *isec0, int *isec2, T *fsec2, int *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));

  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;
	  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 )
    {
      unsigned locnl = GDS_PVPL - 1 + (VertCoorTab * 4 * GDS_NV);
      unsigned jlenl = (gdsLen - locnl)  >> 1;
      if ( jlenl == GDS_NumLat )
	{
	  *numGridVals = 0;
	  ISEC2_Reduced = true;
	  for ( unsigned i = 0; i < jlenl; i++ )
	    {
	      ISEC2_ReducedPoints(i) = GET_UINT2(gds[locnl+2*i], gds[locnl+2*i+1]);
	      *numGridVals += ISEC2_ReducedPoints(i);
	    }
	}
      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    = GDS_NumLat;
      if ( ! ReducedGrid )
	{
	  ISEC2_NumLon = GDS_NumLon;
	  *numGridVals  = ISEC2_NumLon*ISEC2_NumLat;
	}
      ISEC2_FirstLat  = GDS_FirstLat;
      ISEC2_FirstLon  = GDS_FirstLon;
      ISEC2_ResFlag   = GDS_ResFlag;
      ISEC2_LastLat   = GDS_LastLat;
      ISEC2_LastLon   = GDS_LastLon;
      ISEC2_LonIncr   = GDS_LonIncr;

      ISEC2_NumPar    = 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    = GDS_NumLon;
      ISEC2_NumLat    = GDS_NumLat;
      *numGridVals  = ISEC2_NumLon*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  = GDS_PentaJ; /* Truncation */
      ISEC2_PentaK  = GDS_PentaK;
      ISEC2_PentaM  = GDS_PentaM;
      ISEC2_RepType = GDS_RepType;
      ISEC2_RepMode = GDS_RepMode;
      *numGridVals  = (ISEC2_PentaJ+1)*(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    = GDS_GME_NI2;
      ISEC2_GME_NI3    = GDS_GME_NI3;
      ISEC2_GME_ND     = GDS_GME_ND;
      ISEC2_GME_NI     = 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  = (ISEC2_GME_NI+1)*(ISEC2_GME_NI+1)*10;
      // iret = decodeGDS_TR(gds, gdspos, isec0, isec2, imisng);
    }
  else
    {
      static bool lwarn = true;
      ISEC2_NumLon = GDS_NumLon;
      ISEC2_NumLat = GDS_NumLat;
      *numGridVals  = ISEC2_NumLon*ISEC2_NumLat;
      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 = (gdsLen - 32) >> 2;
	}
      else
	{
	  locnv = 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++ )
	{
	  int iexp  = lgrib[4*i];
	  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++ )
	{
	  int iexp  = gds[locnv+4*i];
	  int imant = 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, int bdsLen, int numGridVals, int *iret)
{
  int 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.

  int bds_flag = BDS_Flag;

  // 0------- grid point
  // 1------- spherical harmonics

  bool lspherc = (bds_flag >> 7)&1;

  if ( lspherc ) isec4[2] = 128;
  else           isec4[2] = 0;

  // -0------  simple packing
  // -1------ complex packing

  bool lcomplex = (bds_flag >> 6)&1;

  if ( lcomplex ) isec4[3] = 64;
  else            isec4[3] =  0;

  // ---0---- No additional flags
  // ---1---- No additional flags

  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)

  int bds_ubits = bds_flag & 0xF;
  
  // scale factor (2 bytes)
  int jscale = BDS_BinScale;

  // check for missing data indicators.

  int iexp  = bds[ 6];
  int imant = GET_UINT3(bds[ 7], bds[ 8], bds[ 9]);

  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.

  ISEC4_NumBits = BDS_NumBits;

  // octet number of start of packed data calculated from start of block 4 - 1

  unsigned 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

	  int jup = bds[zoff+15];
	  int kup = bds[zoff+16];
	  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   = (jup+1)*(jup+2);

	  if ( dfunc != 'J' )
	    for ( int i = 0; i < ioff; i++ )
	      {
		if ( imiss )
		  *fpdata++ = 0.0;
		else
		  {
		    int iexp2  = (bds[locnd+4*i]);
		    int imant2 = GET_UINT3(bds[locnd+4*i+1], bds[locnd+4*i+2], bds[locnd+4*i+3]);
		    *fpdata++ = (T)decfp2(iexp2,imant2);
		  }
	      }
	  
	  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)           */

  int jlend = bdsLen - locnd;

  if ( ISEC4_NumBits == 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;
      jlend -= ioff;
    }
  else
    {
      jlend = (int) (((long)jlend*8 - bds_ubits) / ISEC4_NumBits);
    }

  ISEC4_NumValues        = jlend + ioff;
  ISEC4_NumNonMissValues = 0;

  if ( lcompress )
    {
      size_t len = ((size_t) ((bds[17]<<16)+(bds[18]<<8)+bds[19]));

      ISEC4_NumValues = (int)(len*8/(size_t)ISEC4_NumBits);

      if ( lspherc ) ISEC4_NumValues += lcomplex ? 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, (size_t)jlend*sizeof(T));
  else
    {
      igrib += locnd;

      TEMPLATE(decode_array,T)(igrib, 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) )
	  {
	    T zserr = fsec4[1];
	    for ( int i = 1; i < ISEC4_NumValues; i++ ) fsec4[i] -= zserr;
	  }
      }

  if ( decscale )
    {
      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];
  int isLen = decodeIS(is, isec0, iret);

  int gribLen = 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.

  */
  if ( ISEC0_GRIB_Len == 24 && ISEC0_GRIB_Version == 0 )
    {
      // Set length of GRIB message to missing data value.
      ISEC0_GRIB_Len = 0;
    }

  // ----------------------------------------------------------------
  // PDS Product Definition Section (Section 1)
  // ----------------------------------------------------------------
  UCHAR *pds = is + isLen;
  int pdsLen = decodePDS(pds, isec0, isec1);

  // ----------------------------------------------------------------
  // GDS Grid Description Section (Section 2)
  // ----------------------------------------------------------------
  int numGridVals = 0;
  bool gdsIncluded = ISEC1_Sec2Or3Flag & 128;
  int gdsLen = 0;
  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;
  bool bmsIncluded = ISEC1_Sec2Or3Flag & 64;
  int bmsLen = 0, bitmapSize = 0, imaskSize = 0;
  if ( bmsIncluded )
    {
      bms = is + isLen + pdsLen + gdsLen;
      bmsLen = BMS_Len;

      imaskSize = (bmsLen - 6)<<3;
      bitmapSize = imaskSize - BMS_UnusedBits;
    }

  // ----------------------------------------------------------------
  // BDS Binary Data Section (Section 4)
  // ----------------------------------------------------------------
  UCHAR *bds = is + isLen + pdsLen + gdsLen + bmsLen;
  int 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.
  */
  bool llarge = (gribLen > JP23SET && bdsLen <= 120);
  if ( llarge )
    {
      gribLen &= JP23SET;
      gribLen *= 120;
      ISEC0_GRIB_Len = gribLen;
      bdsLen = correct_bdslen(bdsLen, gribLen, 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 = bitmapSize;

      if ( dfunc != 'J' || bitmapSize == ISEC4_NumNonMissValues )
	{
	  GRIBPACK bitmap;
	  /*
	  unsigned char *bitmap;
	  bitmap = BMS_Bitmap;
	  int j = ISEC4_NumNonMissValues;
	  for ( int i = ISEC4_NumValues-1; i >= 0; i-- )
	    {
	      if ( (bitmap[i/8]>>(7-(i&7)))&1 )
		fsec4[i] = fsec4[--j];
	      else
		fsec4[i] = 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 ( int i = imaskSize/8-1; i >= 0; 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;
  const int esLen = 4;
  gribLen = isLen + pdsLen + gdsLen + bmsLen + bdsLen + esLen;

  if ( !llarge && ISEC0_GRIB_Len && ISEC0_GRIB_Len < gribLen )
    Warning("Inconsistent length of GRIB message (grib_message_size=%d < grib_record_size=%d)!", ISEC0_GRIB_Len, gribLen);

  ISEC0_GRIB_Len = gribLen;

  *kword = (int)(((size_t)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:
 */

/* 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;

  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;

  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;

  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;

  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            */
  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                    */    
    }

  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
	{
	  long i, localextlen;
	  localextlen = getLocalExtLen(isec1);
	  for ( i = 0; i < localextlen; i++ )
	    {
	      Put1Byte(isec1[24+i]);
	    }
	}
    }
}




#ifdef T
#undef T
#endif
#define T double
#ifdef T


#define round_float roundf
#define round_double round

#if 0
#define CGRIBEX_FPSCALE(data) (TEMPLATE(round,T)(((data) - zref) * factor))
#else
#define CGRIBEX_FPSCALE(data) (((data) - zref) * factor + (T)0.5)
#endif

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)
{
  U_BYTEORDER;
  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
    {
      uint16_t ui16;
      for ( size_t i = 0; i < datasize; i++ )
        {
          ui16 = (uint16_t)(CGRIBEX_FPSCALE(data[i]));
          sgrib[i] = gribSwapByteOrder_uint16(ui16);
        }
    }

  *gz += 2*datasize;
}
/*
static
void TEMPLATE(encode_array_2byte,T)(size_t datasize, GRIBPACK *restrict lGrib,
				    const T *restrict data, T zref, T factor, size_t *gz)
{
  size_t i, z = *gz;
  uint16_t ui16;
  T tmp;

#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 ( i = 0; i < datasize; i++ )
    {
      tmp = CGRIBEX_FPSCALE(data[i]);
      ui16 = (uint16_t) tmp;
      lGrib[z  ] = ui16 >>  8;
      lGrib[z+1] = ui16;
      z += 2;
    }

  *gz = z;
}
*/
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)
{
#if defined _GET_X86_COUNTER || defined _GET_MACH_COUNTER 
  uint64_t start_minmax, end_minmax;
#endif
  uint32_t ui32;
  size_t i, z = *gz;
  T tmp;

  data += packStart;
  datasize -= packStart;

  if      ( numBits ==  8 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(2, "pack 8 bit base");
#endif

#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 ( i = 0; i < datasize; i++ )
	{
          tmp = CGRIBEX_FPSCALE(data[i]);
	  lGrib[z  ] = (GRIBPACK)tmp;
          z++;
	}

#ifdef _GET_IBM_COUNTER 
      hpmStop(2);
#endif
    }
  else if ( numBits == 16 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(3, "pack 16 bit base");
#elif defined _GET_X86_COUNTER 
      start_minmax = _rdtsc();
#elif defined _GET_MACH_COUNTER 
      start_minmax = mach_absolute_time();
#endif
      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);
        }

#if defined _GET_X86_COUNTER || defined _GET_MACH_COUNTER
#if defined _GET_X86_COUNTER 
      end_minmax = _rdtsc();
#elif defined _GET_MACH_COUNTER 
      end_minmax = mach_absolute_time();
#endif
#if defined _ENABLE_AVX
      printf("AVX encoding cycles:: %" PRIu64 "\n", end_minmax-start_minmax);
#elif defined _ENABLE_SSE4_1
      printf("SSE 4.1 encoding cycles:: %" PRIu64 "\n", end_minmax-start_minmax);
#else
      printf("loop encoding cycles:: %" PRIu64 "\n", end_minmax-start_minmax);
#endif  
#endif
      
#ifdef _GET_IBM_COUNTER 
      hpmStop(3);
#endif
    }
  else if ( numBits == 24 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(4, "pack 24 bit base");
#endif

#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 ( i = 0; i < datasize; i++ )
	{
          tmp = CGRIBEX_FPSCALE(data[i]);
          ui32 = (uint32_t) tmp;
          lGrib[z  ] =  (GRIBPACK)(ui32 >> 16);
          lGrib[z+1] =  (GRIBPACK)(ui32 >>  8);
          lGrib[z+2] =  (GRIBPACK)ui32;
          z += 3;
	}

#ifdef _GET_IBM_COUNTER 
      hpmStop(4);
#endif
    }
  else if ( numBits == 32 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(5, "pack 32 bit base");
#endif

#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 ( i = 0; i < datasize; i++ )
	{
          tmp = CGRIBEX_FPSCALE(data[i]);
          ui32 = (uint32_t) tmp;
          lGrib[z  ] =  (GRIBPACK)(ui32 >> 24);
          lGrib[z+1] =  (GRIBPACK)(ui32 >> 16);
          lGrib[z+2] =  (GRIBPACK)(ui32 >>  8);
          lGrib[z+3] =  (GRIBPACK)ui32;
          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;
}

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)
{
  U_BYTEORDER;
  size_t i, j, 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;
  T 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);
      for ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
	{
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	    }
	  for (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 (j = 0; j < residual; j++) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	}
      for (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 ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
	{
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
	    dval[j] = CGRIBEX_FPSCALE(data[j]);
	  if ( IS_BIGENDIAN() )
	    {
	      for (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 (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
		{
		  ival = (uint16_t) dval[j];
                  *sgrib++ = gribSwapByteOrder_uint16(ival);
		}
	      z += 2*CGRIBEX__UNROLL_DEPTH_2;
	    }
	}
      for (j = 0; j < residual; j++) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (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 (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 ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
	{
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[j]);
	    }
	  for (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 (j = 0; j < residual; j++) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      for (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 ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
        {
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
            dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	  if ( IS_BIGENDIAN() )
	    {
	      for (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 (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 (j = 0; j < residual; j++) 
	{
          dval[j] = CGRIBEX_FPSCALE(data[ofs+j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (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 (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;
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */

#ifdef T
#undef T
#endif
#define T float
#ifdef T


#define round_float roundf
#define round_double round

#if 0
#define CGRIBEX_FPSCALE(data) (TEMPLATE(round,T)(((data) - zref) * factor))
#else
#define CGRIBEX_FPSCALE(data) (((data) - zref) * factor + (T)0.5)
#endif

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)
{
  U_BYTEORDER;
  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
    {
      uint16_t ui16;
      for ( size_t i = 0; i < datasize; i++ )
        {
          ui16 = (uint16_t)(CGRIBEX_FPSCALE(data[i]));
          sgrib[i] = gribSwapByteOrder_uint16(ui16);
        }
    }

  *gz += 2*datasize;
}
/*
static
void TEMPLATE(encode_array_2byte,T)(size_t datasize, GRIBPACK *restrict lGrib,
				    const T *restrict data, T zref, T factor, size_t *gz)
{
  size_t i, z = *gz;
  uint16_t ui16;
  T tmp;

#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 ( i = 0; i < datasize; i++ )
    {
      tmp = CGRIBEX_FPSCALE(data[i]);
      ui16 = (uint16_t) tmp;
      lGrib[z  ] = ui16 >>  8;
      lGrib[z+1] = ui16;
      z += 2;
    }

  *gz = z;
}
*/
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)
{
#if defined _GET_X86_COUNTER || defined _GET_MACH_COUNTER 
  uint64_t start_minmax, end_minmax;
#endif
  uint32_t ui32;
  size_t i, z = *gz;
  T tmp;

  data += packStart;
  datasize -= packStart;

  if      ( numBits ==  8 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(2, "pack 8 bit base");
#endif

#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 ( i = 0; i < datasize; i++ )
	{
          tmp = CGRIBEX_FPSCALE(data[i]);
	  lGrib[z  ] = (GRIBPACK)tmp;
          z++;
	}

#ifdef _GET_IBM_COUNTER 
      hpmStop(2);
#endif
    }
  else if ( numBits == 16 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(3, "pack 16 bit base");
#elif defined _GET_X86_COUNTER 
      start_minmax = _rdtsc();
#elif defined _GET_MACH_COUNTER 
      start_minmax = mach_absolute_time();
#endif
      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);
        }

#if defined _GET_X86_COUNTER || defined _GET_MACH_COUNTER
#if defined _GET_X86_COUNTER 
      end_minmax = _rdtsc();
#elif defined _GET_MACH_COUNTER 
      end_minmax = mach_absolute_time();
#endif
#if defined _ENABLE_AVX
      printf("AVX encoding cycles:: %" PRIu64 "\n", end_minmax-start_minmax);
#elif defined _ENABLE_SSE4_1
      printf("SSE 4.1 encoding cycles:: %" PRIu64 "\n", end_minmax-start_minmax);
#else
      printf("loop encoding cycles:: %" PRIu64 "\n", end_minmax-start_minmax);
#endif  
#endif
      
#ifdef _GET_IBM_COUNTER 
      hpmStop(3);
#endif
    }
  else if ( numBits == 24 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(4, "pack 24 bit base");
#endif

#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 ( i = 0; i < datasize; i++ )
	{
          tmp = CGRIBEX_FPSCALE(data[i]);
          ui32 = (uint32_t) tmp;
          lGrib[z  ] =  (GRIBPACK)(ui32 >> 16);
          lGrib[z+1] =  (GRIBPACK)(ui32 >>  8);
          lGrib[z+2] =  (GRIBPACK)ui32;
          z += 3;
	}

#ifdef _GET_IBM_COUNTER 
      hpmStop(4);
#endif
    }
  else if ( numBits == 32 )
    {
#ifdef _GET_IBM_COUNTER 
      hpmStart(5, "pack 32 bit base");
#endif

#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 ( i = 0; i < datasize; i++ )
	{
          tmp = CGRIBEX_FPSCALE(data[i]);
          ui32 = (uint32_t) tmp;
          lGrib[z  ] =  (GRIBPACK)(ui32 >> 24);
          lGrib[z+1] =  (GRIBPACK)(ui32 >> 16);
          lGrib[z+2] =  (GRIBPACK)(ui32 >>  8);
          lGrib[z+3] =  (GRIBPACK)ui32;
          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;
}

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)
{
  U_BYTEORDER;
  size_t i, j, 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;
  T 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);
      for ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
	{
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	    }
	  for (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 (j = 0; j < residual; j++) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	}
      for (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 ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
	{
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
	    dval[j] = CGRIBEX_FPSCALE(data[j]);
	  if ( IS_BIGENDIAN() )
	    {
	      for (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 (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
		{
		  ival = (uint16_t) dval[j];
                  *sgrib++ = gribSwapByteOrder_uint16(ival);
		}
	      z += 2*CGRIBEX__UNROLL_DEPTH_2;
	    }
	}
      for (j = 0; j < residual; j++) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (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 (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 ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
	{
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
	    {
	      dval[j] = CGRIBEX_FPSCALE(data[j]);
	    }
	  for (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 (j = 0; j < residual; j++) 
	{
	  dval[j] = CGRIBEX_FPSCALE(data[j]);
	}
      for (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 ( i = 0; i < datasize - residual; i += CGRIBEX__UNROLL_DEPTH_2 )
        {
	  for (j = 0; j < CGRIBEX__UNROLL_DEPTH_2; j++)
            dval[j] = CGRIBEX_FPSCALE(data[i+j]);
	  if ( IS_BIGENDIAN() )
	    {
	      for (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 (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 (j = 0; j < residual; j++) 
	{
          dval[j] = CGRIBEX_FPSCALE(data[ofs+j]);
	}
      if ( IS_BIGENDIAN() )
	{
	  for (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 (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;
}

#endif /* T */

/*
 * Local Variables:
 * mode: c
 * End:
 */


#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 += 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 += 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:
 */

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[] = "1.9.5";
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 <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

#if  defined(HAVE_MALLOC_H)
#  include <malloc.h>
#endif


enum             {MALLOC_FUNC=0, CALLOC_FUNC, REALLOC_FUNC, FREE_FUNC};
static const char *memfunc[] = {"Malloc", "Calloc", "Realloc", "Free"};

#undef   MEM_UNDEFID
#define  MEM_UNDEFID  -1

#define  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 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 *functionname, const char *fmt, ...)
{
  va_list args;

  va_start(args, fmt);

  printf("\n");
   fprintf(stderr, "Internal problem (%s) : ", functionname);
  vfprintf(stderr, fmt, args);
   fprintf(stderr, "\n");

  va_end(args);

  exit(EXIT_FAILURE);
}

static
void memError(const char *functionname, 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",
	  functionname, 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 *functionname, 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 ( functionname != NULL )
    fprintf(stderr, " (%s)", functionname);
  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;

  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;

  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
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;

  if ( file )
    {
      const char *filename = get_filename(file);
      size_t len = strlen(filename);
      if ( len > MEM_MAXNAME-1 ) len = MEM_MAXNAME-1;

      (void) memcpy(memTable[memID].filename, filename, len);
      memTable[memID].filename[len] = '\0';
    }
  else
    {
      (void) strcpy(memTable[memID].filename, "unknown");
    }

  if ( functionname )
    {
      size_t len = strlen(functionname);
      if ( len > MEM_MAXNAME-1 ) len = MEM_MAXNAME-1;

      (void) memcpy(memTable[memID].functionname, functionname, len);
      memTable[memID].functionname[len] = '\0';
    }
  else
    {
      (void) strcpy(memTable[memID].functionname, "unknown");
    }

  MaxMemObjs++;
  MemObjs++;
  MemUsed += size*nobj;
  if ( MemUsed > MaxMemUsed ) MaxMemUsed = MemUsed;

  return item++;
}

static
int memListChangeEntry(void *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 )
	if ( memTable[memID].ptr == ptrold ) break;
      memID++;
    }

  if ( memID == memTableSize )
    {
      if ( 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;

      if ( file )
	{
          const char *filename = get_filename(file);
	  size_t len = strlen(filename);
	  if ( len > MEM_MAXNAME-1 ) len = MEM_MAXNAME-1;

	  (void) memcpy(memTable[memID].filename, filename, len);
	  memTable[memID].filename[len] = '\0';
	}
      else
	{
	  (void) strcpy(memTable[memID].filename, "unknown");
	}

      if ( functionname )
	{
	  size_t len = strlen(functionname);
	  if ( len > MEM_MAXNAME-1 ) len = MEM_MAXNAME-1;

	  (void) memcpy(memTable[memID].functionname, functionname, len);
	  memTable[memID].functionname[len] = '\0';
	}
      else
	{
	  (void) strcpy(memTable[memID].functionname, "unknown");
	}

      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 )
    {
      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 )
    {
      int item;
      size_t size;

      if ( (item = memListDeleteEntry(ptr, &size)) >= 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;
#if  defined  (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:
 */
#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, "Error (%s): ", 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, "Error (%s): ", 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, "ERROR, %s, %s, line %d%s%s\nerrorString: \"",
          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, "Warning (%s): ", 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, "%-18s: ", caller);
  vfprintf(stdout, fmt, args);
   fprintf(stdout, "\n");

  va_end(args);
}
/*
 * 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_SINGLE_PRECISION = 4,
  EXSE_DOUBLE_PRECISION = 8,
};

#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>



enum {
  EXT_HEADER_LEN = 4,
};


static int initExtLib       = 0;
static int extDefaultPrec   = 0;
static int extDefaultNumber = EXT_REAL;


/*
 * A version string.
 */
#undef  LIBVERSION
#define LIBVERSION      1.4.1
#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)
{
  EXT_Debug = debug;
  if (EXT_Debug) Message("debug level %d", debug);
}


static
void extLibInit()
{
  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 '4': extDefaultPrec = EXSE_SINGLE_PRECISION; break;
		  case '8': extDefaultPrec = EXSE_DOUBLE_PRECISION; break;
		  default:
		    Message("Invalid digit in %s: %s", envName, envString);
		  }
		break;
	      }
	    case 'c':
	      {
		extDefaultNumber = EXT_COMP;
		switch ( (int) envString[1] )
		  {
		  case '4': extDefaultPrec = EXSE_SINGLE_PRECISION; break;
		  case '8': extDefaultPrec = EXSE_DOUBLE_PRECISION; break;
		  default:
		    Message("Invalid digit in %s: %s", envName, envString);
		  }
		break;
	      }
	    default:
              {
                Message("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_SUINT32(buffer);

  if ( EXT_Debug )
    Message("blocklen = %d sblocklen = %d", blocklen, sblocklen);

  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_SUINT32(pbuf);
      pbuf = buffer+sblocklen+4;  data  = (size_t) get_SUINT32(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_SUINT64(pbuf);
      pbuf = buffer+sblocklen+4;  data  = (size_t) get_SUINT32(pbuf);
    }

  fileRewind(fileID);

  if ( EXT_Debug )
    {
      Message("swap = %d fact = %d", *swap, fact);
      Message("dimxy = %lu data = %lu", dimxy, data);
    }

  int found = data && (dimxy*fact == data || dimxy*fact*2 == data);
  return found;
}


int extInqHeader(void *ext, int *header)
{
  extrec_t *extp = (extrec_t *) ext;

  for (size_t i = 0; i < EXT_HEADER_LEN; i++) header[i] = extp->header[i];

  if ( EXT_Debug ) Message("datasize = %lu", extp->datasize);

  return 0;
}


int extDefHeader(void *ext, const int *header)
{
  extrec_t *extp = (extrec_t *) ext;

  for (size_t 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 = %lu", 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;
  void *buffer   = extp->buffer;
  int rprec    = extp->prec;

  switch ( rprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	if ( sizeof(FLT32) == 4 )
	  {
	    if ( byteswap ) swap4byte(buffer, datasize);

	    if ( rprec == prec )
	      memcpy(data, buffer, datasize*sizeof(FLT32));
	    else
	      for (size_t i = 0; i < datasize; ++i)
		((double *) data)[i] = (double) ((float *) buffer)[i];
	  }
	else
	  {
	    Error("not implemented for %d byte float", sizeof(FLT32));
	  }
	break;
      }
    case EXSE_DOUBLE_PRECISION:
	if ( sizeof(FLT64) == 8 )
	  {
	    if ( byteswap ) swap8byte(buffer, datasize);

	    if ( rprec == prec )
	      memcpy(data, buffer, datasize*sizeof(FLT64));
	    else
	      for (size_t i = 0; i < datasize; ++i)
		((float *) data)[i] = (float) ((double *) buffer)[i];
	  }
	else
	  {
	    Error("not implemented for %d byte float", sizeof(FLT64));
	  }
	break;
    default:
      {
	Error("unexpected data precision %d", rprec);
	break;
      }
    }

  return ierr;
}


int extInqDataSP(void *ext, float *data)
{
  return extInqData((extrec_t *)ext, EXSE_SINGLE_PRECISION, (void *) data);
}


int extInqDataDP(void *ext, double *data)
{
  return extInqData((extrec_t *)ext, EXSE_DOUBLE_PRECISION, (void *) data);
}


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;

  void *buffer = extp->buffer;
  size_t buffersize = extp->buffersize;

  if ( buffersize != blocklen )
    {
      buffersize = blocklen;
      buffer = Realloc(buffer, buffersize);
      extp->buffer = buffer;
      extp->buffersize = buffersize;
    }

  switch ( rprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	if ( rprec == prec )
	  memcpy(buffer, data, datasize*sizeof(FLT32));
	else
	  for (size_t i = 0; i < datasize; i++)
	    ((float *) buffer)[i] = (float) ((double *) data)[i];

	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	if ( rprec == prec )
	  memcpy(buffer, data, datasize*sizeof(FLT64));
	else
	  for (size_t i = 0; i < datasize; i++)
	    ((double *) buffer)[i] = (double) ((float *) data)[i];

	break;
      }
    default:
      {
	Error("unexpected data precision %d", rprec);
        break;
      }
    }

  return 0;
}


int extDefDataSP(void *ext, const float *data)
{
  return extDefData(ext, EXSE_SINGLE_PRECISION, (void *) data);
}


int extDefDataDP(void *ext, const double *data)
{
  return extDefData(ext, EXSE_DOUBLE_PRECISION, (void *) data);
}


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;

  switch ( hprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
        INT32 tempheader[4];
	binReadInt32(fileID, byteswap, EXT_HEADER_LEN, tempheader);

	for (size_t i = 0; i < EXT_HEADER_LEN; i++)
          extp->header[i] = (int)tempheader[i];

	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
        INT64 tempheader[4];
	binReadInt64(fileID, byteswap, EXT_HEADER_LEN, tempheader);

	for (size_t i = 0; i < EXT_HEADER_LEN; i++)
          extp->header[i] = (int)tempheader[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 = %lu", extp->datasize);

  blocklen = binReadF77Block(fileID, byteswap);

  void *buffer = extp->buffer;
  size_t buffersize = (size_t)extp->buffersize;

  if ( buffersize < blocklen )
    {
      buffersize = blocklen;
      buffer = Realloc(buffer, buffersize);
      extp->buffer = buffer;
      extp->buffersize = buffersize;
    }

  size_t dprec = blocklen / extp->datasize;

  if ( dprec == hprec )
    {
      extp->number = EXT_REAL;
    }
  else if ( dprec == 2*hprec )
    {
      dprec /= 2;
      extp->datasize *= 2;
      extp->number = EXT_COMP;
    }

  if ( dprec != EXSE_SINGLE_PRECISION && dprec != EXSE_DOUBLE_PRECISION )
    {
      Warning("Unexpected data precision %d", dprec);
      return -1;
    }

  fileRead(fileID, 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 { INT32 i32[EXT_HEADER_LEN]; INT64 i64[EXT_HEADER_LEN]; } 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;

  binWriteF77Block(fileID, byteswap, blocklen);

  switch ( rprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	for (size_t i = 0; i < EXT_HEADER_LEN; i++)
          tempheader.i32[i] = (INT32) header[i];

	binWriteInt32(fileID, byteswap, EXT_HEADER_LEN, tempheader.i32);

	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	for (size_t i = 0; i < EXT_HEADER_LEN; i++)
          tempheader.i64[i] = (INT64) header[i];

	binWriteInt64(fileID, byteswap, EXT_HEADER_LEN, tempheader.i64);

	break;
      }
    default:
      {
	Error("unexpected header precision %d", rprec);
        break;
      }
    }

  binWriteF77Block(fileID, byteswap, blocklen);

  size_t datasize = (size_t)header[3];
  if ( number == EXT_COMP ) datasize *= 2;
  blocklen = datasize * (size_t)rprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  extp->datasize = datasize;

  void *buffer = extp->buffer;

  switch ( rprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	binWriteFlt32(fileID, byteswap, datasize, (FLT32 *) buffer);
	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	binWriteFlt64(fileID, byteswap, datasize, (FLT64 *) 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:
 */
#ifdef HAVE_CONFIG_H
#endif

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

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

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>  // gettimeofday()
#endif


#ifdef CDI
#endif

#ifndef O_BINARY
#define O_BINARY 0
#endif

#ifndef strdup
char *strdup(const char *s);
#endif

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

#define 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;

#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)
{
  const 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()
{
#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)
{
  bfile_t *fileptr = file_to_pointer(fileID);
  return fileptr ? fflush(fileptr->fp) : 0;
}

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)
    {
      if (fileptr->mode == 'r' && fileptr->type == FILE_TYPE_OPEN)
        filepos = fileptr->position;
      else
        filepos = ftell(fileptr->fp);
    }

  if (FileDebug) Message("Position %ld", filepos);

  return filepos;
}

static int
file_set_buffer_pos(bfile_t *fileptr)
{
  const off_t position = fileptr->position;
  if (position < fileptr->bufferStart || position > fileptr->bufferEnd)
    {
      if (fileptr->bufferType == FILE_BUFTYPE_STD)
        fileptr->bufferPos = position;
      else
        fileptr->bufferPos = 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)
    if ((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;
                  Message("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)
{
#ifdef HAVE_LIBPTHREAD
  // initialize global API mutex lock
  pthread_mutex_init(&_file_mutex, NULL);
#endif

  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;

  const 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)
            {
              const 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
    {
      const 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();

  const 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)
    if (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
{
  int ret;
  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)
    {
      ret = fclose(fileptr->fp);
      if (ret == EOF) SysError("EOF returned for close of %s!", name);
    }
  else
    {
#ifdef HAVE_MMAP
      if (fileptr->buffer && fileptr->mappedSize)
        {
          ret = munmap(fileptr->buffer, fileptr->mappedSize);
          if (ret == -1) SysError("munmap error for close %s", fileptr->name);
          fileptr->buffer = NULL;
        }
#endif
      ret = close(fileptr->fd);
      if (ret == -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:
 */
#ifdef  HAVE_CONFIG_H
#endif

#include <stdio.h>
#include <float.h>
#include <math.h>

#ifndef  M_SQRT2
#define  M_SQRT2  1.41421356237309504880168872420969808
#endif



static
void cpledn(size_t kn, size_t kodd, double *pfn, double pdx, int kflag,
            double *pw, double *pdxn, double *pxmod)
{
  /* 1.0 Newton iteration step */

  double zdlx = pdx;
  double zdlk = 0.0;
  if ( kodd == 0 ) zdlk = 0.5*pfn[0];
  double zdlxn  = 0.0;
  double zdlldn = 0.0;

  size_t ik = 1;

  if ( kflag == 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);
      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);
    }

  return;
}

static
void gawl(double *pfn, double *pl, double *pw, size_t kn)
{
  double pmod = 0;
  double zw = 0;
  double zdlxn = 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;

  return;
}

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);
  double z;

  for ( size_t jgl = 1; jgl <= ins2; jgl++ )
    {
      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);

  return;
}


void gaussianLatitudes(double *latitudes, double *weights, size_t nlat)
{
  //gauaw_old(latitudes, weights, nlat);
  gauaw(nlat, latitudes, weights);
}


bool isGaussGrid(size_t ysize, double yinc, const double *yvals)
{
  bool lgauss = false;

  if ( IS_EQUAL(yinc, 0) && ysize > 2 ) // check if gaussian
    {
      size_t i;
      double *yv = (double *) Malloc(ysize*sizeof(double));
      double *yw = (double *) Malloc(ysize*sizeof(double));
      gaussianLatitudes(yv, yw, ysize);
      Free(yw);
      for ( i = 0; i < ysize; i++ )
        yv[i] = asin(yv[i])/M_PI*180.0;

      for ( i = 0; i < ysize; i++ )
        if ( fabs(yv[i] - yvals[i]) > ((yv[0] - yv[1])/500) ) break;

      if ( i == ysize ) lgauss = true;

      // check S->N
      if ( lgauss == false )
        {
          for ( i = 0; i < ysize; i++ )
            if ( fabs(yv[i] - yvals[ysize-i-1]) > ((yv[0] - yv[1])/500) ) break;

          if ( i == ysize ) lgauss = true;
        }

      Free(yv);
    }

  return lgauss;
}

/*
#define NGL  48

int main (int rgc, char *argv[])
{
  int ngl = NGL;
  double plo[NGL], pwo[NGL];
  double pl[NGL], pw[NGL];

  int i;

  gauaw(ngl, pl, pw);
  for (i = 0; i < ngl; i++)
    {
      pl[i]  = asin(pl[i])/M_PI*180.0;
      plo[i] = asin(plo[i])/M_PI*180.0;
    }

  for (i = 0; i < ngl; i++)
    {
      fprintf(stderr, "%4d%25.18f%25.18f%25.18f%25.18f\n", i+1, pl[i], pw[i], pl[i]-plo[i], pw[i]-pwo[i]);
    }

  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:
 */
#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);

      sprintf(gribapi_libvers, "%d.%d.%d", major_version, minor_version, revision_version);
      gribapi_libvers_init = true;
    }
#endif

  return (gribapi_libvers);
}


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\
{\
  const 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 = !strcmp(value, expectedValue);
  Free(value);
#else
  char *value = gribCopyString(gribHandle, key);
  int rc = value ? (strlen(value) + 1 == expectedLength ? !strcmp(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()
{
  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)
{
  char *result= (char *) Malloc(4+1+ 2+1+ 2+1+ 2+1+ 2+1+ 2+ 4+ 1);
  sprintf(result, "%04d-%02d-%02dT%02d:%02d:%02d.000", me->tm_year + 1900, me->tm_mon + 1, me->tm_mday, me->tm_hour, me->tm_min, me->tm_sec);
  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
  [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" },
                    {  0, "" }
  // 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) )
    {
      const int status = grib_get_string(gh, "stepType", stepType, &len);
      if ( status == 0 && len > 1 && len < 256 )
        {
          for (int i = TSTEP_INSTANT; cdiGribAPI_ts_str_map[i].sname[0]; ++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);
        }

#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");  // was incorrectly set before into accum
                       break;
              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);
}


int gribapiGetGridType(grib_handle *gh)
{
  int gridtype = GRID_GENERIC;
  const long gridDefinitionTemplateNumber = gribGetLongDefault(gh, "gridDefinitionTemplateNumber", -1);
  switch (gridDefinitionTemplateNumber)
    {
      case  GRIB2_GTYPE_LATLON:
        gridtype = ( gribGetLong(gh, "Ni") == (long) GRIB_MISSING_LONG ) ? GRID_GENERIC : GRID_LONLAT;
        break;
      case  GRIB2_GTYPE_GAUSSIAN:
        gridtype = ( gribGetLong(gh, "Ni") == (long) GRIB_MISSING_LONG ) ? GRID_GAUSSIAN_REDUCED : GRID_GAUSSIAN;
        break;
      case GRIB2_GTYPE_LATLON_ROT:   gridtype = GRID_PROJECTION; break;
      case GRIB2_GTYPE_LCC:          gridtype = CDI_PROJ_LCC; break;
      case GRIB2_GTYPE_STERE:        gridtype = CDI_PROJ_STERE; break;
      case GRIB2_GTYPE_SPECTRAL:     gridtype = GRID_SPECTRAL; break;
      case GRIB2_GTYPE_GME:          gridtype = GRID_GME; break;
      case GRIB2_GTYPE_UNSTRUCTURED: gridtype = GRID_UNSTRUCTURED; break;
      default:
        {
          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 gridtype;
}

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, int 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 ( IS_NOT_EQUAL(grid->x.first, 0) || IS_NOT_EQUAL(grid->x.last, 0) ) */
  {
    if ( grid->x.size > 1 )
      {
        if ( (grid->x.first > grid->x.last) && (grid->x.first >= 180) ) grid->x.first -= 360;

        if ( editionNumber <= 1 )
          {
            /* correct xinc if necessary */
            if ( IS_EQUAL(grid->x.first, 0) && grid->x.last > 354 )
              {
                double xinc = 360. / 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 gribapiGetGridRegular(grib_handle *gh, grid_t *grid, int editionNumber, int gridtype, size_t numberOfPoints)
{
  long lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Ni", &lpar);
  const size_t nlon = (size_t) lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Nj", &lpar);
  const 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. / 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);
  const size_t nlon = (size_t)lpar;
  FAIL_ON_GRIB_ERROR(grib_get_long, gh, "Ny", &lpar);
  const 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 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 = strdupx(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;
  const size_t nlon = (grib_get_long(gh, "Ni", &lpar) == 0) ? (size_t)lpar : 0;
  const size_t nlat = (grib_get_long(gh, "Nj", &lpar) == 0) ? (size_t)lpar : 0;

  grid->size = numberOfPoints;

  const 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;
  const 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)
    {
      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 ( 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 )
    {
      long temp;
      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 )
    {
      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*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)
      {
        //  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



#ifdef __cplusplus
extern "C" {
#endif

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;
}

void cdiCreateUUID(unsigned char uuid[CDI_UUID_SIZE]);

void cdiUUID2Str(const unsigned char uuid[], char uuidstr[]);
int cdiStr2UUID(const char *uuidstr, unsigned char uuid[]);

#if defined (__cplusplus)
}
#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:
 */
#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,
  RESH_DELETE,
  START     = 55555555,
  END       = 99999999
};

int reshUnpackResources(char * unpackBuffer, int unpackBufferSize,
                        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:
 */
#ifndef  VLIST_H
#define  VLIST_H

#ifdef  HAVE_CONFIG_H
#endif

#ifndef  ERROR_H
#endif

#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;
  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         chunktype;
  int         xyz;
  bool        missvalused; // true if missval is defined
  bool        lvalidrange;
  char       *extra;
  double      missval;
  double      scalefactor;
  double      addoffset;
  double      validrange[2];
  levinfo_t  *levinfo;
  int         comptype;     // compression type
  int         complevel;    // compression level
  cdi_keys_t  keys;
  cdi_atts_t  atts;
  int         iorank;

  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 vlistCheckVarID(const char *caller, int vlistID, int varID);
void     cdiVlistDestroy_(int vlistID);
int      vlistInqVarMissvalUsed(int vlistID, int varID);
int      vlistHasTime(int vlistID);

void     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
#ifndef __cplusplus
const
#endif
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)() = NULL;
int (*proj_lcc_to_lonlat_func)() = NULL;
int (*proj_lonlat_to_stere_func)() = NULL;
int (*proj_stere_to_lonlat_func)() = 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 */  "lcc",
  /* 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 );

static const resOps gridOps = {
  gridCompareP,
  gridDestroyP,
  gridPrintP,
  gridGetPackSize,
  gridPack,
  gridTxCode
};

static int  GRID_Debug = 0;   /* If set to 1, debugging */


grid_t *grid_to_pointer(int gridID)
{
  return (grid_t *)reshGetVal(gridID, &gridOps);
}

#define gridMark4Update(gridID) reshSetStatus(gridID, &gridOps, RESH_DESYNC_IN_USE)

static
bool cdiInqAttConvertedToFloat(int gridID, int atttype, const char *attname, int attlen, double *attflt)
{
  bool status = true;

  if ( atttype == CDI_DATATYPE_INT32 )
    {
      int attint;
      int *pattint = attlen > 1 ? (int*) malloc(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);
}

void grid_init(grid_t *gridptr)
{
  gridptr->self           = CDI_UNDEFID;
  gridptr->type           = 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->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->datatype       = CDI_DATATYPE_FLT64;
  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_SCANNINGMODE, 64);
}


static
void grid_free_components(grid_t *gridptr)
{
  void *p2free[] = { 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]);

  /*
  int gridID = gridptr->self;
  cdiDeleteKeys(gridID, CDI_XAXIS);
  cdiDeleteKeys(gridID, CDI_YAXIS);
  cdiDeleteKeys(gridID, CDI_GLOBAL);
  cdiDeleteAtts(gridID, CDI_GLOBAL);
  */
}

void grid_free(grid_t *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;

  const char *env = getenv("GRID_DEBUG");
  if ( env ) GRID_Debug = atoi(env);
}

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;

  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;

  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 )
          {
            const char gmapname[] = "Projection";
            cdiDefVarKeyBytes(&gridptr->keys, CDI_KEY_GRIDMAP_NAME, (const unsigned char *)gmapname, (int)sizeof(gmapname));

            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(int 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)/(xsize);
        }
      else
        {
          xinc = (xlast-xfirst)/(xsize-1);
        }
    }

  for ( int i = 0; i < xsize; ++i )
    xvals[i] = xfirst + 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(yvals, yw, ysize);
  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 )
    {
      const 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(int 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;
        int ny = (int) (180./(fabs(ylast-yfirst)/(ysize-1)) + 0.5);
        ny -= ny%2;
        if ( ny > ysize && ny < 4096 )
          {
            double *ytmp = (double *) Malloc((size_t)ny * sizeof (double));
            calc_gaussgrid(ytmp, ny, yfirst, ylast);

            int i;
            for ( i = 0; i < (ny-ysize); i++ )
              if ( fabs(ytmp[i] - yfirst) < deleps ) break;
            int nstart = i;

            lfound = (nstart+ysize-1) < ny && fabs(ytmp[nstart+ysize-1] - ylast) < deleps;
            if ( lfound )
              {
                for (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 (int i = 0; i < ysize; i++ ) yvals[i] = 0;
            yvals[0] = yfirst;
            yvals[ysize-1] = ylast;
          }
      }
}

static
void gridGenYvalsRegular(int 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)/(ysize-1);
      else if ( yfirst < ylast )
        yinc = (ylast-yfirst)/(ysize-1);
      else
        {
          if ( ysize%2 != 0 )
            {
              yinc = 180.0/(ysize-1);
              yfirst = -90;
            }
          else
            {
              yinc = 180.0/ysize;
              yfirst = -90 + yinc/2;
            }
        }
    }

  if ( yfirst > ylast && yinc > 0 ) yinc = -yinc;

  for (int i = 0; i < ysize; i++ )
    yvals[i] = yfirst + i*yinc;
}

// used also in CDO
void gridGenYvals(int gridtype, int 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, size_t 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, size_t size)
{
  if ( CDI_Debug ) Message("gridtype=%s  size=%zu", gridNamePtr(gridtype), size);

  xassert(size);
  gridInit();

  grid_t *gridptr = gridNewEntry(CDI_UNDEFID);
  if ( ! gridptr ) Error("No memory");

  const int gridID = gridptr->self;

  if ( CDI_Debug ) Message("gridID: %d", gridID);

  cdiGridTypeInit(gridptr, gridtype, size);

  return gridID;
}

static
void gridDestroyKernel( grid_t * gridptr )
{
  xassert ( gridptr );

  const int id = gridptr->self;

  grid_free_components(gridptr);
  Free( gridptr );

  reshRemove( id, &gridOps );
}

/*
@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)
{
  //cdiDeleteKeys(gridID, CDI_GLOBAL);
  //cdiDeleteAtts(gridID, CDI_GLOBAL);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->vtable->destroy(gridptr);
}

static
void gridDestroyP(void * gridptr)
{
  ((grid_t *)gridptr)->vtable->destroy((grid_t *)gridptr);
}


const char *gridNamePtr(int gridtype)
{
  const int size = (int) (sizeof(Grids)/sizeof(Grids[0]));

  const char *name = (gridtype >= 0 && gridtype < size) ? Grids[gridtype] : Grids[GRID_GENERIC];

  return name;
}


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);
}


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      ( strIsEqual(gmapname, "rotated_latitude_longitude") )   projtype = CDI_PROJ_RLL;
          else if ( strIsEqual(gmapname, "lambert_azimuthal_equal_area") ) projtype = CDI_PROJ_LAEA;
          else if ( strIsEqual(gmapname, "lambert_conformal_conic") )      projtype = CDI_PROJ_LCC;
          else if ( strIsEqual(gmapname, "sinusoidal") )                   projtype = CDI_PROJ_SINU;
          else if ( strIsEqual(gmapname, "polar_stereographic") )          projtype = CDI_PROJ_STERE;
          // clang-format on
          gridptr->projtype = projtype;
        }
    }

  return projtype;
}


void gridVerifyProj(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  const 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 size_t 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
*/
size_t 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 size;
}

static
int nsp2trunc(int 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(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, size_t 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, size_t xsize)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  const size_t gridSize = gridInqSize(gridID);
  if ( xsize > gridSize )
    Error("xsize %zu is greater then gridsize %zu", xsize, gridSize);

  const int gridType = gridInqType(gridID);
  if ( gridType == GRID_UNSTRUCTURED && xsize != gridSize )
    Error("xsize %zu must be equal to gridsize %zu for gridtype: %s", xsize, gridSize, gridNamePtr(gridType));
  if ( gridType == GRID_GAUSSIAN_REDUCED && xsize != 2 && xsize != gridSize )
    Error("xsize %zu must be equal to gridsize %zu for gridtype: %s", xsize, gridSize, gridNamePtr(gridType));

  if ( gridptr->x.size != xsize )
    {
      gridMark4Update(gridID);
      gridptr->x.size = xsize;
    }

  if ( gridType != GRID_UNSTRUCTURED && gridType != GRID_GAUSSIAN_REDUCED && gridType != GRID_PROJECTION )
    {
      const size_t axisproduct = gridptr->x.size*gridptr->y.size;
      if ( axisproduct > 0 && axisproduct != gridSize )
        Error("Inconsistent grid declaration! (xsize=%zu ysize=%zu gridsize=%zu)",
              gridptr->x.size, gridptr->y.size, gridSize);
    }
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void gridDefDatatype(int gridID, int datatype)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  if ( gridptr->datatype != datatype )
    {
      gridMark4Update(gridID);
      gridptr->datatype = datatype;
    }
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
int gridInqDatatype(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->datatype;
}

/*
@Function  gridInqXsize
@Title     Get the number of values of a X-axis

@Prototype size_t 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
*/
size_t gridInqXsize(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->x.size;
}

/*
@Function  gridDefYsize
@Title     Define the number of values of a Y-axis

@Prototype void gridDefYsize(int gridID, size_t 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, size_t ysize)
{
  grid_t *gridptr = grid_to_pointer(gridID);

  const size_t gridSize = gridInqSize(gridID);

  if ( ysize > gridSize )
    Error("ysize %zu is greater then gridsize %zu", ysize, gridSize);

  const int gridType = gridInqType(gridID);
  if ( gridType == GRID_UNSTRUCTURED && ysize != gridSize )
    Error("ysize %zu must be equal gridsize %zu for gridtype: %s", gridNamePtr(gridType), ysize, gridSize);

  if ( gridptr->y.size != ysize )
    {
      gridMark4Update(gridID);
      gridptr->y.size = ysize;
    }

  if ( gridType != GRID_UNSTRUCTURED && gridType != GRID_GAUSSIAN_REDUCED && gridType != GRID_PROJECTION )
    {
      const size_t axisproduct = gridptr->x.size*gridptr->y.size;
      if ( axisproduct > 0 && axisproduct != gridSize )
        Error("Inconsistent grid declaration! (xsize=%zu ysize=%zu gridsize=%zu)",
              gridptr->x.size, gridptr->y.size, gridSize);
    }
}

/*
@Function  gridInqYsize
@Title     Get the number of values of a Y-axis

@Prototype size_t 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
*/
size_t gridInqYsize(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return 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 size_t
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 size;
}

static size_t
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)
{
  const 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 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)
{
  const 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
size_t gridInqXValsSerial(grid_t *gridptr, double *xvals)
{
  size_t size;
  if ( gridptr->type == GRID_CURVILINEAR || gridptr->type == GRID_UNSTRUCTURED )
    size = gridptr->size;
  else
    size = gridptr->x.size;

  if ( CDI_Debug && size == 0 )
    Warning("size undefined for gridID = %d", gridptr->self);

  if ( gridptr->x.vals )
    {
      if ( size && xvals )
        {
          const double *gridptr_xvals = gridptr->vtable->inqXValsPtr(gridptr);
          memcpy(xvals, gridptr_xvals, size * sizeof (double));
        }
    }
  else
    size = 0;

  return size;
}

static
size_t gridInqXValsPartSerial(grid_t *gridptr, int start, size_t length, double *xvals)
{
  size_t size;
  if ( gridptr->type == GRID_CURVILINEAR || gridptr->type == GRID_UNSTRUCTURED )
    size = gridptr->size;
  else
    size = gridptr->x.size;

  if ( CDI_Debug && size == 0 )
    Warning("size undefined for gridID = %d", gridptr->self);

  if ( gridptr->x.vals )
    {
      if ( size && xvals && length <= size )
        {
          const double *gridptr_xvals = gridptr->vtable->inqXValsPtr(gridptr);
          memcpy(xvals, gridptr_xvals+start, length * sizeof (double));
        }
    }
  else
    size = 0;

  return size;
}

#ifndef USE_MPI
static
size_t gridInqXCvalsSerial(grid_t *gridptr, char **xcvals)
{
  if ( gridptr->type != GRID_CHARXY )
    Error("Function only valid for grid type 'GRID_CHARXY'.");

  const size_t size = gridptr->x.size;
  size_t maxclength = 0;

  const char **gridptr_xcvals = gridptr->vtable->inqXCvalsPtr(gridptr);
  if ( gridptr_xcvals && size && xcvals )
    {
      maxclength = gridptr->x.clength;
      for ( size_t i = 0; i < size; i++ )
        memcpy(xcvals[i], gridptr_xcvals[i], maxclength*sizeof(char));
    }

  return 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 size_t 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
*/
size_t gridInqXvals(int gridID, double *xvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXVals(gridptr, xvals);
}


size_t gridInqXvalsPart(int gridID, int start, size_t length, double *xvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXValsPart(gridptr, start, length, xvals);
}


size_t gridInqXCvals(int gridID, char **xcvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
#ifndef USE_MPI
  return gridptr->vtable->inqXCvals(gridptr, xcvals);
#else
  return 0;
#endif
}


int gridInqXIsc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
#ifndef USE_MPI
  return gridptr->vtable->inqXIsc(gridptr);
#else
  return 0;
#endif
}

static
void gridDefXValsSerial(grid_t *gridptr, const double *xvals)
{
  const int gridtype = gridptr->type;

  size_t size;
  if ( gridtype == GRID_UNSTRUCTURED || gridtype == GRID_CURVILINEAR )
    size = gridptr->size;
  else
    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));
  memcpy(gridptr->x.vals, xvals, size * sizeof (double));
}

#ifndef USE_MPI
static
size_t gridInqYCvalsSerial(grid_t *gridptr, char **ycvals)
{
  if ( gridptr->type != GRID_CHARXY )
    Error("Function only valid for grid type 'GRID_CHARXY'.");

  const size_t size = gridptr->y.size;
  size_t maxclength = 0;

  const char **gridptr_ycvals = gridptr->vtable->inqYCvalsPtr(gridptr);
  if ( gridptr_ycvals && size && ycvals )
    {
      maxclength = gridptr->y.clength;
      for ( size_t i = 0; i < size; i++ )
        memcpy(ycvals[i], gridptr_ycvals[i], maxclength*sizeof(char));
    }

  return 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
size_t gridInqYValsSerial(grid_t *gridptr, double *yvals)
{
  const int gridtype = gridptr->type;
  size_t size = (gridtype == GRID_CURVILINEAR || gridtype == GRID_UNSTRUCTURED)
            ? 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 )
        {
          const double *gridptr_yvals = gridptr->vtable->inqYValsPtr(gridptr);
          memcpy(yvals, gridptr_yvals, size * sizeof (double));
        }
    }
  else
    size = 0;

  return size;
}

static
size_t gridInqYValsPartSerial(grid_t *gridptr, int start, size_t length, double *yvals)
{
  const int gridtype = gridptr->type;
  size_t size = (gridtype == GRID_CURVILINEAR || gridtype == GRID_UNSTRUCTURED)
            ? 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 && length <= size )
        {
          const double *gridptr_yvals = gridptr->vtable->inqYValsPtr(gridptr);
          memcpy(yvals, gridptr_yvals+start, length * sizeof (double));
        }
    }
  else
    size = 0;

  return size;
}

/*
@Function  gridInqYvals
@Title     Get all values of a Y-axis

@Prototype size_t 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
*/
size_t gridInqYvals(int gridID, double *yvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYVals(gridptr, yvals);
}


size_t gridInqYvalsPart(int gridID, int start, size_t size, double *yvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqYValsPart(gridptr, start, size, yvals);
}


size_t gridInqYCvals(int gridID, char **ycvals)
{
  grid_t *gridptr = grid_to_pointer(gridID);
#ifndef USE_MPI
  return gridptr->vtable->inqYCvals(gridptr, ycvals);
#else
  return 0;
#endif
}


int gridInqYIsc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
#ifndef USE_MPI
  return gridptr->vtable->inqYIsc(gridptr);
#else
  return 0;
#endif
}

static
void gridDefYValsSerial(grid_t *gridptr, const double *yvals)
{
  const int gridtype = gridptr->type;
  const size_t size = (gridtype == GRID_CURVILINEAR || gridtype == GRID_UNSTRUCTURED)
                    ? 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));
  memcpy(gridptr->y.vals, yvals, size * sizeof (double));
}


/*
@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, size_t index)
{
  const double xval = gridptr->x.vals ? gridptr->x.vals[index] : 0;
  return xval;
}


double gridInqXval(int gridID, size_t index)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->inqXVal(gridptr, index);
}

static double
gridInqYValSerial(grid_t *gridptr, size_t index)
{
  const double yval = gridptr->y.vals ? gridptr->y.vals[index] : 0;
  return yval;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
double gridInqYval(int gridID, size_t 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])/(size-1);
      const 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;
            break;
          }

      return inc;
    }

  return 0;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
double gridInqXinc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *restrict xvals = gridptr->vtable->inqXValsPtr(gridptr);

  if (fabs(gridptr->x.inc) <= 0 && xvals)
    {
      const size_t xsize = gridptr->x.size;
      if ( xsize > 1 ) gridptr->x.inc = grid_calc_increment(xsize, xvals);
    }

  return gridptr->x.inc;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
double gridInqYinc(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *yvals = gridptr->vtable->inqYValsPtr(gridptr);

  if (fabs(gridptr->y.inc) <= 0 && yvals)
    {
      const size_t ysize = gridptr->y.size;
      if ( ysize > 1 ) gridptr->y.inc = grid_calc_increment(ysize, yvals);
    }

  return gridptr->y.inc;
}

/*
@Function
@Title

@Prototype
@Parameter
    @Item  Grid identifier

@EndFunction
*/
void gridInqParamRLL(int gridID, double *xpole, double *ypole, double *angle)
{
  *xpole = 0; *ypole = 0; *angle = 0;

  const char *projection = "rotated_latitude_longitude";
  char gmapname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
  if ( gmapname[0] && strIsEqual(gmapname, projection) )
    {
      int atttype, attlen;
      char attname[CDI_MAX_NAME+1];

      int natts;
      cdiInqNatts(gridID, CDI_GLOBAL, &natts);

      for ( int iatt = 0; iatt < natts; ++iatt )
        {
          cdiInqAtt(gridID, CDI_GLOBAL, iatt, attname, &atttype, &attlen);
          if ( attlen != 1 ) continue;

          double attflt;
          if ( cdiInqAttConvertedToFloat(gridID, atttype, attname, attlen, &attflt) )
            {
              if      ( strIsEqual(attname, "grid_north_pole_longitude") ) *xpole = attflt;
              else if ( strIsEqual(attname, "grid_north_pole_latitude")  ) *ypole = attflt;
              else if ( strIsEqual(attname, "north_pole_grid_longitude") ) *angle = attflt;
            }
        }
    }
  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_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)/(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 gridIsCircular(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;

  size_t xsizeTest = gridTest->x.size, ysizeTest = gridTest->y.size;
  if ( !differ && xsizeTest > 0 && xsizeTest == gridRef->vtable->inqXVals(gridRef, NULL) )
    {
      const double *restrict xvalsRef = gridRef->vtable->inqXValsPtr(gridRef),
        *restrict xvalsTest = gridTest->vtable->inqXValsPtr(gridTest);

      for ( size_t i = 0; i < xsizeTest; ++i )
	if ( fabs(xvalsTest[i] - xvalsRef[i]) > 1.e-10 )
	  {
	    differ = true;
	    break;
	  }
    }

  if ( !differ && ysizeTest > 0 && ysizeTest == gridRef->vtable->inqYVals(gridRef, NULL) )
    {
      const double *restrict yvalsRef = gridRef->vtable->inqYValsPtr(gridRef),
        *restrict yvalsTest = gridTest->vtable->inqYValsPtr(gridTest);
      for ( size_t i = 0; i < ysizeTest; ++i )
	if ( fabs(yvalsTest[i] - yvalsRef[i]) > 1.e-10 )
	  {
	    differ = true;
	    break;
	  }
    }

  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, size_t 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, gridsize-1) - inqXValRef(gridRef, gridsize-1)) > 1.e-9;

  if ( !differ && gridTest->y.vals )
    differ = fabs(inqYValTest(gridTest, 0) - inqYValRef(gridRef, 0)) > 1.e-9
          || fabs(inqYValTest(gridTest, gridsize-1) - inqYValRef(gridRef, gridsize-1)) > 1.e-9;

  return differ;
}

static
bool gridCompare(int gridID, const grid_t *grid, bool coord_compare)
{
  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 || grid->type == GRID_PROJECTION )
	    {
	      /*
	      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);
		}
	      else
		differ = true;
	    }
	  else if ( grid->type == GRID_GENERIC )
	    {
	      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;
	    }
	  else if ( grid->type == GRID_GAUSSIAN )
	    {
	      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)) > 0.0015 ||
			     fabs(grid->y.first - gridInqYval(gridID, 0)) > 0.0015 ||
			     (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);
		}
	      else
		differ = true;
	    }
	  else if ( grid->type == GRID_CURVILINEAR )
	    {
	      /*
	      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);
	    }
	  else if ( grid->type == GRID_UNSTRUCTURED )
	    {
              unsigned char uuid1[CDI_UUID_SIZE]; memset(uuid1, 0, CDI_UUID_SIZE);
              unsigned char uuid2[CDI_UUID_SIZE]; memset(uuid2, 0, CDI_UUID_SIZE);
              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 = ((uuid1[0] || uuid2[0]) && 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 ( coord_compare )
                    {
                      differ = grid->nvertex != gridRef->nvertex
                        || (numberA > 0 && positionA != positionB)
                        || gridRef->vtable->compareXYAO((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);
                        }
                    }
                }
            }
	}
    }

  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 = 1;
#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);
  if (v1 != v2) return 1;
  return 0;
}


int gridCompareP(void *gridptr1, void *gridptr2)
{
  grid_t *g1 = ( grid_t * ) gridptr1;
  grid_t *g2 = ( grid_t * ) gridptr2;
  enum { equal = 0,
         differ = -1 };
  size_t i, size;

  xassert ( g1 );
  xassert ( g2 );

  if ( g1->type          != g2->type         ) return differ;
  if ( g1->datatype      != g2->datatype     ) 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;

  const double *restrict g1_xvals = g1->vtable->inqXValsPtr(g1),
               *restrict g2_xvals = g2->vtable->inqXValsPtr(g2);
  if ( g1_xvals )
    {
      if ( g1->type == GRID_UNSTRUCTURED || g1->type == GRID_CURVILINEAR )
        size = g1->size;
      else
        size = g1->x.size;
      xassert ( size );

      if ( !g2_xvals ) return differ;

      for ( i = 0; i < size; i++ )
        if ( IS_NOT_EQUAL(g1_xvals[i], g2_xvals[i]) ) return differ;
    }
  else if ( g2_xvals )
    return differ;

  const double *restrict g1_yvals = g1->vtable->inqYValsPtr(g1),
               *restrict g2_yvals = g2->vtable->inqYValsPtr(g2);
  if ( g1_yvals )
    {
      if ( g1->type == GRID_UNSTRUCTURED || g1->type == GRID_CURVILINEAR )
	size = g1->size;
      else
	size = g1->y.size;
      xassert ( size );

      if ( !g2_yvals ) return differ;

      for ( i = 0; i < size; i++ )
        if ( IS_NOT_EQUAL(g1_yvals[i], g2_yvals[i]) ) return differ;
    }
  else if ( g2_yvals )
    return differ;

  const double *restrict g1_area = g1->vtable->inqAreaPtr(g1),
               *restrict g2_area = g2->vtable->inqAreaPtr(g2);
  if ( g1_area )
    {
      xassert ( g1->size );

      if ( !g2_area ) return differ;

      for ( i = 0; i < g1->size; i++ )
	if ( IS_NOT_EQUAL(g1_area[i], g2_area[i]) ) return differ;
    }
  else if ( g2_area )
    return differ;

  {
    const double *restrict g1_xbounds, *restrict g2_xbounds;
    if ( (g1_xbounds = g1->vtable->inqXBoundsPtr(g1)) )
      {
        xassert ( g1->nvertex );
        if ( g1->type == GRID_CURVILINEAR || g1->type == GRID_UNSTRUCTURED )
          size = g1->nvertex * g1->size;
        else
          size = g1->nvertex * g1->x.size;
        xassert ( size );

        if ( !(g2_xbounds = g2->vtable->inqXBoundsPtr(g2)) ) return differ;

        for ( i = 0; i < size; i++ )
          if ( IS_NOT_EQUAL(g1_xbounds[i], g2_xbounds[i]) ) return differ;
      }
    else if ( g2->vtable->inqXBoundsPtr(g2) )
      return differ;
  }

  {
    const double *restrict g1_ybounds, *restrict g2_ybounds;
    if ( (g1_ybounds = g1->vtable->inqYBoundsPtr(g1)) )
      {
        xassert ( g1->nvertex );
        if ( g1->type == GRID_CURVILINEAR || g1->type == GRID_UNSTRUCTURED )
          size = g1->nvertex * g1->size;
        else
          size = g1->nvertex * g1->y.size;
        xassert ( size );

        if ( ! (g2_ybounds = g2->vtable->inqYBoundsPtr(g2)) ) return differ;

        for ( i = 0; i < size; i++ )
          if ( IS_NOT_EQUAL(g1->y.bounds[i], g2->y.bounds[i]) ) return differ;
      }
    else if ( g2->vtable->inqYBoundsPtr(g2) )
      return differ;
  }

  if (strcmp(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_NAME), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_NAME))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_NAME), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_NAME))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_LONGNAME), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_LONGNAME))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_LONGNAME), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_LONGNAME))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_UNITS), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_UNITS))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_UNITS), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_UNITS))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->x.keys, CDI_KEY_STDNAME), cdiInqVarKeyString(&g2->x.keys, CDI_KEY_STDNAME))) return differ;
  if (strcmp(cdiInqVarKeyString(&g1->y.keys, CDI_KEY_STDNAME), cdiInqVarKeyString(&g2->y.keys, CDI_KEY_STDNAME))) return differ;

  if (strcmp(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]; memset(uuid1, 0, CDI_UUID_SIZE);
  unsigned char uuid2[CDI_UUID_SIZE]; memset(uuid2, 0, CDI_UUID_SIZE);
  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 gridComplete(grid_t *grid)
{
  const int gridID = grid->self;
  gridDefDatatype(gridID, grid->datatype);

  const 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_CHARXY:
      {
	if ( grid->x.size > 0 ) gridDefXsize(gridID, grid->x.size);
	if ( grid->y.size > 0 ) gridDefYsize(gridID, 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, 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, grid->size);
  grid_t *restrict gridptr = grid_to_pointer(gridID);
  gridptr->datatype = grid->datatype;
  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
  };
  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, 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;

  gridComplete(gridptr);

  return gridID;
}

static void
grid_copy_base_array_fields(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 )
    {
      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 )
    {
      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 )
    {
      size_t size  = (irregular ? gridsize : gridptrOrig->x.size)
        * 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 = (irregular ? gridsize : gridptrOrig->y.size)
        * 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 = 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  = (int)gridsize;
	  gridptr->x.size = (int)gridsize;
	  gridptr->y.size = (int)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
gridHasAreaBase(grid_t *gridptr)
{
  return gridptr->area != NULL;
}

int gridHasArea(int gridID)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  return gridptr->vtable->hasArea(gridptr);
}


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)
{
  const int irregular = gridptr->type == GRID_CURVILINEAR || gridptr->type == GRID_UNSTRUCTURED;
  const size_t nvertex = (size_t)gridptr->nvertex;
  if ( nvertex == 0 )
    {
      Warning("nvertex undefined for gridID = %d. Cannot define bounds!", gridptr->self);
      return;
    }

  const size_t size = nvertex * (irregular ? 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!");

  memcpy(*field, bounds, size * sizeof (double));
}


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 size_t
gridInqXBoundsSerial(grid_t *gridptr, double *xbounds)
{
  size_t nvertex = (size_t)gridptr->nvertex;

  int irregular = gridptr->type == GRID_CURVILINEAR
    || gridptr->type == GRID_UNSTRUCTURED;
  size_t size = nvertex * (irregular ? gridptr->size : gridptr->x.size);

  const double *gridptr_xbounds = gridptr->vtable->inqXBoundsPtr(gridptr);
  if ( gridptr_xbounds )
    {
      if ( size && xbounds )
        memcpy(xbounds, gridptr_xbounds, size * sizeof (double));
    }
  else
    size = 0;

  return size;
}

/*
@Function  gridInqXbounds
@Title     Get the bounds of a X-axis

@Prototype size_t 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
*/
size_t 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
//----------------------------------------------------------------------------

size_t gridInqXboundsPart(int gridID, int start, size_t size, double *xbounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *gridptr_xbounds = gridptr->vtable->inqXBoundsPtr(gridptr);
  if ( gridptr_xbounds )
    if ( size && xbounds )
      memcpy(xbounds, gridptr_xbounds+start, size * sizeof (double));

  return size;
}

size_t gridInqYboundsPart(int gridID, int start, size_t size, double *ybounds)
{
  grid_t *gridptr = grid_to_pointer(gridID);
  const double *gridptr_ybounds = gridptr->vtable->inqYBoundsPtr(gridptr);
  if ( gridptr_ybounds )
    if ( size && ybounds )
      memcpy(ybounds, gridptr_ybounds+start, 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 size_t
gridInqYBoundsSerial(grid_t *gridptr, double *ybounds)
{
  size_t nvertex = (size_t)gridptr->nvertex;

  int irregular = gridptr->type == GRID_CURVILINEAR
    || gridptr->type == GRID_UNSTRUCTURED;
  size_t size = nvertex * (irregular ? gridptr->size : gridptr->y.size);

  const double *gridptr_ybounds = gridptr->vtable->inqYBoundsPtr(gridptr);
  if ( gridptr_ybounds )
    {
      if ( size && ybounds )
        memcpy(ybounds, gridptr_ybounds, size * sizeof (double));
    }
  else
    size = 0;

  return size;
}


/*
@Function  gridInqYbounds
@Title     Get the bounds of a Y-axis

@Prototype size_t 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
*/
size_t 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 = gridInqXvals(gridID, NULL);
  size_t nyvals = gridInqYvals(gridID, NULL);

  int type     = gridInqType(gridID);
  size_t gridsize = gridInqSize(gridID);
  size_t xsize    = gridInqXsize(gridID);
  size_t ysize    = gridInqYsize(gridID);
  int nvertex  = gridInqNvertex(gridID);
  int datatype = gridInqDatatype(gridID);

  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] && strcmp(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] && strcmp(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 || type == GRID_GENERIC )
	      {
		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);

  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",
          gridptr->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

/*
@Function  gridDefParamLCC
@Title     Define the parameter of a Lambert Conformal Conic grid

@Prototype void gridDefParamLCC(int gridID, double missval, double lon_0, double lat_0, double lat_1, double lat_2, double a, double rf, double xval_0, double yval_0, double x_0, double y_0)
@Parameter
    @Item  gridID    Grid ID, from a previous call to @fref{gridCreate}.
    @Item  missval   Missing value.
    @Item  lon_0     The East longitude of the meridian which is parallel to the Y-axis.
    @Item  lat_0     Latitude of the projection origin.
    @Item  lat_1     First latitude from the pole at which the secant cone cuts the sphere.
    @Item  lat_2     Second latitude at which the secant cone cuts the sphere.
    @Item  a         Earth radius in metres (optional).
    @Item  rf        Inverse flattening (1/f) (optional).
    @Item  xval_0    Longitude of the first grid point in degree (optional).
    @Item  yval_0    Latitude of the first grid point in degree (optional).
    @Item  x_0       False easting (optional).
    @Item  y_0       False northing (optional).

@Description
The function @func{gridDefParamLCC} defines the parameter of a Lambert Conformal Conic grid.

@EndFunction
*/
void gridDefParamLCC(int gridID, double missval, double lon_0, double lat_0, double lat_1, double lat_2,
                     double a, double rf, double xval_0, double yval_0, double x_0, double y_0)
{
  (void)lat_0;
  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++] = lat_1;
  if ( IS_NOT_EQUAL(lat_1, lat_2) ) lats[nlats++] = 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, &lon_0);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "latitude_of_projection_origin", CDI_DATATYPE_FLT64, 1, &lat_2);
  if ( a > 0 ) cdiDefAttFlt(gridID, CDI_GLOBAL, "earth_radius", CDI_DATATYPE_FLT64, 1, &a);
  if ( rf > 0 ) cdiDefAttFlt(gridID, CDI_GLOBAL, "inverse_flattening", CDI_DATATYPE_FLT64, 1, &rf);
  if ( IS_NOT_EQUAL(x_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "false_easting", CDI_DATATYPE_FLT64, 1, &x_0);
  if ( IS_NOT_EQUAL(y_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "false_northing", CDI_DATATYPE_FLT64, 1, &y_0);
  if ( IS_NOT_EQUAL(xval_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "longitudeOfFirstGridPointInDegrees", CDI_DATATYPE_FLT64, 1, &xval_0);
  if ( IS_NOT_EQUAL(yval_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "latitudeOfFirstGridPointInDegrees", CDI_DATATYPE_FLT64, 1, &yval_0);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->projtype = CDI_PROJ_LCC;

  if (gridptr->type != GRID_PROJECTION) gridptr->type = GRID_PROJECTION;

  gridVerifyProj(gridID);
}

/*
@Function  gridInqParamLCC
@Title     Get the parameter of a Lambert Conformal Conic grid

@Prototype void gridInqParamLCC(int gridID, double missval, double *lon_0, double *lat_0, double *lat_1, double *lat_2, double *a, double *rf, double *xval_0, double *yval_0, double *x_0, double *y_0)
@Parameter
    @Item  gridID    Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  missval   Missing value
    @Item  lon_0     The East longitude of the meridian which is parallel to the Y-axis.
    @Item  lat_0     Latitude of the projection origin.
    @Item  lat_1     First latitude from the pole at which the secant cone cuts the sphere.
    @Item  lat_2     Second latitude at which the secant cone cuts the sphere.
    @Item  a         Earth radius in metres (optional).
    @Item  rf        Inverse flattening (1/f) (optional).
    @Item  xval_0    Longitude of the first grid point in degree (optional).
    @Item  yval_0    Latitude of the first grid point in degree (optional).
    @Item  x_0       False easting (optional).
    @Item  y_0       False northing (optional).

@Description
The function @func{gridInqParamLCC} returns the parameter of a Lambert Conformal Conic grid.

@EndFunction
*/
int gridInqParamLCC(int gridID, double missval, double *lon_0, double *lat_0, double *lat_1, double *lat_2,
                    double *a, double *rf, double *xval_0, double *yval_0, double *x_0, double *y_0)
{
  *a = 0; *rf = 0;
  *lon_0 = missval; *lat_0 = missval, *lat_1 = missval, *lat_2 = missval;
  *xval_0 = missval; *yval_0 = missval; *x_0 = missval, *y_0 = missval;

  int status = -1;
  if ( gridInqType(gridID) != GRID_PROJECTION ) return status;

  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] && strIsEqual(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) )
            {
              if      ( strIsEqual(attname, "earth_radius") )                       *a      = attflt[0];
              else if ( strIsEqual(attname, "semi_major_axis") )                    *a      = attflt[0];
              else if ( strIsEqual(attname, "inverse_flattening") )                 *rf     = attflt[0];
              else if ( strIsEqual(attname, "longitude_of_central_meridian") )      *lon_0  = attflt[0];
              else if ( strIsEqual(attname, "latitude_of_projection_origin") )      *lat_0  = attflt[0];
              else if ( strIsEqual(attname, "false_easting")  )                     *x_0    = attflt[0];
              else if ( strIsEqual(attname, "false_northing") )                     *y_0    = attflt[0];
              else if ( strIsEqual(attname, "longitudeOfFirstGridPointInDegrees") ) *xval_0 = attflt[0];
              else if ( strIsEqual(attname, "latitudeOfFirstGridPointInDegrees")  ) *yval_0 = attflt[0];
              else if ( strIsEqual(attname, "standard_parallel") )
                {
                  *lat_1 = attflt[0];
                  *lat_2 = (attlen == 2) ? attflt[1] : attflt[0];
                }
            }
        }
    }

  return status;
}


int gridVerifyGribParamLCC(double missval, double *lon_0, double *lat_0, double *lat_1, double *lat_2,
                           double *a, double *rf, double *xval_0, double *yval_0, double *x_0, double *y_0)
{
  static bool lwarn = true;

  if ( lwarn )
    {
      // lwarn = false;
      const char *projection = "lambert_conformal_conic";
      if ( IS_EQUAL(*lon_0, missval) ) { Warning("%s mapping parameter %s missing!", projection, "longitude_of_central_meridian"); }
      if ( IS_EQUAL(*lat_0, missval) ) { Warning("%s mapping parameter %s missing!", projection, "latitude_of_central_meridian"); }
      if ( IS_EQUAL(*lat_1, missval) ) { Warning("%s mapping parameter %s missing!", projection, "standard_parallel"); }
      if ( IS_NOT_EQUAL(*x_0, missval) && IS_NOT_EQUAL(*y_0, missval) && (IS_EQUAL(*xval_0, missval) || IS_EQUAL(*yval_0, missval)) )
        {
          if ( proj_lcc_to_lonlat_func )
            {
              *xval_0 = -(*x_0); *yval_0 = -(*y_0);
              proj_lcc_to_lonlat_func(missval, *lon_0, *lat_0, *lat_1, *lat_2, *a, *rf, 0.0, 0.0, (size_t)1, xval_0, yval_0);
            }
          if ( IS_EQUAL(*xval_0, missval) || IS_EQUAL(*yval_0, missval) )
            Warning("%s mapping parameter %s missing!", projection, "longitudeOfFirstGridPointInDegrees and latitudeOfFirstGridPointInDegrees");
        }
    }

  return 0;
}


int gridVerifyGribParamSTERE(double missval, double *lon_0, double *lat_ts, double *lat_0,
                             double *a, double *xval_0, double *yval_0, double *x_0, double *y_0)
{
  static bool lwarn = true;

  if ( lwarn )
    {
      // lwarn = false;
      const char *projection = "lambert_conformal_conic";
      if ( IS_EQUAL(*lon_0, missval) ) { Warning("%s mapping parameter %s missing!", projection, "straight_vertical_longitude_from_pole"); }
      if ( IS_EQUAL(*lat_0, missval) ) { Warning("%s mapping parameter %s missing!", projection, "latitude_of_projection_origin"); }
      if ( IS_EQUAL(*lat_ts, missval) ) { Warning("%s mapping parameter %s missing!", projection, "standard_parallel"); }
      if ( IS_NOT_EQUAL(*x_0, missval) && IS_NOT_EQUAL(*y_0, missval) && (IS_EQUAL(*xval_0, missval) || IS_EQUAL(*yval_0, missval)) )
        {
          if ( proj_stere_to_lonlat_func )
            {
              *xval_0 = -(*x_0); *yval_0 = -(*y_0);
              proj_stere_to_lonlat_func(missval, *lon_0, *lat_ts, *lat_0, *a, 0.0, 0.0, (size_t)1, xval_0, yval_0);
            }
          if ( IS_EQUAL(*xval_0, missval) || IS_EQUAL(*yval_0, missval) )
            Warning("%s mapping parameter %s missing!", projection, "longitudeOfFirstGridPointInDegrees and latitudeOfFirstGridPointInDegrees");
        }
    }

  return 0;
}

/*
@Function  gridDefParamSTERE
@Title     Define the parameter of a Polar stereographic grid

@Prototype void gridDefParamSTERE(int gridID, double missval, double lon_0, double lat_ts, double lat_0, double a, double xval_0, double yval_0, double x_0, double y_0)
@Parameter
    @Item  gridID    Grid ID, from a previous call to @fref{gridCreate}.
    @Item  missval   Missing value.
    @Item  lon_0     Longitude at natural origin.
    @Item  lat_ts    Latitude of the projection origin.
    @Item  lat_0     First latitude from the pole at which the secant cone cuts the sphere.
    @Item  a         Earth radius in metres (optional).
    @Item  xval_0    Longitude of the first grid point in degree (optional).
    @Item  yval_0    Latitude of the first grid point in degree (optional).
    @Item  x_0       False easting (optional).
    @Item  y_0       False northing (optional).

@Description
The function @func{gridDefParamSTERE} defines the parameter of a Polar stereographic grid.

@EndFunction
*/
void gridDefParamSTERE(int gridID, double missval, double lon_0, double lat_ts, double lat_0,
                       double a, double xval_0, double yval_0, double x_0, double y_0)
{
  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, &lat_ts);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "straight_vertical_longitude_from_pole", CDI_DATATYPE_FLT64, 1, &lon_0);
  cdiDefAttFlt(gridID, CDI_GLOBAL, "latitude_of_projection_origin", CDI_DATATYPE_FLT64, 1, &lat_0);
  if ( a > 0 ) cdiDefAttFlt(gridID, CDI_GLOBAL, "earth_radius", CDI_DATATYPE_FLT64, 1, &a);
  if ( IS_NOT_EQUAL(x_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "false_easting", CDI_DATATYPE_FLT64, 1, &x_0);
  if ( IS_NOT_EQUAL(y_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "false_northing", CDI_DATATYPE_FLT64, 1, &y_0);
  if ( IS_NOT_EQUAL(xval_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "longitudeOfFirstGridPointInDegrees", CDI_DATATYPE_FLT64, 1, &xval_0);
  if ( IS_NOT_EQUAL(yval_0, missval) ) cdiDefAttFlt(gridID, CDI_GLOBAL, "latitudeOfFirstGridPointInDegrees", CDI_DATATYPE_FLT64, 1, &yval_0);

  grid_t *gridptr = grid_to_pointer(gridID);
  gridptr->projtype = CDI_PROJ_STERE;

  gridVerifyProj(gridID);
}

/*
@Function  gridInqParamSTERE
@Title     Get the parameter of a Polar stereographic grid

@Prototype void gridInqParamSTERE(int gridID, double missval, double *lon_0, double *lat_ts, double *lat_0, double *a, double *xval_0, double *yval_0, double *x_0, double *y_0)
@Parameter
    @Item  gridID    Grid ID, from a previous call to @fref{gridCreate} or @fref{vlistInqVarGrid}.
    @Item  missval   Missing value
    @Item  lon_0     Longitude at natural origin.
    @Item  lat_ts    Latitude of the projection origin.
    @Item  lat_0     First latitude from the pole at which the secant cone cuts the sphere.
    @Item  a         Earth radius in metres (optional).
    @Item  xval_0    Longitude of the first grid point in degree (optional).
    @Item  yval_0    Latitude of the first grid point in degree (optional).
    @Item  x_0       False easting (optional).
    @Item  y_0       False northing (optional).

@Description
The function @func{gridInqParamSTERE} returns the parameter of a Polar stereographic grid.

@EndFunction
*/
int gridInqParamSTERE(int gridID, double missval, double *lon_0, double *lat_ts, double *lat_0,
                      double *a, double *xval_0, double *yval_0, double *x_0, double *y_0)
{
  *a = 0;
  *lon_0 = missval; *lat_ts = missval, *lat_0 = missval;
  *xval_0 = missval; *yval_0 = missval; *x_0 = missval, *y_0 = missval;

  int status = -1;
  if ( gridInqType(gridID) != GRID_PROJECTION ) return status;

  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] && strIsEqual(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) )
            {
              if      ( strIsEqual(attname, "earth_radius") )                          *a      = attflt[0];
              else if ( strIsEqual(attname, "standard_parallel") )                     *lat_ts = attflt[0];
              else if ( strIsEqual(attname, "straight_vertical_longitude_from_pole") ) *lon_0  = attflt[0];
              else if ( strIsEqual(attname, "latitude_of_projection_origin") )         *lat_0  = attflt[0];
              else if ( strIsEqual(attname, "false_easting")  )                        *x_0    = attflt[0];
              else if ( strIsEqual(attname, "false_northing") )                        *y_0    = attflt[0];
              else if ( strIsEqual(attname, "longitudeOfFirstGridPointInDegrees") )    *xval_0 = attflt[0];
              else if ( strIsEqual(attname, "latitudeOfFirstGridPointInDegrees")  )    *yval_0 = attflt[0];
            }
        }
    }

  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, const 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, const 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 ()
{
  return GRID;
}

enum {
  GRID_PACK_INT_IDX_SELF,
  GRID_PACK_INT_IDX_TYPE,
  GRID_PACK_INT_IDX_DATATYPE,
  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,
  REDUCEDPOINTSSIZE,
  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 = (gridHasMaskFlag & (int)((unsigned)(gridP->mask == NULL) - 1U))
    | (gridHasGMEMaskFlag & (int)((unsigned)(gridP->mask_gme == NULL) - 1U))
    | (gridHasXValsFlag & (int)((unsigned)(gridP->vtable->inqXValsPtr((grid_t *)gridP) == NULL) - 1U))
    | (gridHasYValsFlag & (int)((unsigned)(gridP->vtable->inqYValsPtr((grid_t *)gridP) == NULL) - 1U))
    | (gridHasAreaFlag & (int)((unsigned)(gridP->vtable->inqAreaPtr((grid_t *)gridP) == NULL) - 1U))
    | (gridHasXBoundsFlag & (int)((unsigned)(gridP->x.bounds == NULL) - 1U))
    | (gridHasYBoundsFlag & (int)((unsigned)(gridP->y.bounds == NULL) - 1U))
    | (gridHasReducedPointsFlag & (int)((unsigned)(gridP->reducedPoints == NULL) - 1U));
  return flags;
}

static int
gridGetPackSize(void * voidP, void *context)
{
  grid_t * gridP = ( grid_t * ) voidP;
  int packBuffSize = 0, count;

  packBuffSize += serializeGetSize(gridNint, CDI_DATATYPE_INT, context)
    + serializeGetSize(1, CDI_DATATYPE_UINT32, context);

  if (gridP->reducedPoints)
    {
      xassert(gridP->reducedPointsSize);
      packBuffSize += serializeGetSize(gridP->reducedPointsSize, CDI_DATATYPE_INT, context)
        + serializeGetSize( 1, CDI_DATATYPE_UINT32, context);
    }

  packBuffSize += serializeGetSize(gridNdouble, CDI_DATATYPE_FLT64, context);

  if (gridP->vtable->inqXValsPtr(gridP))
    {
      if (gridP->type == GRID_UNSTRUCTURED || gridP->type == GRID_CURVILINEAR)
	count = gridP->size;
      else
	count = gridP->x.size;
      xassert(count);
      packBuffSize += serializeGetSize(count, CDI_DATATYPE_FLT64, context)
        + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
    }

  if (gridP->vtable->inqYValsPtr(gridP))
    {
      if (gridP->type == GRID_UNSTRUCTURED || gridP->type == GRID_CURVILINEAR)
	count = gridP->size;
      else
	count = gridP->y.size;
      xassert(count);
      packBuffSize += serializeGetSize(count, CDI_DATATYPE_FLT64, context)
        + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
    }

  if (gridP->vtable->inqAreaPtr(gridP))
    {
      xassert(gridP->size);
      packBuffSize +=
        serializeGetSize(gridP->size, CDI_DATATYPE_FLT64, context)
        + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
    }

  if (gridP->x.bounds)
    {
      xassert(gridP->nvertex);
      if (gridP->type == GRID_CURVILINEAR || gridP->type == GRID_UNSTRUCTURED)
	count = gridP->size;
      else
	count = gridP->x.size;
      xassert(count);
      packBuffSize
        += (serializeGetSize(gridP->nvertex * count, CDI_DATATYPE_FLT64, context)
            + serializeGetSize(1, CDI_DATATYPE_UINT32, context));
    }

  if (gridP->y.bounds)
    {
      xassert(gridP->nvertex);
      if (gridP->type == GRID_CURVILINEAR || gridP->type == GRID_UNSTRUCTURED)
	count = gridP->size;
      else
	count = gridP->y.size;
      xassert(count);
      packBuffSize
        += (serializeGetSize(gridP->nvertex * count, CDI_DATATYPE_FLT64, context)
            + serializeGetSize(1, CDI_DATATYPE_UINT32, context));
    }

  packBuffSize += serializeKeysGetPackSize(&gridP->keys, context);
  packBuffSize += serializeKeysGetPackSize(&gridP->x.keys, context);
  packBuffSize += serializeKeysGetPackSize(&gridP->y.keys, context);

  if (gridP->mask)
    {
      xassert(gridP->size);
      packBuffSize
        += serializeGetSize(gridP->size, CDI_DATATYPE_UCHAR, context)
        + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
    }

  if (gridP->mask_gme)
    {
      xassert(gridP->size);
      packBuffSize += serializeGetSize(gridP->size, CDI_DATATYPE_UCHAR, context)
        + serializeGetSize(1, CDI_DATATYPE_UINT32, context);
    }

  return packBuffSize;
}

void
gridUnpack(char * unpackBuffer, int unpackBufferSize,
           int * unpackBufferPos, int originNamespace, void *context,
           int force_id)
{
  grid_t * gridP;
  uint32_t d;
  int memberMask, size;

  gridInit();

  {
    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->datatype      =   intBuffer[GRID_PACK_INT_IDX_DATATYPE];
    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[REDUCEDPOINTSSIZE];
    gridP->size          =   intBuffer[GRID_PACK_INT_IDX_SIZE];
    gridP->x.size        =   intBuffer[GRID_PACK_INT_IDX_X_SIZE];
    gridP->y.size        =   intBuffer[GRID_PACK_INT_IDX_Y_SIZE];
    gridP->lcomplex      =   (bool)intBuffer[GRID_PACK_INT_IDX_LCOMPLEX];
    memberMask           =   intBuffer[GRID_PACK_INT_IDX_MEMBERMASK];
  }

  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);
    }

  {
    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];
  }

  bool irregular = gridP->type == GRID_UNSTRUCTURED
    || gridP->type == GRID_CURVILINEAR;
  if (memberMask & gridHasXValsFlag)
    {
      size = irregular ? gridP->size : gridP->x.size;

      gridP->x.vals = (double *) Malloc(size * sizeof (double));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      gridP->x.vals, size, CDI_DATATYPE_FLT64, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->x.vals) == d );
    }

  if (memberMask & gridHasYValsFlag)
    {
      size = irregular ? gridP->size : gridP->y.size;

      gridP->y.vals = (double *) Malloc(size * sizeof (double));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      gridP->y.vals, size, CDI_DATATYPE_FLT64, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->y.vals) == d);
    }

  if (memberMask & gridHasAreaFlag)
    {
      size = gridP->size;
      xassert(size);
      gridP->area = (double *) Malloc(size * sizeof (double));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      gridP->area, size, CDI_DATATYPE_FLT64, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->area) == d);
    }

  if (memberMask & gridHasXBoundsFlag)
    {
      size = gridP->nvertex * (irregular ? gridP->size : gridP->x.size);
      xassert(size);

      gridP->x.bounds = (double *) Malloc(size * sizeof (double));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      gridP->x.bounds, size, CDI_DATATYPE_FLT64, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->x.bounds) == d);
    }

  if (memberMask & gridHasYBoundsFlag)
    {
      size = gridP->nvertex * (irregular ? gridP->size : gridP->y.size);
      xassert(size);

      gridP->y.bounds = (double *) Malloc(size * sizeof (double));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
			  gridP->y.bounds, size, CDI_DATATYPE_FLT64, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->y.bounds) == d);
    }

  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &gridP->keys, context);
  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &gridP->x.keys, context);
  serializeKeysUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos, &gridP->y.keys, context);

  if (memberMask & gridHasMaskFlag)
    {
      xassert((size = gridP->size));
      gridP->mask = (mask_t *) Malloc(size * sizeof (mask_t));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      gridP->mask, gridP->size, CDI_DATATYPE_UCHAR, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_UCHAR, gridP->size, gridP->mask) == d);
    }

  if (memberMask & gridHasGMEMaskFlag)
    {
      xassert((size = gridP->size));
      gridP->mask_gme = (mask_t *) Malloc(size * sizeof (mask_t));
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      gridP->mask_gme, gridP->size, CDI_DATATYPE_UCHAR, context);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      &d, 1, CDI_DATATYPE_UINT32, context);
      xassert(cdiCheckSum(CDI_DATATYPE_UCHAR, gridP->size, gridP->mask_gme) == d);
    }

  reshSetStatus(gridP->self, &gridOps,
                reshGetStatus(gridP->self, &gridOps) & ~RESH_SYNC_BIT);
}


static void
gridPack(void * voidP, void * packBuffer, int packBufferSize,
         int * packBufferPos, void *context)
{
  grid_t   * gridP = ( grid_t * )   voidP;
  int size;
  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_DATATYPE]     = gridP->datatype;
    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[REDUCEDPOINTSSIZE]      = gridP->reducedPointsSize;
    intBuffer[GRID_PACK_INT_IDX_SIZE]         = gridP->size;
    intBuffer[GRID_PACK_INT_IDX_X_SIZE]       = gridP->x.size;
    intBuffer[GRID_PACK_INT_IDX_Y_SIZE]       = 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);
  }

  if (memberMask & gridHasReducedPointsFlag)
    {
      size = gridP->reducedPointsSize;
      xassert(size > 0);
      serializePack(gridP->reducedPoints, size, CDI_DATATYPE_INT,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_INT , size, gridP->reducedPoints);
      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);
  }

  if (memberMask & gridHasXValsFlag)
    {
      if (gridP->type == GRID_UNSTRUCTURED || gridP->type == GRID_CURVILINEAR)
	size = gridP->size;
      else
	size = gridP->x.size;
      xassert(size);

      const double *gridP_xvals = gridP->vtable->inqXValsPtr(gridP);
      serializePack(gridP_xvals, size, CDI_DATATYPE_FLT64,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_FLT, size, gridP_xvals);
      serializePack(&d, 1, CDI_DATATYPE_UINT32,
                    packBuffer, packBufferSize, packBufferPos, context);
    }

  if (memberMask & gridHasYValsFlag)
    {
      if (gridP->type == GRID_UNSTRUCTURED || gridP->type == GRID_CURVILINEAR )
	size = gridP->size;
      else
	size = gridP->y.size;
      xassert(size);
      const double *gridP_yvals = gridP->vtable->inqYValsPtr(gridP);
      serializePack(gridP_yvals, size, CDI_DATATYPE_FLT64,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_FLT, size, gridP_yvals);
      serializePack(&d, 1, CDI_DATATYPE_UINT32,
                    packBuffer, packBufferSize, packBufferPos, context);
    }

  if (memberMask & gridHasAreaFlag)
    {
      xassert(gridP->size);

      serializePack(gridP->area, gridP->size, CDI_DATATYPE_FLT64,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_FLT, gridP->size, gridP->area);
      serializePack(&d, 1, CDI_DATATYPE_UINT32,
                    packBuffer, packBufferSize, packBufferPos, context);
    }

  if (memberMask & gridHasXBoundsFlag)
    {
      xassert ( gridP->nvertex );
      if (gridP->type == GRID_CURVILINEAR || gridP->type == GRID_UNSTRUCTURED)
	size = gridP->nvertex * gridP->size;
      else
	size = gridP->nvertex * gridP->x.size;
      xassert ( size );

      serializePack(gridP->x.bounds, size, CDI_DATATYPE_FLT64,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->x.bounds);
      serializePack(&d, 1, CDI_DATATYPE_UINT32,
                    packBuffer, packBufferSize, packBufferPos, context);
    }

  if (memberMask & gridHasYBoundsFlag)
    {
      xassert(gridP->nvertex);
      if (gridP->type == GRID_CURVILINEAR || gridP->type == GRID_UNSTRUCTURED)
	size = gridP->nvertex * gridP->size;
      else
	size = gridP->nvertex * gridP->y.size;
      xassert ( size );

      serializePack(gridP->y.bounds, size, CDI_DATATYPE_FLT64,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_FLT, size, gridP->y.bounds);
      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);

  if (memberMask & gridHasMaskFlag)
    {
      xassert((size = gridP->size));
      serializePack(gridP->mask, size, CDI_DATATYPE_UCHAR,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_UCHAR, size, gridP->mask);
      serializePack(&d, 1, CDI_DATATYPE_UINT32,
                    packBuffer, packBufferSize, packBufferPos, context);
    }

  if (memberMask & gridHasGMEMaskFlag)
    {
      xassert((size = gridP->size));

      serializePack(gridP->mask_gme, size, CDI_DATATYPE_UCHAR,
                    packBuffer, packBufferSize, packBufferPos, context);
      d = cdiCheckSum(CDI_DATATYPE_UCHAR, 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 gridglobdefined = false;
  bool griddefined = false;
  int gridID = CDI_UNDEFID;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  unsigned ngrids = (unsigned)vlistptr->ngrids;

  if ( mode == 0 )
    for ( unsigned index = 0; index < ngrids; index++ )
      {
	if ( (gridID = vlistptr->gridIDs[index]) != CDI_UNDEFID )
          {
            if ( gridCompare(gridID, grid, false) == false )
              {
                griddefined = true;
                break;
              }
          }
        else
          Error("Internal problem: undefined gridID in vlist %d, position %u!", vlistID, index);
      }

  if ( ! griddefined )
    {
      struct gridCompareSearchState query;
      query.queryKey = grid;// = { .queryKey = grid };
      if ( (gridglobdefined = (cdiResHFilterApply(&gridOps, gridCompareSearch, &query) == CDI_APPLY_STOP)) )
        gridID = query.resIDValue;

      if ( mode == 1 && gridglobdefined )
	for ( unsigned index = 0; index < ngrids; index++ )
	  if ( vlistptr->gridIDs[index] == gridID )
	    {
	      gridglobdefined = false;
	      break;
	    }
    }

  if ( ! griddefined )
    {
      if ( ! gridglobdefined )
        {
          grid->self = gridID = reshPut(grid, &gridOps);
          gridComplete(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 = !griddefined && !gridglobdefined };
}


const struct gridVirtTable cdiGridVtable
  = {
  .destroy = gridDestroyKernel,
  .copy = grid_copy_base,
  .copyScalarFields = grid_copy_base_scalar_fields,
  .copyArrayFields = grid_copy_base_array_fields,
  .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
  .compareXYFull = compareXYvals,
  .compareXYAO = compareXYvals2,
  .inqArea = gridInqAreaSerial,
  .inqAreaPtr = gridInqAreaPtrBase,
  .hasArea = gridHasAreaBase,
  .inqMask = gridInqMaskSerial,
  .inqMaskGME = gridInqMaskGMESerial,
  .inqXBounds = gridInqXBoundsSerial,
  .inqYBounds = gridInqYBoundsSerial,
  .inqXBoundsPtr = gridInqXBoundsPtrSerial,
  .inqYBoundsPtr = gridInqYBoundsPtrSerial,
};

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>



static int initIegLib      = 0;
static int iegDefaultDprec = 0;


/*
 * A version string.
 */
#undef  LIBVERSION
#define LIBVERSION      1.4.1
#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_SINGLE_PRECISION; break;
		  case '8': iegDefaultDprec = EXSE_DOUBLE_PRECISION; break;
		  default:
		    Message("Invalid digit in %s: %s", envName, envString);
		  }
		break;
	      }
	    default:
              {
                Message("Invalid character in %s: %s", envName, envString);
                break;
              }
            }
	  pos += 2;
	}
    }

  initIegLib = 1;
}


void iegDebug(int debug)
{
  IEG_Debug = debug;
  if (IEG_Debug) Message("debug level %d", 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_SUINT32(buffer);

  if ( IEG_Debug ) Message("blocklen = %d sblocklen = %d", blocklen, sblocklen);

  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_SUINT32(pbuf);
      pbuf = buffer+(37+5)*4;     dimy = (size_t) get_SUINT32(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_SUINT32(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_SUINT32(pbuf);
      pbuf = buffer+(37+5)*4;     dimy = (size_t) get_SUINT32(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_SUINT32(pbuf);
    }

  fileRewind(fileID);

  if ( IEG_Debug )
    {
      Message("swap = %d fact = %d", *swap, fact);
      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_SINGLE_PRECISION:
      {
	if ( sizeof(FLT32) == 4 )
	  {
	    if ( byteswap ) swap4byte(buffer, datasize);

	    if ( dprec == prec )
	      memcpy(data, buffer, datasize*sizeof(FLT32));
	    else
              {
                const float *restrict p = (float *)buffer;
                double *restrict q = (double *)data;
                for (size_t i = 0; i < datasize; i++) q[i] = p[i];
              }
	  }
	else
	  {
	    Error("not implemented for %d byte float", sizeof(FLT32));
	  }
	break;
      }
    case EXSE_DOUBLE_PRECISION:
	if ( sizeof(FLT64) == 8 )
	  {
	    if ( byteswap ) swap8byte(buffer, datasize);

	    if ( dprec == prec )
	      memcpy(data, buffer, datasize*sizeof(FLT64));
	    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];
              }
	  }
	else
	  {
	    Error("not implemented for %d byte float", sizeof(FLT64));
	  }
	break;
    default:
      {
	Error("unexpected data precision %d", dprec);
        break;
      }
    }

  return ierr;
}


int iegInqDataSP(void *ieg, float *data)
{
  return iegInqData(ieg, EXSE_SINGLE_PRECISION, (void *) data);
}


int iegInqDataDP(void *ieg, double *data)
{
  return iegInqData(ieg, EXSE_DOUBLE_PRECISION, (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;

  void *buffer = iegp->buffer;
  size_t buffersize = iegp->buffersize;
  if ( buffersize != blocklen )
    {
      buffersize = blocklen;
      buffer = Realloc(buffer, buffersize);
      iegp->buffer = buffer;
      iegp->buffersize = buffersize;
    }

  switch ( dprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	if ( dprec == prec )
	  memcpy(buffer, data, datasize*sizeof(FLT32));
	else
          {
            const double *restrict p = (const double *)data;
            float *restrict q = (float *)buffer;
            for (size_t i = 0; i < datasize; i++) q[i] = (float)p[i];
          }
	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	if ( dprec == prec )
	  memcpy(buffer, data, datasize*sizeof(FLT64));
	else
          {
            const float *restrict p = (const float *)data;
            double *restrict q = (double *)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 iegDefDataSP(void *ieg, const float *data)
{
  return iegDefData((iegrec_t *)ieg, EXSE_SINGLE_PRECISION, (void *) data);
}


int iegDefDataDP(void *ieg, const double *data)
{
  return iegDefData((iegrec_t *)ieg, EXSE_DOUBLE_PRECISION, (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 ( size_t i = 0; i < 37; i++ ) iegp->ipdb[i] = (int)buf.i32[i];

  binReadInt32(fileID, byteswap, 18, buf.i32);
  for ( size_t 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 ( size_t i = 0; i < 3; i++ ) iegp->igdb[18+i] = (int)buf.i32[i];

  if ( dprec == EXSE_SINGLE_PRECISION )
    {
      fileRead(fileID, buf.f, 400);
      if ( byteswap ) swap4byte(buf.f, 100);
      for ( size_t 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 ( size_t 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;
    }

  size_t datasize = iegp->datasize
    = (size_t)IEG_G_NumLon(iegp->igdb) * (size_t)IEG_G_NumLat(iegp->igdb);

  if ( IEG_Debug ) Message("datasize = %lu", iegp->datasize);

  blocklen = binReadF77Block(fileID, byteswap);

  void *buffer = iegp->buffer;
  if ( iegp->buffersize < blocklen )
    {
      iegp->buffer = buffer = Realloc(buffer, blocklen);
      iegp->buffersize = blocklen;
    }

  if ( dprec != (int) (blocklen/datasize) )
    {
      Warning("data precision differ! (h = %d; d = %d)",
	      (int) dprec, (int) (blocklen/datasize));
      return -1;
    }

  fileRead(fileID, 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 i32[200]; float fvct[100]; } buf;
  int dprec  = iegp->dprec;
  int byteswap = iegp->byteswap;

  /* write header record */
  size_t blocklen = ( dprec == EXSE_SINGLE_PRECISION ) ? 636 : 1040;

  binWriteF77Block(fileID, byteswap, blocklen);

  for ( size_t i = 0; i < 37; i++ ) buf.i32[i] = (INT32) iegp->ipdb[i];
  binWriteInt32(fileID, byteswap, 37, buf.i32);

  for ( size_t i = 0; i < 18; i++ ) buf.i32[i] = (INT32) iegp->igdb[i];
  binWriteInt32(fileID, byteswap, 18, buf.i32);

  FLT64 refval = (FLT64)iegp->refval;
  FLT32 refvalf = (FLT32)iegp->refval;
  if ( dprec == EXSE_SINGLE_PRECISION )
    binWriteFlt32(fileID, byteswap, 1, &refvalf);
  else
    binWriteFlt64(fileID, byteswap, 1, &refval);

  for ( size_t i = 0; i < 3; i++ ) buf.i32[i] = (INT32) iegp->igdb[18+i];
  binWriteInt32(fileID, byteswap, 3, buf.i32);

  if ( dprec == EXSE_SINGLE_PRECISION )
    {
      for ( size_t 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);

  size_t datasize = (size_t)iegp->igdb[4] * (size_t)iegp->igdb[5];
  blocklen = datasize * (size_t)dprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  iegp->datasize = datasize;

  void *buffer = iegp->buffer;

  switch ( dprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	binWriteFlt32(fileID, byteswap, datasize, (FLT32 *) buffer);
	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	binWriteFlt64(fileID, byteswap, datasize, (FLT64 *) 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:
 */
#define _XOPEN_SOURCE 600


#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

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;
static pthread_mutex_t openFileListLock = PTHREAD_MUTEX_INITIALIZER;

//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);
  int error = pthread_mutex_lock(&openFileListLock);
  xassert(!error);
    {
      //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;
            }
        }
    }
  error = pthread_mutex_unlock(&openFileListLock);
  xassert(!error);
  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)
{
  int error = pthread_mutex_lock(&openFileListLock);
  xassert(!error);
    {
      //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];
    }
  error = pthread_mutex_unlock(&openFileListLock);
  xassert(!error);
  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:
 */
#include <assert.h>
#include <limits.h>



typedef struct
{
  int    self;
  int    used;
  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 );

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->used       = 0;
  instituteptr->center     = CDI_UNDEFID;
  instituteptr->subcenter  = CDI_UNDEFID;
  instituteptr->name       = NULL;
  instituteptr->longname   = NULL;
}

void instituteDefaultEntries(void)
{
  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)");
  };

  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 (ip2->used && !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 = (institute_t *) Malloc(sizeof (*ip_ref));
  ip_ref->self       = CDI_UNDEFID;
  ip_ref->used       = 0;
  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);

  Free(ip_ref);

  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->used = 1;
  instituteptr->center = center;
  instituteptr->subcenter = subcenter;
  if ( name && *name )
    instituteptr->name = strdupx(name);
  if (longname && *longname)
    instituteptr->longname = strdupx(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)
{
  institute_t * instituteptr = NULL;

  if ( instID != CDI_UNDEFID )
    instituteptr = ( institute_t * ) reshGetVal ( instID, &instituteOps );

  return  instituteptr ? instituteptr->center : CDI_UNDEFID;
}


int institutInqSubcenter(int instID)
{
  institute_t * instituteptr = NULL;

  if ( instID != CDI_UNDEFID )
    instituteptr = ( institute_t * ) reshGetVal ( instID, &instituteOps );

  return instituteptr ? instituteptr->subcenter: CDI_UNDEFID;
}


const char *institutInqNamePtr(int instID)
{
  institute_t * instituteptr = NULL;

  if ( instID != CDI_UNDEFID )
    instituteptr = ( institute_t * ) reshGetVal ( instID, &instituteOps );

  return instituteptr ? instituteptr->name : NULL;
}


const char *institutInqLongnamePtr(int instID)
{
  institute_t * instituteptr = NULL;

  if ( instID != CDI_UNDEFID )
    instituteptr = ( institute_t * ) reshGetVal ( instID, &instituteOps );

  return instituteptr ? instituteptr->longname : NULL;
}

static enum cdiApplyRet
activeInstitutes(int id, void *res, void *data)
{
  (void)id;
  if (res && ((institute_t *)res)->used)
    ++(*(int *)data);
  return CDI_APPLY_GO_ON;
}

int institutInqNumber(void)
{
  int instNum = 0;

  cdiResHFilterApply(&instituteOps, activeInstitutes, &instNum);
  return instNum;
}


static void
instituteDestroyP(institute_t *instituteptr)
{
  xassert(instituteptr);

  int instituteID = instituteptr->self;
  Free(instituteptr->name);
  Free(instituteptr->longname);
  reshRemove(instituteID, &instituteOps);
  Free(instituteptr);
}


static void institutePrintP(institute_t *ip, FILE * fp )
{
  if (ip)
    fprintf(fp, "#\n"
            "# instituteID %d\n"
            "#\n"
            "self          = %d\n"
            "used          = %d\n"
            "center        = %d\n"
            "subcenter     = %d\n"
            "name          = %s\n"
            "longname      = %s\n",
            ip->self, ip->self, ip->used, ip->center, ip->subcenter,
            ip->name ? ip->name : "NN",
            ip->longname ? ip->longname : "NN");
}


static int
instituteTxCode ( void )
{
  return INSTITUTE;
}

enum {
  institute_nints = 5,
};

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[0] = p->self;
  tempbuf[1] = p->center;
  tempbuf[2] = p->subcenter;
  tempbuf[3] = (int)strlen(p->name) + 1;
  tempbuf[4] = (int)strlen(p->longname) + 1;
  serializePack(tempbuf, institute_nints, CDI_DATATYPE_INT, buf, size, position, context);
  serializePack(p->name, tempbuf[3], CDI_DATATYPE_TXT, buf, size, position, context);
  serializePack(p->longname, tempbuf[4], CDI_DATATYPE_TXT, buf, size, position, context);
}

int instituteUnpack(void *buf, int size, int *position, int originNamespace,
                    void *context, int force_id)
{
  int tempbuf[institute_nints];
  int instituteID;
  serializeUnpack(buf, size, position, tempbuf, institute_nints, CDI_DATATYPE_INT, context);
  char *name = (char *) Malloc((size_t)tempbuf[3] + (size_t)tempbuf[4]);
  char *longname = name + tempbuf[3];
  serializeUnpack(buf, size, position, name, tempbuf[3], CDI_DATATYPE_TXT, context);
  serializeUnpack(buf, size, position, longname, tempbuf[4], CDI_DATATYPE_TXT, context);
  int targetID = namespaceAdaptKey(tempbuf[0], originNamespace);
  institute_t *ip = instituteNewEntry(force_id?targetID:CDI_UNDEFID,
                                      tempbuf[1], tempbuf[2], name, longname);
  instituteID = ip->self;
  xassert(!force_id || instituteID == targetID);
  Free(name);
  reshSetStatus(instituteID, &instituteOps,
                reshGetStatus(instituteID, &instituteOps) & ~RESH_SYNC_BIT);
  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 *nmiss);
void cdiFallbackIterator_readFieldF(CdiIterator *me, float *buffer, size_t *nmiss);

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 *nmiss);
void cdiGribIterator_readFieldF(CdiIterator *me, float *buffer, size_t *nmiss);

#endif

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

#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";
#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::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;
  int filetype = cdiGetFiletype(path, &trash);
  switch(filetype)
    {
      case CDI_FILETYPE_UNDEF:
        Warning("Can't open file \"%s\": unknown format\n", path);
        return NULL;

#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_new(path, filetype);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_getSuper(cdiGribIterator_clone(me));
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          subclassDescription = cdiGribIterator_serialize(me);
          break;
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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;
  char* result = (char *) Malloc(strlen(ftypeStr) + 1 + strlen(advStr) + 1
                         + strlen(subclassDescription) + 1);
  sprintf(result, "%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(string2FileType(description, NULL))
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_getSuper(cdiGribIterator_deserialize(description));
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_nextField(me);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_inqTime(me, timeType);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_levelType(me, levelSelector, outName, outLongName, outStdName, outUnit);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_level(me, levelSelector, outValue1, outValue2);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_zaxisUuid(me, outVgridNumber, outLevelCount, outUuid);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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(me->filetype)
    {
      #ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_inqTile(me, outTileIndex, outTileAttribute);
      #endif

      #ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
      #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(me->filetype)
    {
      #ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_inqTileCount(me, outTileCount, outTileAttributeCount);
      #endif

      #ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
      #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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          return cdiGribIterator_copyVariableName(me);
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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, size_t* nmiss)
@Parameter
    @item iterator The iterator to operate on.
    @item buffer A pointer to the double array that the data should be written to.
    @item nmiss 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, size_t* nmiss)
{
  sanityCheck(me);
  if(!buffer) xabort("NULL was passed in a buffer. Please provide a suitable buffer.");
  switch(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          cdiGribIterator_readField(me, buffer, nmiss);
	  return;
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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, nmiss);
          return;
      default:
        Error(kUnexpectedFileTypeMessage);
    }
}

/*
@Function cdiIterator_readFieldF
@Title Read the whole field into a double buffer

@Prototype void cdiIterator_readFieldF(CdiIterator* me, float* buffer, size_t* nmiss)
@Parameter
    @item iterator The iterator to operate on.
    @item buffer A pointer to the double array that the data should be written to.
    @item nmiss 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, size_t* nmiss)
{
  sanityCheck(me);
  if(!buffer) xabort("NULL was passed in a buffer. Please provide a suitable buffer.");
  switch(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          cdiGribIterator_readFieldF(me, buffer, nmiss);
	  return;
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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, nmiss);
          return;
      default:
        Error(kUnexpectedFileTypeMessage);
    }
}

/*
@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(me->filetype)
    {
#ifdef HAVE_LIBGRIB_API
        case CDI_FILETYPE_GRB:
        case CDI_FILETYPE_GRB2:
          cdiGribIterator_delete((CdiGribIterator*)me);
          break;
#endif

#ifdef HAVE_LIBNETCDF
        case CDI_FILETYPE_NC:
        case CDI_FILETYPE_NC2:
        case CDI_FILETYPE_NC4:
        case CDI_FILETYPE_NC4C:
        case CDI_FILETYPE_NC5:
#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:
 */


#include <assert.h>
#include <limits.h>
#include <stdlib.h>

struct CdiFallbackIterator {
  CdiIterator super;
  int streamId, vlistId, subtypeId;
  char *path;   //needed for clone() & serialize()

  int variableCount, curVariable;
  int curLevelCount, curLevel;
  int curSubtypeCount, curSubtype;
  int curTimestep;
};

CdiIterator *cdiFallbackIterator_getSuper(CdiFallbackIterator *me)
{
  return &me->super;
}


//For more information on the condestruct() pattern, see comment in src/iterator_grib.c
static CdiFallbackIterator *cdiFallbackIterator_condestruct(CdiFallbackIterator *me, const char *path, int filetype)
{
  if(me) goto destruct;

  me = (CdiFallbackIterator *) Malloc(sizeof(*me));
  baseIterConstruct(&me->super, filetype);

  me->streamId = streamOpenRead(path);
  if(me->streamId == CDI_UNDEFID) goto destructSuper;
  me->vlistId = streamInqVlist(me->streamId);
  if(me->vlistId == CDI_UNDEFID) goto closeStream;
  me->variableCount = vlistNvars(me->vlistId);
  if(me->variableCount <= 0) goto closeStream;
  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;
  if(streamInqTimestep(me->streamId, me->curTimestep) <= 0) goto closeStream;
  me->curVariable = -1;
  me->curSubtype = -1;
  me->curLevel = -1;
  me->path = path ? strdup(path) : NULL;
  if(!me->path) goto closeStream;

  return me;

// ^        constructor code        ^
// |                                |
// v destructor/error-cleanup code  v

destruct:
  Free(me->path);
closeStream:
  streamClose(me->streamId);
destructSuper:
  baseIterDestruct(&me->super);
  Free(me);
  return NULL;
}

CdiIterator *cdiFallbackIterator_new(const char *path, int filetype)
{
  return &cdiFallbackIterator_condestruct(NULL, path, filetype)->super;
}

//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_condestruct(NULL, me->path, me->super.filetype);
  if(!clone) return NULL;

  //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);
  char *result = (char *) Malloc(strlen(escapedPath)
                         + 7 * (3 * sizeof (int) * CHAR_BIT / 8 + 1) + 1);
  sprintf(result, "%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);
  char *result
    = (char *) Malloc(   4+1 +2+1 +2+1 +2+1 +2+1 +2+4+1);
  sprintf(result,     "%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, 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;
#ifndef __cplusplus
  if(!outTileIndex) outTileIndex = &(int){0};
  if(!outTileAttribute) outTileAttribute = &(int){0};
#else
  int dummy = 0;
  if(!outTileIndex) outTileIndex = &dummy;
  if(!outTileAttribute) outTileAttribute = &dummy;
#endif

  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;
#ifndef __cplusplus
  if(!outTileCount) outTileCount = &(int){0};
  if(!outTileAttributeCount) outTileAttributeCount = &(int){0};
#else
  int temp = 0;
  if(!outTileCount) outTileCount = &temp;
  if(!outTileAttributeCount) outTileAttributeCount = &temp;
#endif

  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 *nmiss)
{
  CdiFallbackIterator *me = (CdiFallbackIterator*)(void *)super;
  size_t missingValues = 0;
  streamReadVarSlice(me->streamId, me->curVariable, me->curLevel, buffer, &missingValues);
  if(nmiss) *nmiss = (size_t)missingValues;
}

void cdiFallbackIterator_readFieldF(CdiIterator *super, float *buffer, size_t *nmiss)
{
  CdiFallbackIterator *me = (CdiFallbackIterator*)(void *)super;
  size_t missingValues = 0;
  streamReadVarSliceF(me->streamId, me->curVariable, me->curLevel, buffer, &missingValues);
  if(nmiss) *nmiss = (size_t)missingValues;
}

void cdiFallbackIterator_delete(CdiIterator *super)
{
  CdiFallbackIterator *me = (CdiFallbackIterator*)(void *)super;
  cdiFallbackIterator_condestruct(me, NULL, 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 STREAM_GRB_H
#define STREAM_GRB_H

void ensureBufferSize(size_t requiredSize, size_t *curSize, void **buffer);
int grbDecompress(size_t recsize, size_t *buffersize, void **gribbuffer);

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   grbInqContents(stream_t *streamptr);
int   grbInqTimestep(stream_t *streamptr, int tsID);

int   grbInqRecord(stream_t *streamptr, int *varID, int *levelID);
void  grbDefRecord(stream_t *streamptr);
void  grb_read_record(stream_t *streamptr, int memtype, void *data, size_t *nmiss);
void  grb_write_record(stream_t *streamptr, int memtype, const void *data, size_t nmiss);
void  grbCopyRecord(stream_t *streamptr2, stream_t *streamptr1);

void  grb_read_var(stream_t *streamptr, int varID, int memtype, void *data, size_t *nmiss);
void  grb_write_var(stream_t *streamptr, int varID, int memtype, const void *data, size_t nmiss);

void  grb_read_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, void *data, size_t *nmiss);
void  grb_write_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, const void *data, size_t nmiss);

int   grib1ltypeToZaxisType(int grib_ltype);
int   grib2ltypeToZaxisType(int grib_ltype);

int   zaxisTypeToGrib1ltype(int zaxistype);
int   zaxisTypeToGrib2ltype(int zaxistype);

int grbGetGridtype(int *gridID, size_t 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



typedef struct {
  double    *vals;
#ifndef USE_MPI
  char     **cvals;
  int        clength;
#endif
  double    *lbounds;
  double    *ubounds;
  double    *weights;
  int        self;
  int        datatype;
  int        scalar;
  int        type;
  int        size;
  int        direction;
  int        vctsize;
  unsigned   positive;
  double    *vct;
  cdi_keys_t keys;
  cdi_atts_t atts;
}
zaxis_t;


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);

void
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);
  char *result = (char *) Malloc(strlen(escapedPath) + 3 * sizeof(int) * CHAR_BIT/8);
  sprintf(result, "%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 compressio 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 *nmiss)
{
  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(nmiss)
    {
      *nmiss = (gridType >= 50 && gridType <= 53) ? (size_t)0 : (size_t)gribGetLong(me->gribHandle, "numberOfMissing");        //The condition excludes harmonic data.
    }
}

void cdiGribIterator_readFieldF(CdiIterator *super, float *buffer, size_t *nmiss)
{
  CdiGribIterator *me = (CdiGribIterator*)(void *)super;

  size_t valueCount = gribGetArraySize(me->gribHandle, "values");
  double *temp = (double *) Malloc(valueCount*sizeof(*temp));
  cdiGribIterator_readField(super, temp, nmiss);
  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:
 */
#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:
 */
#include <limits.h>


#undef  CDI_UNDEFID
#define CDI_UNDEFID -1

typedef struct
{
  int      self;
  int      used;
  int      instID;
  int      modelgribID;
  char    *name;
}
model_t;


static int  MODEL_Debug = 0;   /* If set to 1, debugging */

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 );

static const resOps modelOps = {
  modelCompareP,
  modelDestroyP,
  modelPrintP,
  modelGetSizeP,
  modelPackP,
  modelTxCode
};

static
void modelDefaultValue ( model_t *modelptr )
{
  modelptr->self        = CDI_UNDEFID;
  modelptr->used        = 0;
  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->used = 1;
  modelptr->instID = instID;
  modelptr->modelgribID = modelgribID;
  if ( name && *name ) modelptr->name = strdupx(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;

  modelInitialized = true;
  char *env = getenv("MODEL_DEBUG");
  if ( env ) MODEL_Debug = atoi(env);
}

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->used
      && 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 (modelptr->used
      && (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)
    | (strcmp(model1->name, model2->name) != 0);
  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;

  if ( !mp ) return;

  fprintf ( fp, "#\n");
  fprintf ( fp, "# modelID %d\n", mp->self);
  fprintf ( fp, "#\n");
  fprintf ( fp, "self          = %d\n", mp->self );
  fprintf ( fp, "used          = %d\n", mp->used );
  fprintf ( fp, "instID        = %d\n", mp->instID );
  fprintf ( fp, "modelgribID   = %d\n", mp->modelgribID );
  fprintf ( fp, "name          = %s\n", mp->name ? mp->name : "NN" );
}


static int
modelTxCode ( void )
{
  return MODEL;
}

enum {
  model_nints = 4,
};


static int modelGetSizeP(void * modelptr, void *context)
{
  model_t *p = (model_t*)modelptr;
  size_t txsize = (size_t)serializeGetSize(model_nints, CDI_DATATYPE_INT, context)
    + (size_t)serializeGetSize(p->name?(int)strlen(p->name) + 1: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[model_nints];
  tempbuf[0] = p->self;
  tempbuf[1] = p->instID;
  tempbuf[2] = p->modelgribID;
  tempbuf[3] = p->name ? (int)strlen(p->name) + 1 : 0;
  serializePack(tempbuf, model_nints, CDI_DATATYPE_INT, buf, size, position, context);
  if (p->name)
    serializePack(p->name, tempbuf[3], CDI_DATATYPE_TXT, buf, size, position, context);
}

int
modelUnpack(void *buf, int size, int *position, int originNamespace, void *context,
            int force_id)
{
  int tempbuf[model_nints];
  char *name;
  serializeUnpack(buf, size, position, tempbuf, model_nints, CDI_DATATYPE_INT, context);
  if (tempbuf[3] != 0)
    {
      name = (char *) Malloc((size_t)tempbuf[3]);
      serializeUnpack(buf, size, position,
                      name, tempbuf[3], CDI_DATATYPE_TXT, context);
    }
  else
    {
      name = (char*)"";
    }
  int targetID = namespaceAdaptKey(tempbuf[0], originNamespace);
  model_t *mp = modelNewEntry(force_id?targetID:CDI_UNDEFID,
                              namespaceAdaptKey(tempbuf[1], originNamespace),
                              tempbuf[2], name);
  if (tempbuf[3] != 0)
    Free(name);
  xassert(!force_id
          || (mp->self == namespaceAdaptKey(tempbuf[0], originNamespace)));
  reshSetStatus(mp->self, &modelOps,
                reshGetStatus(mp->self, &modelOps) & ~RESH_SYNC_BIT);
  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:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 600 /* PTHREAD_MUTEX_RECURSIVE */
#endif

#include <limits.h>
#include <stdio.h>


static unsigned nNamespaces = 1;
static int activeNamespace = 0;

#ifdef HAVE_LIBNETCDF
#define CDI_NETCDF_SWITCHES                     \
  { .func = (void (*)()) nc__create },          \
  { .func = (void (*)()) cdf_def_var_serial },  \
  { .func = (void (*)()) cdfDefTimestep },      \
  { .func = (void (*)()) cdfDefCoordinateVars }

#else
#define CDI_NETCDF_SWITCHES
#endif

#define defaultSwitches {                                   \
    { .func = (void (*)()) cdiAbortC_serial },              \
    { .func = (void (*)()) cdiWarning },                    \
    { .func = (void (*)()) serializeGetSizeInCore },        \
    { .func = (void (*)()) serializePackInCore },           \
    { .func = (void (*)()) serializeUnpackInCore },         \
    { .func = (void (*)()) fileOpen_serial },               \
    { .func = (void (*)()) fileWrite },                     \
    { .func = (void (*)()) fileClose_serial },              \
    { .func = (void (*)()) cdiStreamOpenDefaultDelegate },  \
    { .func = (void (*)()) cdiStreamDefVlist_ },            \
    { .func = (void (*)()) cdiStreamSetupVlist_ },          \
    { .func = (void (*)()) cdiStreamWriteVar_ },            \
    { .func = (void (*)()) cdiStreamWriteVarChunk_ },       \
    { .func = (void (*)()) 0 },                             \
    { .func = (void (*)()) 0 },                             \
    { .func = (void (*)()) cdiStreamCloseDefaultDelegate }, \
    { .func = (void (*)()) cdiStreamDefTimestep_ }, \
    { .func = (void (*)()) cdiStreamSync_ },                \
    CDI_NETCDF_SWITCHES                        \
    }

#if defined (SX) || defined (__cplusplus)
static const union namespaceSwitchValue
  defaultSwitches_[NUM_NAMESPACE_SWITCH] = defaultSwitches;
#endif

enum namespaceStatus {
  NAMESPACE_STATUS_INUSE,
  NAMESPACE_STATUS_UNUSED,
};

static struct Namespace
{
  enum namespaceStatus resStage;
  union namespaceSwitchValue switches[NUM_NAMESPACE_SWITCH];
} initialNamespace = {
  .resStage = NAMESPACE_STATUS_INUSE,
  .switches = defaultSwitches
};

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()
{
  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;
#if defined (SX) || defined (__cplusplus)
  memcpy(namespaces[newNamespaceID].switches,
         defaultSwitches_,
         sizeof (namespaces[newNamespaceID].switches));
#else
    memcpy(namespaces[newNamespaceID].switches,
           (union namespaceSwitchValue[NUM_NAMESPACE_SWITCH])defaultSwitches,
           sizeof (namespaces[newNamespaceID].switches));
#endif
  reshListCreate(newNamespaceID);
  NAMESPACE_UNLOCK();
  return newNamespaceID;
}

void
namespaceDelete(int namespaceID)
{
  NAMESPACE_INIT();
  NAMESPACE_LOCK();
  xassert(namespaceID >= 0 && (unsigned)namespaceID < namespacesSize
          && nNamespaces);
  reshListDestruct(namespaceID);
  namespaces[namespaceID].resStage = NAMESPACE_STATUS_UNUSED;
  --nNamespaces;
  NAMESPACE_UNLOCK();
}

int namespaceGetNumber ()
{
  return (int)nNamespaces;
}


void namespaceSetActive ( int nId )
{
  xassert((unsigned)nId < namespacesSize
          && namespaces[nId].resStage != NAMESPACE_STATUS_UNUSED);
  activeNamespace = nId;
}


int namespaceGetActive ()
{
  return activeNamespace;
}

int namespaceAdaptKey ( int originResH, int originNamespace )
{
  namespaceTuple_t tin;
  int nsp;

  if ( originResH == CDI_UNDEFID ) return CDI_UNDEFID;

  tin.idx = originResH & idxmask;
  tin.nsp = (int)(((unsigned)( originResH & nspmask )) >> idxbits);

  xassert ( tin.nsp == originNamespace );

  nsp = namespaceGetActive ();

  return namespaceIdxEncode2 ( nsp, tin.idx );
}


int namespaceAdaptKey2 ( int originResH )
{
  namespaceTuple_t tin;
  int nsp;

  if ( originResH == CDI_UNDEFID ) return CDI_UNDEFID;

  tin.idx = originResH & idxmask;
  tin.nsp = (int)(((unsigned)( originResH & nspmask )) >> idxbits);

  nsp = namespaceGetActive ();

  return namespaceIdxEncode2 ( nsp, tin.idx );
}

void namespaceSwitchSet(enum namespaceSwitch sw, union namespaceSwitchValue value)
{
  xassert(sw > NSSWITCH_NO_SUCH_SWITCH && sw < NUM_NAMESPACE_SWITCH);
  int nsp = namespaceGetActive();
  namespaces[nsp].switches[sw] = value;
}

union namespaceSwitchValue namespaceSwitchGet(enum namespaceSwitch sw)
{
  xassert(sw > NSSWITCH_NO_SUCH_SWITCH && sw < NUM_NAMESPACE_SWITCH);
  int nsp = namespaceGetActive();
  return namespaces[nsp].switches[sw];
}

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:
 */


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()
{
#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

/**************************************************************/

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;
  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();
}


/**************************************************************/

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);
        }
      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()
{
  int nsp = namespaceGetActive ();
  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;
}

/**************************************************************/

static void
reshPut_(int nsp, int entry, void *p, const resOps *ops)
{
  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;
}

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)
{
  int curFree = resHList[nsp].freeHead;
  listElem_t *r = resHList[nsp].resources;
  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;
}

void reshDestroy(cdiResH resH)
{
  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].res.v.ops);

  if (resHList[nsp].resources[nspT.idx].status & RESH_IN_USE_BIT)
    reshRemove_(nsp, nspT.idx);

  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);

  LIST_UNLOCK();
}

/**************************************************************/

void reshReplace(cdiResH resH, void *p, const resOps *ops)
{
  xassert(p && ops);
  LIST_INIT(1);
  LIST_LOCK();
  int nsp = namespaceGetActive();
  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);
    }
  reshPut_(nsp, nspT.idx, p, ops);
  LIST_UNLOCK();
}


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();
    }

  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();
            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;

  xassert((ops != NULL) ^ !(status & RESH_IN_USE_BIT));

  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));
      status = listElem->status;
    }

  LIST_UNLOCK();

  return status;
}

/**************************************************************/

void reshLock ()
{
  LIST_LOCK();
}

/**************************************************************/

void reshUnlock ()
{
  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:
 */
#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:
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>



enum {
  SRV_HEADER_LEN = 8,
};


static int initSrvLib      = 0;
static int srvDefaultHprec = 0;
static int srvDefaultDprec = 0;


/*
 * A version string.
 */
#undef  LIBVERSION
#define LIBVERSION      1.4.1
#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)
{
  SRV_Debug = debug;
  if (SRV_Debug) Message("debug level %d", debug);
}

static
void srvLibInit()
{
  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_SINGLE_PRECISION; break;
		  case '8': srvDefaultHprec = EXSE_DOUBLE_PRECISION; break;
		  default:
		    Message("Invalid digit in %s: %s", envName, envString);
		  }
		break;
	      }
	    case 'r':
	      {
		switch ( (int) envString[pos+1] )
		  {
		  case '4': srvDefaultDprec = EXSE_SINGLE_PRECISION; break;
		  case '8': srvDefaultDprec = EXSE_DOUBLE_PRECISION; break;
		  default:
		    Message("Invalid digit in %s: %s", envName, envString);
		  }
		break;
	      }
	    default:
              {
                Message("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_SUINT32(buffer);

  if ( SRV_Debug )
    Message("blocklen = %d sblocklen = %d", blocklen, sblocklen);

  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_SUINT32(pbuf);
      pbuf = buffer+5*fact;       dimy = (size_t) get_SUINT32(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_SUINT32(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_SUINT64(pbuf);
      pbuf = buffer+5*fact;       dimy = (size_t) get_SUINT64(pbuf);
      pbuf = buffer+sblocklen+4;  data = (size_t) get_SUINT32(pbuf);
    }

  fileRewind(fileID);

  if ( SRV_Debug )
    {
      Message("swap = %d fact = %d", *swap, fact);
      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 (size_t 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 (size_t i = 0; i < SRV_HEADER_LEN; i++) srvp->header[i] = header[i];

  srvp->datasize = (size_t)(header[4] * header[5]);

  if (SRV_Debug) Message("datasize = %lu", 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_SINGLE_PRECISION:
      {
	if ( sizeof(FLT32) == 4 )
	  {
	    if ( byteswap ) swap4byte(buffer, datasize);

	    if ( dprec == prec )
	      memcpy(data, buffer, datasize*sizeof(FLT32));
	    else
	      for (size_t i = 0; i < datasize; i++)
		((double *) data)[i] = (double) ((float *) buffer)[i];
	  }
	else
	  {
	    Error("not implemented for %d byte float", sizeof(FLT32));
	  }
	break;
      }
    case EXSE_DOUBLE_PRECISION:
	if ( sizeof(FLT64) == 8 )
	  {
	    if ( byteswap ) swap8byte(buffer, datasize);

	    if ( dprec == prec )
	      memcpy(data, buffer, datasize*sizeof(FLT64));
	    else
	      for (size_t i = 0; i < datasize; i++)
		((float *) data)[i] = (float) ((double *) buffer)[i];
	  }
	else
	  {
	    Error("not implemented for %d byte float", sizeof(FLT64));
	  }
	break;
    default:
      {
	Error("unexpected data precision %d", dprec);
        break;
      }
    }

  return ierr;
}


int srvInqDataSP(void *srv, float *data)
{
  return srvInqData((srvrec_t *)srv, EXSE_SINGLE_PRECISION, (void *) data);
}


int srvInqDataDP(void *srv, double *data)
{
  return srvInqData((srvrec_t *)srv, EXSE_DOUBLE_PRECISION, (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] * header[5]);
  size_t blocklen = datasize * (size_t)dprec;

  srvp->datasize = datasize;

  void *buffer = srvp->buffer;
  size_t buffersize = srvp->buffersize;
  if ( buffersize != blocklen )
    {
      buffersize = blocklen;
      buffer = Realloc(buffer, buffersize);
      srvp->buffer = buffer;
      srvp->buffersize = buffersize;
    }

  switch ( dprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	if ( dprec == prec )
	  memcpy(buffer, data, datasize*sizeof(FLT32));
	else
	  for (size_t i = 0; i < datasize; i++)
	    ((float *) buffer)[i] = (float) ((double *) data)[i];

	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	if ( dprec == prec )
	  memcpy(buffer, data, datasize*sizeof(FLT64));
	else
	  for (size_t i = 0; i < datasize; i++)
	    ((double *) buffer)[i] = (double) ((float *) data)[i];

	break;
      }
    default:
      {
	Error("unexpected data precision %d", dprec);
        break;
      }
    }

  return 0;
}


int srvDefDataSP(void *srv, const float *data)
{
  return srvDefData(srv, EXSE_SINGLE_PRECISION, (void *) data);
}


int srvDefDataDP(void *srv, const double *data)
{
  return srvDefData(srv, EXSE_DOUBLE_PRECISION, (void *) data);
}


int srvRead(int fileID, void *srv)
{
  srvrec_t *srvp = (srvrec_t *) srv;
  union {
    INT32 i32[SRV_HEADER_LEN];
    INT64 i64[SRV_HEADER_LEN];
  } 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_SINGLE_PRECISION:
      {
	binReadInt32(fileID, byteswap, SRV_HEADER_LEN, tempheader.i32);

	for (size_t i = 0; i < SRV_HEADER_LEN; i++)
          srvp->header[i] = (int)tempheader.i32[i];

	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	binReadInt64(fileID, byteswap, SRV_HEADER_LEN, tempheader.i64);

	for (size_t 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] * srvp->header[5]);

  if ( SRV_Debug ) Message("datasize = %lu", srvp->datasize);

  blocklen = binReadF77Block(fileID, byteswap);

  void *buffer = srvp->buffer;
  size_t buffersize = srvp->buffersize;

  if ( buffersize < blocklen )
    {
      buffersize = blocklen;
      buffer = Realloc(buffer, buffersize);
      srvp->buffer = buffer;
      srvp->buffersize = buffersize;
    }

  size_t datasize = srvp->datasize;

  size_t dprec = blocklen / datasize;

  srvp->dprec = (int)dprec;

  if ( dprec != EXSE_SINGLE_PRECISION && dprec != EXSE_DOUBLE_PRECISION )
    {
      Warning("Unexpected data precision %d", dprec);
      return -1;
    }

  fileRead(fileID, 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
  {
    INT32 i32[SRV_HEADER_LEN];
    INT64 i64[SRV_HEADER_LEN];
  } 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_SINGLE_PRECISION:
      {
	for (size_t i = 0; i < SRV_HEADER_LEN; i++)
          tempheader.i32[i] = (INT32) header[i];

	binWriteInt32(fileID, byteswap, SRV_HEADER_LEN, tempheader.i32);

	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	for (size_t i = 0; i < SRV_HEADER_LEN; i++)
          tempheader.i64[i] = (INT64) header[i];

	binWriteInt64(fileID, byteswap, SRV_HEADER_LEN, tempheader.i64);

	break;
      }
    default:
      {
	Error("unexpected header precision %d", hprec);
        break;
      }
    }

  binWriteF77Block(fileID, byteswap, blocklen);

  size_t datasize = (size_t)(header[4] * header[5]);
  blocklen = datasize * (size_t)dprec;

  binWriteF77Block(fileID, byteswap, blocklen);

  srvp->datasize = datasize;

  void *buffer = srvp->buffer;

  switch ( dprec )
    {
    case EXSE_SINGLE_PRECISION:
      {
	binWriteFlt32(fileID, byteswap, datasize, (FLT32 *) buffer);
	break;
      }
    case EXSE_DOUBLE_PRECISION:
      {
	binWriteFlt64(fileID, byteswap, datasize, (FLT64 *) 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 */


void streamScanResizeRecords1(stream_t *streamptr)
{
  const int nrecords = streamptr->tsteps[0].nallrecs;
  if (nrecords < streamptr->tsteps[0].recordSize)
    {
      streamptr->tsteps[0].recordSize = nrecords;
      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)
{
  const 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;

  for (int recID = 0; recID < nrecords; ++recID)
    {
      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)
{
  const 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)
{
  const int vlistID = streamptr->vlistID;
  if (streamptr->ntsteps == 1)
    {
      if (taxis->vdate == 0 && taxis->vtime == 0)
	{
	  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)
    {
      const 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 STREAM_CGRIBEX_H
#define STREAM_CGRIBEX_H

void *cgribexNew();
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 *nmiss, double missval);

size_t cgribexEncode(int memtype, int varID, int levelID, int vlistID, int gridID, int zaxisID,
		     int vdate, int vtime, int tsteptype, int numavg,
		     size_t datasize, const void *data, size_t nmiss, 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);

int    srvInqRecord(stream_t *streamptr, int *varID, int *levelID);
void   srvDefRecord(stream_t *streamptr);
void   srvCopyRecord(stream_t *streamptr2, stream_t *streamptr1);
void   srvReadRecord(stream_t *streamptr, double *data, size_t *nmiss);
void   srvWriteRecord(stream_t *streamptr, const double *data);

void   srvReadVarDP (stream_t *streamptr, int varID,       double *data, size_t *nmiss);
void   srvWriteVarDP(stream_t *streamptr, int varID, const double *data);

void   srvReadVarSliceDP (stream_t *streamptr, int varID, int levelID,       double *data, size_t *nmiss);
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);

int    extInqRecord(stream_t *streamptr, int *varID, int *levelID);
void   extDefRecord(stream_t *streamptr);
void   extCopyRecord(stream_t *streamptr2, stream_t *streamptr1);
void   extReadRecord(stream_t *streamptr, double *data, size_t *nmiss);
void   extWriteRecord(stream_t *streamptr, const double *data);

void   extReadVarDP (stream_t *streamptr, int varID,       double *data, size_t *nmiss);
void   extWriteVarDP(stream_t *streamptr, int varID, const double *data);

void   extReadVarSliceDP (stream_t *streamptr, int varID, int levelID,       double *data, size_t *nmiss);
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);

int    iegInqRecord(stream_t *streamptr, int *varID, int *levelID);
void   iegDefRecord(stream_t *streamptr);
void   iegCopyRecord(stream_t *streamptr2, stream_t *streamptr1);
void   iegReadRecord(stream_t *streamptr, double *data, size_t *nmiss);
void   iegWriteRecord(stream_t *streamptr, const double *data);

void   iegReadVarDP (stream_t *streamptr, int varID,       double *data, size_t *nmiss);
void   iegWriteVarDP(stream_t *streamptr, int varID, const double *data);

void   iegReadVarSliceDP (stream_t *streamptr, int varID, int levelID,       double *data, size_t *nmiss);
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

#include <sys/stat.h> // struct stat
#include <ctype.h>



static stream_t *stream_new_entry(int resH);
static void stream_delete_entry(stream_t *streamptr);
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);

const resOps streamOps = {
  streamCompareP,
  streamDestroyP,
  streamPrintP,
  streamGetPackSize,
  streamPack,
  streamTxCode
};



static
int getByteorder(int byteswap)
{
  int byteorder = -1;

  switch (HOST_ENDIANNESS)
    {
    case CDI_BIGENDIAN:
      byteorder = byteswap ? CDI_LITTLEENDIAN : CDI_BIGENDIAN;
      break;
    case CDI_LITTLEENDIAN:
      byteorder = byteswap ? CDI_BIGENDIAN : CDI_LITTLEENDIAN;
      break;
    /* FIXME: does not currently adjust for PDP endianness */
    case CDI_PDPENDIAN:
    default:
      Error("unhandled endianness");
    }
  return byteorder;
}

// used also in CDO
int cdiGetFiletype(const char *filename, int *byteorder)
{
  int filetype = CDI_EUFTYPE;
  int swap = 0;
  int version;
  long recpos;

  int fileID = fileOpen(filename, "r");

  if ( fileID == CDI_UNDEFID )
    {
      if ( strncmp(filename, "http:", 5) == 0 || strncmp(filename, "https:", 6) == 0 )
	return CDI_FILETYPE_NC;
      else
	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;
	  if ( CDI_Debug ) Message("found GRIB file = %s, version %d", filename, version);
	}
      else if ( version == 2 )
	{
	  filetype = CDI_FILETYPE_GRB2;
	  if ( CDI_Debug ) Message("found GRIB2 file = %s", filename);
	}
    }
  else if ( memcmp(buffer, "CDF\001", 4) == 0 )
    {
      filetype = CDI_FILETYPE_NC;
      if ( CDI_Debug ) Message("found CDF1 file = %s", filename);
    }
  else if ( memcmp(buffer, "CDF\002", 4) == 0 )
    {
      filetype = CDI_FILETYPE_NC2;
      if ( CDI_Debug ) Message("found CDF2 file = %s", filename);
    }
  else if ( memcmp(buffer, "CDF\005", 4) == 0 )
    {
      filetype = CDI_FILETYPE_NC5;
      if ( CDI_Debug ) Message("found CDF5 file = %s", filename);
    }
  else if ( memcmp(buffer+1, "HDF", 3) == 0 )
    {
      filetype = CDI_FILETYPE_NC4;
      if ( CDI_Debug ) Message("found HDF file = %s", filename);
    }
#ifdef  HAVE_LIBSERVICE
  else if ( srvCheckFiletype(fileID, &swap) )
    {
      filetype = CDI_FILETYPE_SRV;
      if ( CDI_Debug ) Message("found SRV file = %s", filename);
    }
#endif
#ifdef  HAVE_LIBEXTRA
  else if ( extCheckFiletype(fileID, &swap) )
    {
      filetype = CDI_FILETYPE_EXT;
      if ( CDI_Debug ) Message("found EXT file = %s", filename);
    }
#endif
#ifdef  HAVE_LIBIEG
  else if ( iegCheckFiletype(fileID, &swap) )
    {
      filetype = CDI_FILETYPE_IEG;
      if ( CDI_Debug ) Message("found IEG file = %s", filename);
    }
#endif
#ifdef HAVE_LIBGRIB
  else if ( gribCheckSeek(fileID, &recpos, &version) == 0 )
    {
      if ( version <= 1 )
	{
	  filetype = CDI_FILETYPE_GRB;
	  if ( CDI_Debug ) Message("found seeked GRIB file = %s", filename);
	}
      else if ( version == 2 )
	{
	  filetype = CDI_FILETYPE_GRB2;
	  if ( CDI_Debug ) Message("found seeked GRIB2 file = %s", filename);
	}
    }
#endif

  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_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)
{
  int byteswap = -1;

  switch (byteorder)
    {
    case CDI_BIGENDIAN:
    case CDI_LITTLEENDIAN:
    case CDI_PDPENDIAN:
      byteswap = (HOST_ENDIANNESS != byteorder);
      break;
    case -1:
      break;
    default:
      Error("unexpected byteorder %d query!", byteorder);
    }

  return byteswap;
}


void streamDefNumWorker(int streamID, int numWorker)
{
  if (numWorker > 0)
    {
      stream_t *streamptr = stream_to_pointer(streamID);
      streamptr->numWorker = 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->exsep;
	srvp->byteswap = getByteswap(byteorder);

	break;
      }
#endif
#ifdef  HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      {
	extrec_t *extp = (extrec_t*) streamptr->record->exsep;
	extp->byteswap = getByteswap(byteorder);

	break;
      }
#endif
#ifdef  HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      {
	iegrec_t *iegp = (iegrec_t*) streamptr->record->exsep;
	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_GRB:
    case CDI_FILETYPE_GRB2:    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 cdiInqTimeSize(int streamID)
{
  int tsID = 0, nrecs;
  stream_t *streamptr = stream_to_pointer(streamID);
  long ntsteps = streamptr->ntsteps;

  if ( ntsteps == (long)CDI_UNDEFID )
    while ( (nrecs = streamInqTimestep(streamID, tsID++)) )
      ntsteps = streamptr->ntsteps;

  return ntsteps;
}

static
int cdiInqContents(stream_t *streamptr)
{
  int status = 0;
  int filetype = streamptr->filetype;

  switch (cdiBaseFiletype(filetype))
    {
#ifdef  HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      {
        status = grbInqContents(streamptr);
	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:
      {
	if ( CDI_Debug )
	  Message("%s support not compiled in!", strfiletype(filetype));

	status = CDI_ELIBNAVAIL;
        break;
      }
    }

  if ( status == 0 )
    {
      int taxisID = vlistInqTaxis(streamptr->vlistID);
      if ( taxisID != CDI_UNDEFID )
        {
          taxis_t *taxisptr1 = &streamptr->tsteps[0].taxis;
          taxis_t *taxisptr2 = taxisPtr(taxisID);
          ptaxisCopy(taxisptr2, taxisptr1);
        }
    }

  return status;
}


int cdiStreamOpenDefaultDelegate(const char *filename, char filemode, int filetype, stream_t *streamptr, int recordBufIsToBeCreated)
{
  int fileID;
  char temp[2] = { filemode, 0 };

  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->cgribexp = cgribexNew();
#else
            streamptr->record->cgribexp = 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->exsep = 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->exsep = 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->exsep = 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:
      {
        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;
}


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;

  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;
    }
  else
    {
      streamID = streamptr->self;
      if ( streamID < 0 ) return CDI_ELIMIT;

      streamptr->filemode = filemode;
      streamptr->filename = strdupx(filename);
      streamptr->fileID   = fileID;

      if ( filemode == 'r' )
        {
          int vlistID = vlistCreate();
          if ( vlistID < 0 ) return CDI_ELIMIT;

          cdiVlistMakeInternal(vlistID);
          streamptr->vlistID = vlistID;
          /* cdiReadByteorder(streamID); */
          int status = cdiInqContents(streamptr);
          if ( status < 0 )
            {
              streamID = status;
            }
          else
            {
              vlist_t *vlistptr = vlist_to_pointer(streamptr->vlistID);
              vlistptr->ntsteps = streamptr->ntsteps;
              cdiVlistMakeImmutable(vlistID);
            }
        }
    }

  if ( streamID < 0 )
    {
      Free(streamptr->record);
      stream_delete_entry(streamptr);
    }

  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 = strdupx(filename);
  streamptr->fileID   = fileID;

  streamptr->vlistID = vlistCreate();
  cdiVlistMakeInternal(streamptr->vlistID);
  /* cdiReadByteorder(streamID); */
  int status = cdiInqContents(streamptr);
  if ( status < 0 ) return status;
  vlist_t *vlistptr = vlist_to_pointer(streamptr->vlistID);
  vlistptr->ntsteps = (int)cdiInqTimeSize(streamID);

  // Needed for NetCDF4
  for ( int varID = 0; varID < vlistptr->nvars; ++varID )
    streamptr->vars[varID].defmiss = true;

  if ( !strcmp(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;
      }
#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;
    }

  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_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->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->tsteps            = NULL;
  streamptr->tstepsTableSize   = 0;
  streamptr->tstepsNextID      = 0;
  streamptr->vlistID           = CDI_UNDEFID;
  streamptr->globalatts        = 0;
  streamptr->localatts         = 0;
  streamptr->unreduced         = cdiDataUnreduced;
  streamptr->sortname          = cdiSortName > 0;
  streamptr->sortparam         = cdiSortParam > 0;
  streamptr->have_missval      = cdiHaveMissval;
  streamptr->comptype          = CDI_COMPRESS_NONE;
  streamptr->complevel         = 0;

  basetimeInit(&streamptr->basetime);

#ifdef HAVE_LIBNETCDF
  streamptr->nc_complex_float_id  = CDI_UNDEFID;
  streamptr->nc_complex_double_id = CDI_UNDEFID;

  for ( int i = 0; i < MAX_ZAXES_PS; i++ ) streamptr->zaxisID[i]  = CDI_UNDEFID;
  for ( int i = 0; i < MAX_ZAXES_PS; i++ ) streamptr->nczvarID[i] = CDI_UNDEFID;

  for ( int i = 0; i < MAX_GRIDS_PS; i++ )
    {
      streamptr->ncgrid[i].gridID = CDI_UNDEFID;
      for (size_t j = 0; j < CDF_SIZE_ncIDs; ++j)
        streamptr->ncgrid[i].ncIDs[j] = CDI_UNDEFID;
    }

  streamptr->vct.ilev          = 0;
  streamptr->vct.mlev          = 0;
  streamptr->vct.ilevID        = CDI_UNDEFID;
  streamptr->vct.mlevID        = CDI_UNDEFID;
#endif

  streamptr->gribContainers    = NULL;

  streamptr->numWorker         = 0;
  streamptr->nextRecID         = 0;
  streamptr->cachedTsID        = -1;
  streamptr->jobs              = NULL;
  streamptr->jobManager        = NULL;
}

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 ( fileID == CDI_UNDEFID )
    Warning("File %s not open!", streamptr->filename);
  else
    switch (cdiBaseFiletype(filetype))
      {
#if defined(HAVE_LIBGRIB) && (defined(HAVE_LIBCGRIBEX) || defined(HAVE_LIBGRIB_API))
      case CDI_FILETYPE_GRB:
        {
          gribClose(fileID);
          if (recordBufIsToBeDeleted) gribContainersDelete(streamptr);
#ifdef  HAVE_LIBCGRIBEX
          if (recordBufIsToBeDeleted) cgribexDelete(streamptr->record->cgribexp);
#endif
          break;
        }
      case 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->exsep);
          break;
        }
#endif
#ifdef  HAVE_LIBEXTRA
      case CDI_FILETYPE_EXT:
        {
          fileClose(fileID);
          if (recordBufIsToBeDeleted) extDelete(streamptr->record->exsep);
          break;
        }
#endif
#ifdef  HAVE_LIBIEG
      case CDI_FILETYPE_IEG:
        {
          fileClose(fileID);
          if (recordBufIsToBeDeleted) iegDelete(streamptr->record->exsep);
          break;
        }
#endif
#ifdef  HAVE_LIBNETCDF
      case CDI_FILETYPE_NETCDF:
        {
          cdfClose(fileID);
          if (streamptr->ntsteps == 0)
            {
              if ( streamptr->tsteps[0].records )
                {
                  Free(streamptr->tsteps[0].records);
                  streamptr->tsteps[0].records = NULL;
                }
              if ( streamptr->tsteps[0].recIDs )
                {
                  Free(streamptr->tsteps[0].recIDs);
                  streamptr->tsteps[0].recIDs = NULL;
                }
            }
          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;
}


/*
@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);

  if ( CDI_Debug )
    Message("streamID = %d filename = %s", streamID, streamptr->filename);

  const int vlistID  = streamptr->vlistID;

  void (*streamCloseDelegate)(stream_t *streamptr, int recordBufIsToBeDeleted)
    = (void (*)(stream_t *, int))
    namespaceSwitchGet(NSSWITCH_STREAM_CLOSE_BACKEND).func;

  if ( streamptr->filetype != -1 ) streamCloseDelegate(streamptr, 1);

  if ( streamptr->record )
    {
      if ( streamptr->record->buffer )
        Free(streamptr->record->buffer);

      Free(streamptr->record);
    }

  streamptr->filetype = 0;
  if ( streamptr->filename ) Free(streamptr->filename);

  for ( int index = 0; index < streamptr->nvars; index++ )
    {
      sleveltable_t *pslev = streamptr->vars[index].recordTable;
      const 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);
    }
  Free(streamptr->vars);
  streamptr->vars = NULL;

  for ( int index = 0; index < streamptr->ntsteps; ++index )
    {
      if ( streamptr->tsteps[index].records ) Free(streamptr->tsteps[index].records);
      if ( streamptr->tsteps[index].recIDs  ) Free(streamptr->tsteps[index].recIDs);
      taxisDestroyKernel(&streamptr->tsteps[index].taxis);
    }

  if ( streamptr->tsteps ) Free(streamptr->tsteps);

  if ( streamptr->basetime.timevar_cache ) Free(streamptr->basetime.timevar_cache);

  if ( vlistID != -1 )
    {
      if ( streamptr->filemode != 'w' && vlistInqTaxis(vlistID) != -1 )
        taxisDestroy(vlistInqTaxis(vlistID));

      cdiVlistDestroy_(vlistID);
    }

  if (streamptr->jobs) free(streamptr->jobs);
  if (streamptr->jobManager) AsyncWorker_finalize(streamptr->jobManager);

  stream_delete_entry(streamptr);
}

static
void stream_delete_entry(stream_t *streamptr)
{
  xassert ( streamptr );

  int idx = streamptr->self;
  Free(streamptr);
  reshRemove ( idx, &streamOps );

  if ( CDI_Debug )
    Message("Removed idx %d from stream list", idx);
}


void cdiStreamSync_(stream_t *streamptr)
{
  int fileID   = streamptr->fileID;
  int filetype = streamptr->filetype;
  int vlistID  = streamptr->vlistID;
  int nvars    = 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 ( nvars == 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;
  int time_is_varying = vlistHasTime(vlistID);
  int taxisID = vlistInqTaxis(vlistID) ;

  /* moved to cdiStreamSetupVlist
  int taxisID = time_is_varying ? vlistInqTaxis(vlistID) : CDI_UNDEFID;
  if ( time_is_varying )
    {
      if ( taxisID == CDI_UNDEFID )
        {
          Warning("taxisID undefined for fileID = %d! Using absolute time axis.", streamptr->self);
          taxisID = taxisCreate(TAXIS_ABSOLUTE);
          vlistDefTaxis(vlistID, taxisID);
        }
    }
  */
  if ( tsID > 0 )
    {
      int newtsID = tstepsNewEntry(streamptr);
      if ( tsID != newtsID )
        Error("Internal problem: tsID = %d newtsID = %d", tsID, newtsID);
    }

  if ( time_is_varying )
    ptaxisCopy(&streamptr->tsteps[tsID].taxis, taxisPtr(taxisID));

  streamptr->curTsID = tsID;
  streamptr->ntsteps = tsID + 1;

#ifdef HAVE_LIBNETCDF
  if (cdiBaseFiletype(streamptr->filetype) == CDI_FILETYPE_NETCDF  && time_is_varying)
    {
      void (*myCdfDefTimestep)(stream_t *streamptr, int tsID)
        = (void (*)(stream_t *, int))
        namespaceSwitchGet(NSSWITCH_CDF_DEF_TIMESTEP).func;
      myCdfDefTimestep(streamptr, tsID);
    }
#endif

  cdi_create_records(streamptr, tsID);

  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);
  int (*myStreamDefTimestep_)(stream_t *streamptr, int tsID)
    = (int (*)(stream_t *, int))
    namespaceSwitchGet(NSSWITCH_STREAM_DEF_TIMESTEP_).func;
  return myStreamDefTimestep_(streamptr, tsID);
}


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;
  int taxisID;
  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;
      taxisID = vlistInqTaxis(vlistID);
      if ( taxisID == -1 )
	Error("Timestep undefined for fileID = %d", streamID);
      ptaxisCopy(taxisPtr(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);

  switch (cdiBaseFiletype(filetype))
    {
#ifdef  HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      {
        nrecs = grbInqTimestep(streamptr, tsID);
	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;
      }
    }

  taxisID = vlistInqTaxis(vlistID);
  if ( taxisID == -1 )
    Error("Timestep undefined for fileID = %d", streamID);

  ptaxisCopy(taxisPtr(taxisID), &streamptr->tsteps[tsID].taxis);

  return nrecs;
}

#if 0
void streamWriteContents(int streamID, char *cname)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  int vlistID = streamptr->vlistID;

  FILE *cnp = fopen(cname, "w");

  if ( cnp == NULL ) SysError(cname);

  fprintf(cnp, "#CDI library version %s\n"
          "#\n", cdiLibraryVersion());

  int filetype = streamptr->filetype;
  fprintf(cnp, "filename: %s\n"
          "filetype: %s\n", streamptr->filename, strfiletype(filetype));

  fputs("#\n#grids:\n", cnp);

  int ngrids = vlistNgrids(vlistID);
  for ( int i = 0; i < ngrids; i++ )
    {
      int gridID   = vlistGrid(vlistID, i);
      int gridtype = gridInqType(gridID);
      size_t xsize    = gridInqXsize(gridID);
      size_t ysize    = gridInqYsize(gridID);
      fprintf(cnp, "%4d:%4d:%4zu:%4zu\n", i+1, gridtype, xsize, ysize);
    }

  fputs("#\nvarID:code:gridID:zaxisID:tsteptype:datatype\n", cnp);

  int nvars = vlistNvars(vlistID);
  for ( int varID = 0; varID < nvars; varID++ )
    {
      int code      = vlistInqVarCode(vlistID, varID);
      int gridID    = vlistInqVarGrid(vlistID, varID);
      int zaxisID   = vlistInqVarZaxis(vlistID, varID);
      int tsteptype = vlistInqVarTsteptype(vlistID, varID);
      int datatype  = vlistInqVarDatatype(vlistID, varID);
      fprintf(cnp, "%4d:%4d:%4d:%4d:%4d:%4d:\n",
	      varID+1, code, gridID, zaxisID, tsteptype, datatype);
    }

  fputs("#\ntsID:nrecs:date:time\n", cnp);

  int tsID = 0;
  while (1)
    {
      int nrecs      = streamptr->tsteps[tsID].nallrecs;
      int date       = streamptr->tsteps[tsID].taxis.vdate;
      int time       = streamptr->tsteps[tsID].taxis.vtime;
      off_t position = streamptr->tsteps[tsID].position;

      fprintf(cnp, "%4d:%4d:%4d:%4d:%ld\n",
	      tsID, nrecs, date, time, (long) position);

      if ( streamptr->tsteps[tsID].next )
	tsID++;
      else
	break;
    }

  fputs("#\ntsID:recID:varID:levID:size:pos\n", cnp);

  tsID = 0;
  while (1)
    {
      int nrecs = streamptr->tsteps[tsID].nallrecs;
      for ( int recID = 0; recID < nrecs; recID++ )
	{
	  int varID   = streamptr->tsteps[tsID].records[recID].varID;
	  int levelID = streamptr->tsteps[tsID].records[recID].levelID;
	  off_t recpos = streamptr->tsteps[tsID].records[recID].position;
	  long recsize = (long)streamptr->tsteps[tsID].records[recID].size;
	  fprintf(cnp, "%4d:%4d:%4d:%4d:%4ld:%ld\n",
		  tsID, recID, varID, levelID, recsize, (long) recpos);
	}

      if ( streamptr->tsteps[tsID].next )
	tsID++;
      else
	break;
    }

  fclose(cnp);
}
#endif

// This function is used in CDO!
size_t 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 )
    {
      int vlistCopy = vlistDuplicate(vlistID);
      cdiVlistMakeInternal(vlistCopy);
      cdiVlistMakeImmutable(vlistID);
      cdiStreamSetupVlist(streamptr, vlistCopy);
    }
  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 *streamptr = stream_to_pointer(streamID);
  return streamptr->vlistID;
}


void streamDefCompType(int streamID, int comptype)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  if ( streamptr->comptype != comptype )
    {
      streamptr->comptype = comptype;
      reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
    }
}


void streamDefCompLevel(int streamID, int complevel)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  if ( streamptr->complevel != complevel )
    {
      streamptr->complevel = complevel;
      reshSetStatus(streamID, &streamOps, RESH_DESYNC_IN_USE);
    }
}


int streamInqCompType(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->comptype;
}


int streamInqCompLevel(int streamID)
{
  stream_t *streamptr = stream_to_pointer(streamID);
  return streamptr->complevel;
}

int streamInqFileID(int streamID)
{
  stream_t *streamptr = ( stream_t *) reshGetVal ( streamID, &streamOps );
  return streamptr->fileID;
}


void cdiDefAccesstype(int streamID, int type)
{
  stream_t *streamptr = (stream_t *)reshGetVal(streamID, &streamOps);

  if ( streamptr->accesstype == CDI_UNDEFID )
    {
      streamptr->accesstype = type;
    }
  else if ( streamptr->accesstype != type )
    Error("Changing access type from %s not allowed!",
          streamptr->accesstype == TYPE_REC ? "REC to VAR" : "VAR to REC");
}


int cdiInqAccesstype(int streamID)
{
  stream_t *streamptr = (stream_t *) reshGetVal ( streamID, &streamOps );
  return streamptr->accesstype;
}

static
int streamTxCode(void)
{
  return STREAM;
}

void cdiStreamSetupVlist(stream_t *streamptr, int vlistID)
{
  void (*myStreamSetupVlist)(stream_t *streamptr, int vlistID)
    = (void (*)(stream_t *, int)) namespaceSwitchGet(NSSWITCH_STREAM_SETUP_VLIST).func;
  myStreamSetupVlist(streamptr, vlistID);
}


void cdiStreamSetupVlist_(stream_t *streamptr, int vlistID)
{
  streamptr->vlistID = vlistID;
  int nvars = vlistNvars(vlistID);
  for ( int varID = 0; varID < nvars; 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 time_is_varying = vlistHasTime(vlistIDw);
      if ( time_is_varying )
        {
          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)
              {
                taxis_t *taxisptr = taxisPtr(taxisID);
                if ( taxisptr->rdate == -1 )
                  {
                    int vdate = taxisInqVdate(taxisID);
                    if ( vdate == 0 ) vdate = 10101;
                    taxisDefRdate(taxisID, vdate);
                  }
              }
#endif
          ptaxisCopy(&streamptr->tsteps[0].taxis, taxisPtr(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_GRB:
        case CDI_FILETYPE_GRB2:
          gribContainersNew(streamptr);
          break;
#endif
        default:
          ;
        }
    }
}


void cdiStreamGetIndexList(unsigned numIDs, int *IDs)
{
  reshGetResHListOfType(numIDs, IDs, &streamOps);
}

int streamInqNvars ( int streamID )
{
  stream_t *streamptr = (stream_t *)reshGetVal(streamID, &streamOps);
  return streamptr->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 (strcmp(s1->filename, s2->filename))
	return differ;
    }
  else if ( s2->filename )
    return differ;

  return equal;
}


void streamDestroyP ( void * streamptr )
{
  stream_t *sp = ( stream_t * ) streamptr;

  xassert ( sp );

  int id = sp->self;
  streamClose ( id );
}


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;
}

/*
 * 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, size_t nmiss)
{
  // 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);

  int filetype = streamptr->filetype;

  switch (cdiBaseFiletype(filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      {
        grb_write_var(streamptr, varID, memtype, data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        srvWriteVarDP(streamptr, varID, (double *)data);
	break;
      }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        extWriteVarDP(streamptr, varID, (double *)data);
	break;
      }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        iegWriteVarDP(streamptr, varID, (double *)data);
	break;
      }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      {
        cdf_write_var(streamptr, varID, memtype, data, nmiss);
	break;
      }
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(filetype));
	break;
      }
    }

  return status;
}

/*
@Function  streamWriteVar
@Title     Write a variable

@Prototype void streamWriteVar(int streamID, int varID, const double *data, size_t nmiss)
@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  nmiss     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, size_t nmiss)
{
  void (*myCdiStreamWriteVar_)(int streamID, int varID, int memtype, const void *data, size_t nmiss)
    = (void (*)(int, int, int, const void *, size_t))
    namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_).func;

  myCdiStreamWriteVar_(streamID, varID, MEMTYPE_DOUBLE, (const void *) data, nmiss);
}

/*
@Function  streamWriteVarF
@Title     Write a variable

@Prototype void streamWriteVarF(int streamID, int varID, const float *data, size_t nmiss)
@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  nmiss     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, size_t nmiss)
{
  int (*myCdiStreamWriteVar_)(int streamID, int varID, int memtype, const void *data, size_t nmiss)
    = (int (*)(int, int, int, const void *, size_t))
    namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_).func;

  if ( myCdiStreamWriteVar_(streamID, varID, MEMTYPE_FLOAT, (const void *) data, nmiss) )
    {
      // In case the file format does not support single precision writing,
      // we fall back to double precision writing, converting the data
      // on the fly.
      int vlistID = streamInqVlist(streamID);
      size_t elementCount = gridInqSize(vlistInqVarGrid(vlistID, varID));
      elementCount *= (size_t) zaxisInqSize(vlistInqVarZaxis(vlistID, varID));
      double *conversionBuffer = (double *) Malloc(elementCount*sizeof(*conversionBuffer));
      for ( size_t i = elementCount; i--; ) conversionBuffer[i] = (double) data[i];
      myCdiStreamWriteVar_(streamID, varID, MEMTYPE_DOUBLE, (const void *) conversionBuffer, nmiss);
      Free(conversionBuffer);
    }
}

static
int cdiStreamWriteVarSlice(int streamID, int varID, int levelID, int memtype, const void *data, size_t nmiss)
{
  // 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);

  int filetype = streamptr->filetype;

  switch (cdiBaseFiletype(filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      {
        grb_write_var_slice(streamptr, varID, levelID, memtype, data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        srvWriteVarSliceDP(streamptr, varID, levelID, (double *)data);
	break;
      }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        extWriteVarSliceDP(streamptr, varID, levelID, (double *)data);
	break;
      }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        iegWriteVarSliceDP(streamptr, varID, levelID, (double *)data);
	break;
      }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      cdf_write_var_slice(streamptr, varID, levelID, memtype, data, nmiss);
      break;
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(filetype));
	break;
      }
    }

  return status;
}

/*
@Function  streamWriteVarSlice
@Title     Write a horizontal slice of a variable

@Prototype void streamWriteVarSlice(int streamID, int varID, int levelID, const double *data, size_t nmiss)
@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  nmiss     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, size_t nmiss)
{
  cdiStreamWriteVarSlice(streamID, varID, levelID, MEMTYPE_DOUBLE, (const void *) data, nmiss);
}

/*
@Function  streamWriteVarSliceF
@Title     Write a horizontal slice of a variable

@Prototype void streamWriteVarSliceF(int streamID, int varID, int levelID, const float *data, size_t nmiss)
@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  nmiss     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, size_t nmiss)
{
  if ( cdiStreamWriteVarSlice(streamID, varID, levelID, MEMTYPE_FLOAT, (const void *) data, nmiss) )
    {
      // In case the file format does not support single precision writing,
      // we fall back to double precision writing, converting the data on the fly.
      size_t elementCount = gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
      double *conversionBuffer = (double *) Malloc(elementCount*sizeof(*conversionBuffer));
      for ( size_t i = elementCount; i--; ) conversionBuffer[i] = (double) data[i];
      streamWriteVarSlice(streamID, varID, levelID, conversionBuffer, nmiss);
      Free(conversionBuffer);
    }
}


void streamWriteVarChunk(int streamID, int varID,
                         const int rect[][2], const double *data, size_t nmiss)
{
  void (*myCdiStreamWriteVarChunk_)(int streamID, int varID, int memtype,
                                    const int rect[][2], const void *data, size_t nmiss)
    = (void (*)(int, int, int, const int [][2], const void *, size_t))
    namespaceSwitchGet(NSSWITCH_STREAM_WRITE_VAR_CHUNK_).func;
  myCdiStreamWriteVarChunk_(streamID, varID, MEMTYPE_DOUBLE, rect, data, nmiss);
}

/* single image implementation */
void cdiStreamWriteVarChunk_(int streamID, int varID, int memtype,
                             const int rect[][2], const void *data, size_t nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d varID = %d", streamID, varID);

  stream_t *streamptr = stream_to_pointer(streamID);

  // streamDefineTaxis(streamID);

  int filetype = streamptr->filetype;

  switch (cdiBaseFiletype(filetype))
    {
#if defined (HAVE_LIBGRIB)
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
#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, nmiss);
      break;
#endif
    default:
      Error("%s support not compiled in!", strfiletype(filetype));
      break;
    }
}

static
int stream_write_record(int streamID, int memtype, const void *data, size_t nmiss)
{
  // 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;

  check_parg(data);

  stream_t *streamptr = stream_to_pointer(streamID);

  switch (cdiBaseFiletype(streamptr->filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      grb_write_record(streamptr, memtype, data, nmiss);
      break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      if ( memtype == MEMTYPE_FLOAT ) return 1;
      srvWriteRecord(streamptr, (const double *)data);
      break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      if ( memtype == MEMTYPE_FLOAT ) return 1;
      extWriteRecord(streamptr, (const double *)data);
      break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      if ( memtype == MEMTYPE_FLOAT ) return 1;
      iegWriteRecord(streamptr, (const double *)data);
      break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      {
	cdf_write_record(streamptr, memtype, data, nmiss);
	break;
      }
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(streamptr->filetype));
	break;
      }
    }

  return status;
}

/*
@Function  streamWriteRecord
@Title     Write a horizontal slice of a variable

@Prototype void streamWriteRecord(int streamID, const double *data, size_t nmiss)
@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  nmiss     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 streamWriteRecord(int streamID, const double *data, size_t nmiss)
{
  stream_write_record(streamID, MEMTYPE_DOUBLE, (const void *) data, nmiss);
}


void streamWriteRecordF(int streamID, const float *data, size_t nmiss)
{
  if ( stream_write_record(streamID, MEMTYPE_FLOAT, (const void *) data, nmiss) )
    {
      // In case the file format does not support single precision writing,
      // we fall back to double precision writing, converting the data on the fly.
      stream_t *streamptr = stream_to_pointer(streamID);
      int varID = streamptr->record->varID;
      size_t elementCount = gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
      double *conversionBuffer = (double *) Malloc(elementCount*sizeof(*conversionBuffer));
      for ( size_t i = elementCount; i--; ) conversionBuffer[i] = (double) data[i];
      streamWriteRecord(streamID, conversionBuffer, nmiss);
      Free(conversionBuffer);
    }
}

#ifdef HAVE_CONFIG_H
#endif



/* the single image implementation */
static
int cdiStreamReadVar(int streamID, int varID, int memtype, void *data, size_t *nmiss)
{
  // 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(nmiss);

  stream_t *streamptr = stream_to_pointer(streamID);
  int filetype = streamptr->filetype;

  *nmiss = 0;

  switch (cdiBaseFiletype(filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      {
        grb_read_var(streamptr, varID, memtype, data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        srvReadVarDP(streamptr, varID, (double *)data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        extReadVarDP(streamptr, varID, (double *)data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        iegReadVarDP(streamptr, varID, (double *)data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      {
        cdf_read_var(streamptr, varID, memtype, data, nmiss);
	break;
      }
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(filetype));
	break;
      }
    }

  return status;
}

/*
@Function  streamReadVar
@Title     Read a variable

@Prototype void streamReadVar(int streamID, int varID, double *data, size_t *nmiss)
@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  nmiss     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, size_t *nmiss)
{
  cdiStreamReadVar(streamID, varID, MEMTYPE_DOUBLE, data, nmiss);
}

/*
@Function  streamReadVarF
@Title     Read a variable

@Prototype void streamReadVar(int streamID, int varID, float *data, size_t *nmiss)
@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  nmiss     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, size_t *nmiss)
{
  if ( cdiStreamReadVar(streamID, varID, MEMTYPE_FLOAT, data, nmiss) )
    {
      // 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 = gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
      elementCount *= (size_t) zaxisInqSize(vlistInqVarZaxis(streamInqVlist(streamID), varID));
      double *conversionBuffer = (double *) Malloc(elementCount*sizeof(*conversionBuffer));
      streamReadVar(streamID, varID, conversionBuffer, nmiss);
      for ( size_t i = elementCount; i--; ) data[i] = (float) conversionBuffer[i];
      Free(conversionBuffer);
    }
}


static
int cdiStreamReadVarSlice(int streamID, int varID, int levelID, int memtype, void *data, size_t *nmiss)
{
  // 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(nmiss);

  stream_t *streamptr = stream_to_pointer(streamID);
  int filetype = streamptr->filetype;

  *nmiss = 0;

  switch (cdiBaseFiletype(filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      {
        grb_read_var_slice(streamptr, varID, levelID, memtype, data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        srvReadVarSliceDP(streamptr, varID, levelID, (double *)data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        extReadVarSliceDP(streamptr, varID, levelID, (double *)data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      {
        if ( memtype == MEMTYPE_FLOAT ) return 1;
        iegReadVarSliceDP(streamptr, varID, levelID, (double *)data, nmiss);
	break;
      }
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      {
        cdf_read_var_slice(streamptr, varID, levelID, memtype, data, nmiss);
        break;
      }
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(filetype));
        status = 2;
	break;
      }
    }

  return status;
}

/*
@Function  streamReadVarSlice
@Title     Read a horizontal slice of a variable

@Prototype void streamReadVarSlice(int streamID, int varID, int levelID, double *data, size_t *nmiss)
@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  nmiss     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, size_t *nmiss)
{
  if ( cdiStreamReadVarSlice(streamID, varID, levelID, MEMTYPE_DOUBLE, data, nmiss) )
    {
      Warning("Unexpected error returned from cdiStreamReadVarSlice()!");
      size_t elementCount = gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
      memset(data, 0, elementCount * sizeof(*data));
    }
}

/*
@Function  streamReadVarSliceF
@Title     Read a horizontal slice of a variable

@Prototype void streamReadVarSliceF(int streamID, int varID, int levelID, float *data, size_t *nmiss)
@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  nmiss     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, size_t *nmiss)
{
  if ( cdiStreamReadVarSlice(streamID, varID, levelID, MEMTYPE_FLOAT, data, nmiss) )
    {
      // 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 = gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
      double *conversionBuffer = (double *) Malloc(elementCount*sizeof(*conversionBuffer));
      streamReadVarSlice(streamID, varID, levelID, conversionBuffer, nmiss);
      for ( size_t i = elementCount; i--; ) data[i] = (float) conversionBuffer[i];
      Free(conversionBuffer);
    }
}

static
int stream_read_record(int streamID, int memtype, void *data, size_t *nmiss)
{
  // 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;

  check_parg(data);
  check_parg(nmiss);

  stream_t *streamptr = stream_to_pointer(streamID);

  *nmiss = 0;

  switch (cdiBaseFiletype(streamptr->filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      grb_read_record(streamptr, memtype, data, nmiss);
      break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      if ( memtype == MEMTYPE_FLOAT ) return 1;
      srvReadRecord(streamptr, (double *)data, nmiss);
      break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      if ( memtype == MEMTYPE_FLOAT ) return 1;
      extReadRecord(streamptr, (double *)data, nmiss);
      break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      if ( memtype == MEMTYPE_FLOAT ) return 1;
      iegReadRecord(streamptr, (double *)data, nmiss);
      break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      cdf_read_record(streamptr, memtype, data, nmiss);
      break;
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(streamptr->filetype));
	break;
      }
    }

  return status;
}


void streamReadRecord(int streamID, double *data, size_t *nmiss)
{
  stream_read_record(streamID, MEMTYPE_DOUBLE, (void *) data, nmiss);
}


void streamReadRecordF(int streamID, float *data, size_t *nmiss)
{
  if ( stream_read_record(streamID, MEMTYPE_FLOAT, (void *) data, nmiss) )
    {
      // In case the file format does not support single precision reading,
      // we fall back to double precision reading, converting the data on the fly.
      stream_t *streamptr = stream_to_pointer(streamID);
      int tsID   = streamptr->curTsID;
      int vrecID = streamptr->tsteps[tsID].curRecID;
      int recID  = streamptr->tsteps[tsID].recIDs[vrecID];
      int varID  = streamptr->tsteps[tsID].records[recID].varID;
      int nwpv = vlistInqVarNumber(streamInqVlist(streamID), varID);
      size_t elementCount = nwpv*gridInqSize(vlistInqVarGrid(streamInqVlist(streamID), varID));
      double *conversionBuffer = (double *) Malloc(elementCount*sizeof(*conversionBuffer));
      streamReadRecord(streamID, conversionBuffer, nmiss);
      for ( size_t i = elementCount; i--; ) data[i] = (float) conversionBuffer[i];
      Free(conversionBuffer);
    }
}
#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, bool lbounds,
		 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);
int varOptGribNentries(int varID);

bool zaxisCompare(int zaxisID, int zaxistype, int nlevels, bool lbounds, const double *levels, 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:
 */
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBNETCDF

#include <ctype.h>
#include <limits.h>



#define  X_AXIS  1
#define  Y_AXIS  2
#define  Z_AXIS  3
#define  T_AXIS  4

#define  POSITIVE_UP    1
#define  POSITIVE_DOWN  2

typedef struct {
  int     dimid;
  int     ncvarid;
  int     dimtype;
  size_t  len;
  char    name[CDI_MAX_NAME];
}
ncdim_t;

#define  MAX_COORDVARS  4
#define  MAX_AUXVARS    4

typedef struct {
  int      ncid;
  int      isvar;
  bool     ignore;
  bool     isx;
  bool     isy;
  bool     isc;
  bool     islon;
  bool     islat;
  bool     islev;
  bool     istime;
  bool     warn;
  bool     calendar;
  bool     climatology;
  bool     lformulaterms;
  int      param;
  int      code;
  int      tabnum;
  int      bounds;
  int      gridID;
  int      zaxisID;
  int      timetype;
  int      gridtype;
  int      zaxistype;
  int      xdim;
  int      ydim;
  int      zdim;
  int      xvarid;
  int      yvarid;
  int      rpvarid;
  int      zvarid;
  int      cvarids[MAX_COORDVARS];
  int      tvarid;
  int      psvarid;
  int      p0varid;
  int      ncoordvars;
  int      coordvarids[MAX_COORDVARS];
  int      nauxvars;
  int      auxvarids[MAX_AUXVARS];
  int      cellarea;
  int      tableID;
  int      truncation;
  int      position;
  int      numLPE;
  bool     defmissval;
  bool     deffillval;
  int      xtype;
  int      gmapid;
  int      positive;
  int      ndims;
  int      dimids[8];
  int      dimtype[8];
  size_t   chunks[8];
  int      chunked;
  int      chunktype;
  int      natts;
  bool     ldeflate;
  bool     lszip;
  bool     lunsigned;
  bool     lvalidrange;
  int     *atts;
  size_t   vctsize;
  double  *vct;
  double   missval;
  double   fillval;
  double   addoffset;
  double   scalefactor;
  double   validrange[2];
  char     name[CDI_MAX_NAME];
  char     longname[CDI_MAX_NAME];
  char     stdname[CDI_MAX_NAME];
  char     units[CDI_MAX_NAME];
  char     extra[CDI_MAX_NAME];
  int      typeOfEnsembleForecast;
  int      numberOfForecastsInEnsemble;
  int      perturbationNumber;
}
ncvar_t;


static
void scanTimeString(const char *ptu, int64_t *rdate, int *rtime)
{
  int year = 1, month = 1, day = 1;
  int hour = 0, minute = 0, second = 0;
  int v1 = 1, v2 = 1, v3 = 1;
  char ch = ' ';

  if ( *ptu ) sscanf(ptu, "%d-%d-%d%c%d:%d:%d", &v1, &v2, &v3, &ch, &hour, &minute, &second);

  if ( v3 > 999 && v1 < 32 )
    { year = v3; month = v2; day = v1; }
  else
    { year = v1; month = v2; day = v3; }

  *rdate = cdiEncodeDate(year, month, day);
  *rtime = cdiEncodeTime(hour, minute, second);
}

static
int scanTimeUnit(const char *unitstr)
{
  const size_t len = strlen(unitstr);
  const int timeunit = get_timeunit(len, unitstr);
  if ( timeunit == -1 ) Message("Unsupported TIMEUNIT: %s!", unitstr);
  return timeunit;
}

static
void setForecastTime(const char *timestr, taxis_t *taxis)
{
  const size_t len = strlen(timestr);
  if ( len != 0 )
    scanTimeString(timestr, &taxis->fdate, &taxis->ftime);
  else
    taxis->fdate = taxis->ftime = 0;
}

static
int setBaseTime(const char *timeunits, taxis_t *taxis)
{
  int taxistype = TAXIS_ABSOLUTE;

  size_t len = strlen(timeunits);
  while ( isspace(*timeunits) && len ) { timeunits++; len--; }

  char *tu = (char *)Malloc((len+1) * sizeof(char));

  for ( size_t i = 0; i < len; i++ ) tu[i] = (char)tolower((int)timeunits[i]);
  tu[len] = 0;

  int timeunit = get_timeunit(len, tu);
  if ( timeunit == -1 )
    {
      Message("Unsupported TIMEUNIT: %s!", timeunits);
      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 ( timeunit == TUNIT_DAY )
                {
                  if ( !strStartsWith(tu+pos, "%y%m%d.%f") )
                    {
                      Warning("Unsupported format %s for TIMEUNIT day!", tu+pos);
                      timeunit = -1;
                    }
                }
              else if ( timeunit == TUNIT_MONTH )
                {
                  if ( !strStartsWith(tu+pos, "%y%m.%f") )
                    {
                      Warning("Unsupported format %s for TIMEUNIT month!", tu+pos);
                      timeunit = -1;
                    }
                }
              else if ( timeunit == TUNIT_YEAR )
                {
                  if ( !strStartsWith(tu+pos, "%y.%f") )
                    {
                      Warning("Unsupported format %s for TIMEUNIT year!", tu+pos);
                      timeunit = -1;
                    }
                }
              else
                {
                  Warning("Unsupported format for time units: %s!", tu);
                }
            }
          else if ( taxistype == TAXIS_RELATIVE )
            {
              int64_t rdate = -1;
              int rtime = -1;
              scanTimeString(tu+pos, &rdate, &rtime);
              taxis->rdate = rdate;
              taxis->rtime = rtime;

              if ( CDI_Debug ) Message("rdate = %lld  rtime = %d", rdate, rtime);
            }
        }
    }

  taxis->type = taxistype;
  taxis->unit = timeunit;

  Free(tu);

  if ( CDI_Debug ) Message("taxistype = %d  unit = %d", taxistype, timeunit);

  return 0;
}

static
bool xtypeIsText(int xtype)
{
  const bool isText = (xtype == NC_CHAR)
#ifdef HAVE_NETCDF4
    || (xtype == NC_STRING)
#endif
    ;
  return isText;
}

static
bool xtypeIsFloat(nc_type xtype)
{
  const bool isFloat = xtype == NC_FLOAT || xtype == NC_DOUBLE;
  return isFloat;
}

static
bool xtypeIsInt(nc_type xtype)
{
  const bool isInt = xtype == NC_SHORT || xtype == NC_INT || xtype == NC_BYTE
#ifdef HAVE_NETCDF4
    || xtype == NC_USHORT || xtype == NC_UINT || xtype == NC_UBYTE
#endif
    ;

  return isInt;
}

static
int cdfInqDatatype(stream_t *streamptr, int xtype, bool lunsigned)
{
  int datatype = -1;

#ifdef HAVE_NETCDF4
  if ( xtype == NC_BYTE && lunsigned ) xtype = NC_UBYTE;
#endif

  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;
#ifdef HAVE_NETCDF4
  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
    {
      if (xtype != streamptr->nc_complex_float_id && xtype != streamptr->nc_complex_double_id)
        {
          int 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)  streamptr->nc_complex_float_id = xtype;
                      else if (field_type1 == NC_DOUBLE) streamptr->nc_complex_double_id = xtype;
                    }
                }
            }
        }
      if      ( xtype == streamptr->nc_complex_float_id  )  datatype = CDI_DATATYPE_CPX32;
      else if ( xtype == streamptr->nc_complex_double_id )  datatype = CDI_DATATYPE_CPX64;
    }
#endif

  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) )
    {
      const bool lalloc = nc_attlen > attlen;
      int *pintatt = lalloc ? (int *)(Malloc(nc_attlen*sizeof(int))) : attint;
      cdf_get_att_int(fileID, ncvarid, attname, pintatt);
      if ( lalloc )
        {
          memcpy(attint, pintatt, attlen*sizeof(int));
          Free(pintatt);
        }
    }
}

static
void cdfGetAttDouble(int fileID, int ncvarid, char *attname, size_t attlen, double *attdouble)
{
  *attdouble = 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) )
    {
      const bool lalloc = nc_attlen > attlen;
      double *pdoubleatt = lalloc ? (double*)Malloc(nc_attlen*sizeof(double)) : attdouble;
      cdf_get_att_double(fileID, ncvarid, attname, pdoubleatt);
      if ( lalloc )
        {
          memcpy(attdouble, pdoubleatt, attlen*sizeof(double));
          Free(pdoubleatt);
        }
    }
}

static
bool cdfCheckAttText(int fileID, int ncvarid, const char *attname)
{
  bool status = false;
  nc_type atttype;

  const int status_nc = nc_inq_atttype(fileID, ncvarid, attname, &atttype);
  if ( status_nc == NC_NOERR && (atttype == NC_CHAR
#ifdef HAVE_NETCDF4
           || atttype == NC_STRING
#endif
           ) )
    {
      status = true;
    }

  return status;
}

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);
        }
    }
#ifdef HAVE_NETCDF4
  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);
        }
    }
#endif
}


void cdf_scale_add(size_t size, double *data, double addoffset, double scalefactor)
{
  const bool laddoffset   = IS_NOT_EQUAL(addoffset, 0);
  const bool lscalefactor = IS_NOT_EQUAL(scalefactor, 1);

  if ( laddoffset && lscalefactor )
    {
      for (size_t i = 0; i < size; ++i) data[i] = data[i] * scalefactor + addoffset;
    }
  else if (lscalefactor)
    {
      for (size_t i = 0; i < size; ++i) data[i] *= scalefactor;
    }
  else if (laddoffset)
    {
      for (size_t i = 0; i < size; ++i) data[i] += addoffset;
    }
}

static
void cdfCreateRecords(stream_t *streamptr, int tsID)
{
  if ( tsID < 0 || (tsID >= streamptr->ntsteps && tsID > 0) ) return;

  if ( streamptr->tsteps[tsID].nallrecs > 0 ) return;

  int vlistID  = streamptr->vlistID;

  tsteps_t* sourceTstep = streamptr->tsteps;
  tsteps_t* destTstep = sourceTstep + tsID;

  const int nvars = vlistNvars(vlistID);
  const int nrecs = vlistNrecs(vlistID);
  if ( nrecs <= 0 ) return;

  if ( tsID == 0 )
    {
      const int nvrecs = nrecs; /* use all records at first timestep */

      streamptr->nrecs += nrecs;

      destTstep->records    = (record_t *) Malloc((size_t)nrecs*sizeof(record_t));
      destTstep->nrecs      = nrecs;
      destTstep->nallrecs   = nrecs;
      destTstep->recordSize = nrecs;
      destTstep->curRecID   = CDI_UNDEFID;
      destTstep->recIDs     = (int *) Malloc((size_t)nvrecs*sizeof (int));;
      for ( int recID = 0; recID < nvrecs; recID++ ) destTstep->recIDs[recID] = recID;

      record_t *records = destTstep->records;

      for ( int varID = 0, recID = 0; varID < nvars; varID++ )
        {
          const int zaxisID = vlistInqVarZaxis(vlistID, varID);
          const int nlev    = zaxisInqSize(zaxisID);
          for ( int levelID = 0; levelID < nlev; levelID++ )
            {
              recordInitEntry(&records[recID]);
              records[recID].varID   = (short)varID;
              records[recID].levelID = (short)levelID;
              recID++;
            }
        }
    }
  else if ( tsID == 1 )
    {
      int nvrecs = 0;
      for ( int varID = 0; varID < nvars; varID++ )
        {
          if ( vlistInqVarTimetype(vlistID, varID) != TIME_CONSTANT )
            {
              int zaxisID = vlistInqVarZaxis(vlistID, varID);
              nvrecs += zaxisInqSize(zaxisID);
            }
        }

      streamptr->nrecs += nvrecs;

      destTstep->records    = (record_t *) Malloc((size_t)nrecs*sizeof(record_t));
      destTstep->nrecs      = nvrecs;
      destTstep->nallrecs   = nrecs;
      destTstep->recordSize = nrecs;
      destTstep->curRecID   = CDI_UNDEFID;

      memcpy(destTstep->records, sourceTstep->records, (size_t)nrecs*sizeof(record_t));

      if ( nvrecs )
        {
          destTstep->recIDs = (int *) Malloc((size_t)nvrecs * sizeof (int));
          for ( int recID = 0, vrecID = 0; recID < nrecs; recID++ )
            {
              int varID = destTstep->records[recID].varID;
              if ( vlistInqVarTimetype(vlistID, varID) != TIME_CONSTANT )
                {
                  destTstep->recIDs[vrecID++] = recID;
                }
            }
        }
    }
  else
    {
      if ( streamptr->tsteps[1].records == 0 ) cdfCreateRecords(streamptr, 1);

      const int nvrecs = streamptr->tsteps[1].nrecs;

      streamptr->nrecs += nvrecs;

      destTstep->records    = (record_t *) Malloc((size_t)nrecs*sizeof(record_t));
      destTstep->nrecs      = nvrecs;
      destTstep->nallrecs   = nrecs;
      destTstep->recordSize = nrecs;
      destTstep->curRecID   = CDI_UNDEFID;

      memcpy(destTstep->records, sourceTstep->records, (size_t)nrecs*sizeof(record_t));

      destTstep->recIDs     = (int *) Malloc((size_t)nvrecs * sizeof(int));

      memcpy(destTstep->recIDs, streamptr->tsteps[1].recIDs, (size_t)nvrecs*sizeof(int));
    }
}

static
int cdf_time_dimid(int fileID, int ndims, int nvars, ncdim_t *ncdims)
{
  char dimname[CDI_MAX_NAME];

  for ( int dimid = 0; dimid < ndims; ++dimid )
    {
      dimname[0] = 0;
      cdf_inq_dimname(fileID, ncdims[dimid].dimid, dimname);
      if ( strIsEqual(dimname, "time") || strIsEqual(dimname, "Time") ) return dimid;
    }

  for ( int varid = 0; varid < nvars; ++varid )
    {
      nc_type xtype;
      int nvdims, nvatts, dimids[9];
      cdf_inq_var(fileID, varid, NULL, &xtype, &nvdims, dimids, &nvatts);
      for ( int i = 0; i < nvdims; ++i )
        for ( int dimid = 0; dimid < ndims; ++dimid )
          if ( ncdims[dimid].dimid == dimids[i] )
            {
              dimids[i] = dimid;
              break;
            }

      if ( nvdims == 1 )
        {
          char sbuf[CDI_MAX_NAME];
          for ( int iatt = 0; iatt < nvatts; ++iatt )
            {
              sbuf[0] = 0;
              cdf_inq_attname(fileID, varid, iatt, sbuf);
              if ( strncmp(sbuf, "units", 5) == 0 )
                {
                  cdfGetAttText(fileID, varid, "units", sizeof(sbuf), sbuf);
                  strToLower(sbuf);

                  if ( is_time_units(sbuf) ) return dimids[0];
                }
            }
        }
    }

  return CDI_UNDEFID;
}

static
void init_ncdims(long ndims, ncdim_t *ncdims)
{
  for ( long ncdimid = 0; ncdimid < ndims; ncdimid++ )
    {
      ncdims[ncdimid].dimid        = CDI_UNDEFID;
      ncdims[ncdimid].ncvarid      = CDI_UNDEFID;
      ncdims[ncdimid].dimtype      = CDI_UNDEFID;
      ncdims[ncdimid].len          = 0;
      ncdims[ncdimid].name[0]      = 0;
    }
}

static
void init_ncvars(long nvars, ncvar_t *ncvars)
{
  for ( long ncvarid = 0; ncvarid < nvars; ++ncvarid )
    {
      ncvars[ncvarid].ncid            = CDI_UNDEFID;
      ncvars[ncvarid].isvar           = CDI_UNDEFID;
      ncvars[ncvarid].ignore          = false;
      ncvars[ncvarid].isx             = false;
      ncvars[ncvarid].isy             = false;
      ncvars[ncvarid].isc             = false;
      ncvars[ncvarid].islon           = false;
      ncvars[ncvarid].islat           = false;
      ncvars[ncvarid].islev           = false;
      ncvars[ncvarid].istime          = false;
      ncvars[ncvarid].warn            = false;
      ncvars[ncvarid].calendar        = false;
      ncvars[ncvarid].climatology     = false;
      ncvars[ncvarid].lformulaterms   = false;
      ncvars[ncvarid].timetype        = TIME_CONSTANT;
      ncvars[ncvarid].param           = CDI_UNDEFID;
      ncvars[ncvarid].code            = CDI_UNDEFID;
      ncvars[ncvarid].tabnum          = 0;
      ncvars[ncvarid].bounds          = CDI_UNDEFID;
      ncvars[ncvarid].gridID          = CDI_UNDEFID;
      ncvars[ncvarid].zaxisID         = CDI_UNDEFID;
      ncvars[ncvarid].gridtype        = CDI_UNDEFID;
      ncvars[ncvarid].zaxistype       = CDI_UNDEFID;
      ncvars[ncvarid].xdim            = CDI_UNDEFID;
      ncvars[ncvarid].ydim            = CDI_UNDEFID;
      ncvars[ncvarid].zdim            = CDI_UNDEFID;
      ncvars[ncvarid].xvarid          = CDI_UNDEFID;
      ncvars[ncvarid].yvarid          = CDI_UNDEFID;
      ncvars[ncvarid].rpvarid         = CDI_UNDEFID;
      ncvars[ncvarid].zvarid          = CDI_UNDEFID;
      ncvars[ncvarid].tvarid          = CDI_UNDEFID;
      ncvars[ncvarid].psvarid         = CDI_UNDEFID;
      ncvars[ncvarid].p0varid         = CDI_UNDEFID;
      ncvars[ncvarid].ncoordvars      = 0;
      for ( int i = 0; i < MAX_COORDVARS; ++i )
        {
          ncvars[ncvarid].coordvarids[i]  = CDI_UNDEFID;
          ncvars[ncvarid].cvarids[i]      = CDI_UNDEFID;
        }
      ncvars[ncvarid].nauxvars      = 0;
      for ( int i = 0; i < MAX_AUXVARS; ++i )
        ncvars[ncvarid].auxvarids[i]  = CDI_UNDEFID;
      ncvars[ncvarid].cellarea        = CDI_UNDEFID;
      ncvars[ncvarid].tableID         = CDI_UNDEFID;
      ncvars[ncvarid].xtype           = 0;
      ncvars[ncvarid].ndims           = 0;
      ncvars[ncvarid].gmapid          = CDI_UNDEFID;
      ncvars[ncvarid].vctsize         = 0;
      ncvars[ncvarid].vct             = NULL;
      ncvars[ncvarid].truncation      = 0;
      ncvars[ncvarid].position        = 0;
      ncvars[ncvarid].numLPE          = 0;
      ncvars[ncvarid].positive        = 0;
      ncvars[ncvarid].chunked         = 0;
      ncvars[ncvarid].chunktype       = CDI_UNDEFID;
      ncvars[ncvarid].defmissval      = false;
      ncvars[ncvarid].deffillval      = false;
      ncvars[ncvarid].missval         = 0;
      ncvars[ncvarid].fillval         = 0;
      ncvars[ncvarid].addoffset       = 0;
      ncvars[ncvarid].scalefactor     = 1;
      ncvars[ncvarid].natts           = 0;
      ncvars[ncvarid].atts            = NULL;
      ncvars[ncvarid].ldeflate        = false;
      ncvars[ncvarid].lszip           = false;
      ncvars[ncvarid].lunsigned       = false;
      ncvars[ncvarid].lvalidrange     = false;
      ncvars[ncvarid].validrange[0]   = VALIDMISS;
      ncvars[ncvarid].validrange[1]   = VALIDMISS;
      ncvars[ncvarid].typeOfEnsembleForecast      = -1;
      ncvars[ncvarid].numberOfForecastsInEnsemble = -1;
      ncvars[ncvarid].perturbationNumber          = -1;
      memset(ncvars[ncvarid].name, 0, CDI_MAX_NAME);
      memset(ncvars[ncvarid].longname, 0, CDI_MAX_NAME);
      memset(ncvars[ncvarid].stdname, 0, CDI_MAX_NAME);
      memset(ncvars[ncvarid].units, 0, CDI_MAX_NAME);
      memset(ncvars[ncvarid].extra, 0, CDI_MAX_NAME);
    }
}

static
void cdf_set_var(ncvar_t *ncvars, int ncvarid, short isvar)
{
  if ( ncvars[ncvarid].isvar != CDI_UNDEFID &&
       ncvars[ncvarid].isvar != isvar   &&
       ncvars[ncvarid].warn  == false )
    {
      if ( ! ncvars[ncvarid].ignore )
        Warning("Inconsistent variable definition for %s!", ncvars[ncvarid].name);

      ncvars[ncvarid].warn = true;
      isvar = FALSE;
    }

  ncvars[ncvarid].isvar = isvar;
}

static
void cdf_set_dim(ncvar_t *ncvars, int ncvarid, int dimid, int dimtype)
{
  if ( ncvars[ncvarid].dimtype[dimid] != CDI_UNDEFID &&
       ncvars[ncvarid].dimtype[dimid] != dimtype )
    {
      Warning("Inconsistent dimension definition for %s! dimid = %d;  type = %d;  newtype = %d",
              ncvars[ncvarid].name, dimid, ncvars[ncvarid].dimtype[dimid], dimtype);
    }

  ncvars[ncvarid].dimtype[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 )
        {
          if      ( strIsEqual(tagname, "ap:") ) *avarid  = dimvarid;
          else if ( strIsEqual(tagname, "a:")  ) *avarid  = dimvarid;
          else if ( strIsEqual(tagname, "b:")  ) *bvarid  = dimvarid;
          else if ( strIsEqual(tagname, "ps:") ) *psvarid = dimvarid;
          else if ( strIsEqual(tagname, "p0:") ) *p0varid = dimvarid;
        }
      else if ( !strIsEqual(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 ( strIsEqual(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].lformulaterms )
        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].isvar = FALSE;
      if ( bvarid1  != -1 ) ncvars[bvarid1].isvar = FALSE;
      if ( psvarid1 != -1 ) ncvar->psvarid = psvarid1;
      if ( p0varid1 != -1 ) ncvar->p0varid = p0varid1;

      if ( ncvar->bounds != CDI_UNDEFID && ncvars[ncvar->bounds].lformulaterms )
        {
          ncfvarid = ncvar->bounds;
          int avarid2 = -1, bvarid2 = -1, psvarid2 = -1, p0varid2 = -1;
          if ( ncvars[ncfvarid].lformulaterms )
            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].isvar = FALSE;
              ncvars[bvarid2].isvar = FALSE;

              const int ndims2 = ncvars[avarid2].ndims;
              const int dimid2 = ncvars[avarid2].dimids[0];
              const size_t dimlen2 = ncdims[dimid2].len;

              if ( (ndims2 == 2 && dimid == ncvars[avarid2].dimids[0] ) ||
                   (ndims2 == 1 && dimlen == dimlen2-1 ) )
                {
                  double px = 1;
                  if ( p0varid1 != -1 && p0varid1 == p0varid2 )
                    cdf_get_var_double(ncid, p0varid2, &px);

                  const 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
void cdf_set_cdi_attr(int ncid, int ncvarid, int attnum, int cdiID, int varID)
{
  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 ( xtypeIsInt(atttype) )
    {
      int attint;
      int *pattint = attlen > 1 ? (int*) malloc(attlen*sizeof(int)) : &attint;
      cdfGetAttInt(ncid, ncvarid, attname, attlen, pattint);
      int datatype = (atttype == NC_SHORT)  ? CDI_DATATYPE_INT16 :
                     (atttype == NC_BYTE)   ? CDI_DATATYPE_INT8 :
#ifdef HAVE_NETCDF4
                     (atttype == NC_UBYTE)  ? CDI_DATATYPE_UINT8 :
                     (atttype == NC_USHORT) ? CDI_DATATYPE_UINT16 :
                     (atttype == NC_UINT)   ? CDI_DATATYPE_UINT32 :
#endif
                     CDI_DATATYPE_INT32;
      cdiDefAttInt(cdiID, varID, attname, datatype, (int)attlen, pattint);
      if (attlen > 1) free(pattint);
    }
  else if ( xtypeIsFloat(atttype) )
    {
      double attflt;
      double *pattflt = attlen > 1 ? (double*) malloc(attlen*sizeof(double)) : &attflt;
      cdfGetAttDouble(ncid, ncvarid, attname, attlen, pattflt);
      const 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, strlen(attstring), attstring);
    }
}

static
void cdf_print_vars(const ncvar_t *ncvars, int nvars, const char *oname)
{
  char axis[7];
  static const char iaxis[] = {'t', 'z', 'y', 'x'};

  fprintf(stderr, "%s:\n", oname);

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      int ndim = 0;
      if ( ncvars[ncvarid].isvar )
        {
          axis[ndim++] = 'v';
          axis[ndim++] = ':';
          for ( int i = 0; i < ncvars[ncvarid].ndims; i++ )
            {/*
              if      ( ncvars[ncvarid].tvarid != -1 ) axis[ndim++] = iaxis[0];
              else if ( ncvars[ncvarid].zvarid != -1 ) axis[ndim++] = iaxis[1];
              else if ( ncvars[ncvarid].yvarid != -1 ) axis[ndim++] = iaxis[2];
              else if ( ncvars[ncvarid].xvarid != -1 ) axis[ndim++] = iaxis[3];
              else
             */
              if      ( ncvars[ncvarid].dimtype[i] == T_AXIS ) axis[ndim++] = iaxis[0];
              else if ( ncvars[ncvarid].dimtype[i] == Z_AXIS ) axis[ndim++] = iaxis[1];
              else if ( ncvars[ncvarid].dimtype[i] == Y_AXIS ) axis[ndim++] = iaxis[2];
              else if ( ncvars[ncvarid].dimtype[i] == X_AXIS ) axis[ndim++] = iaxis[3];
              else                                             axis[ndim++] = '?';
            }
        }
      else
        {
          axis[ndim++] = 'c';
          axis[ndim++] = ':';
          if      ( ncvars[ncvarid].istime ) axis[ndim++] = iaxis[0];
          else if ( ncvars[ncvarid].islev  ) axis[ndim++] = iaxis[1];
          else if ( ncvars[ncvarid].islat  ) axis[ndim++] = iaxis[2];
          else if ( ncvars[ncvarid].isy    ) axis[ndim++] = iaxis[2];
          else if ( ncvars[ncvarid].islon  ) axis[ndim++] = iaxis[3];
          else if ( ncvars[ncvarid].isx    ) axis[ndim++] = iaxis[3];
          else                               axis[ndim++] = '?';
        }

      axis[ndim++] = 0;

      fprintf(stderr, "%3d %3d  %-6s %s\n", ncvarid, ndim-3, axis, ncvars[ncvarid].name);
    }
}

static
void cdfScanAttrAxis(ncvar_t *ncvars, ncdim_t *ncdims, int ncvarid, const char *attstring, int nvdims, const int *dimidsp)
{
  int attlen = (int) strlen(attstring);

  if (nvdims == 0 && attlen == 1)
    {
      if (attstring[0] == 'z')
        {
          cdf_set_var(ncvars, ncvarid, FALSE);
          ncvars[ncvarid].islev = true;
          return;
        }
    }

  if (attlen != nvdims) return;

  for (int i = 0; i < attlen; ++i)
    {
      if (attstring[i] != '-' && attstring[i] != 't' && attstring[i] != 'z' && attstring[i] != 'y' && attstring[i] != 'x')
        {
          return;
        }
    }

  while (attlen--)
    {
      const int attchr = attstring[attlen];
      if (attchr == 't')
        {
          if (attlen != 0) Warning("axis attribute 't' not on first position!");
          cdf_set_dim(ncvars, ncvarid, attlen, T_AXIS);
        }
      else if (attchr == 'z')
        {
          ncvars[ncvarid].zdim = dimidsp[attlen];
          cdf_set_dim(ncvars, ncvarid, attlen, Z_AXIS);

          if (ncvars[ncvarid].ndims == 1)
            {
              cdf_set_var(ncvars, ncvarid, FALSE);
              ncdims[ncvars[ncvarid].dimids[0]].dimtype = Z_AXIS;
            }
        }
      else if (attchr == 'y')
        {
          ncvars[ncvarid].ydim = dimidsp[attlen];
          cdf_set_dim(ncvars, ncvarid, attlen, Y_AXIS);

          if ( ncvars[ncvarid].ndims == 1 )
            {
              cdf_set_var(ncvars, ncvarid, FALSE);
              ncdims[ncvars[ncvarid].dimids[0]].dimtype = Y_AXIS;
            }
        }
      else if (attchr == 'x')
        {
          ncvars[ncvarid].xdim = dimidsp[attlen];
          cdf_set_dim(ncvars, ncvarid, attlen, X_AXIS);

          if (ncvars[ncvarid].ndims == 1)
            {
              cdf_set_var(ncvars, ncvarid, FALSE);
              ncdims[ncvars[ncvarid].dimids[0]].dimtype = X_AXIS;
            }
        }
    }
}

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
void cdfScanVarAttr(int nvars, ncvar_t *ncvars, int ndims, ncdim_t *ncdims, int timedimid, int modelID, int format)
{
  int ncdimid;
  int nvdims, nvatts;
  int iatt;
  nc_type xtype, atttype;
  size_t attlen;
  char name[CDI_MAX_NAME];
  char attname[CDI_MAX_NAME];
  char attstring[8192];

  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;

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      const int ncid = ncvars[ncvarid].ncid;
      int *dimidsp = ncvars[ncvarid].dimids;

      cdf_inq_var(ncid, ncvarid, name, &xtype, &nvdims, dimidsp, &nvatts);
      for ( int i = 0; i < nvdims; ++i )
        for ( int dimid = 0; dimid < ndims; ++dimid )
          if ( ncdims[dimid].dimid == dimidsp[i] )
            {
              dimidsp[i] = dimid;
              break;
            }
      strcpy(ncvars[ncvarid].name, name);

      for ( ncdimid = 0; ncdimid < nvdims; ncdimid++ )
        ncvars[ncvarid].dimtype[ncdimid] = -1;

      ncvars[ncvarid].xtype = xtype;
      ncvars[ncvarid].ndims = nvdims;

#ifdef HAVE_NETCDF4
      if ( format == NC_FORMAT_NETCDF4_CLASSIC || format == NC_FORMAT_NETCDF4 )
        {
          int shuffle = 0, deflate = 0, deflate_level = 0;
          nc_inq_var_deflate(ncid, ncvarid, &shuffle, &deflate, &deflate_level);
          if (deflate > 0) ncvars[ncvarid].ldeflate = 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) ncvars[ncvarid].lszip = true;
#endif
          /*
          size_t cache_size, nelems;
          float preemption;
          nc_get_chunk_cache(&cache_size, &nelems, &preemption);
          printf("cache_size %lu nelems %lu preemption %g\n", cache_size, nelems, preemption);
          nc_get_var_chunk_cache(ncid, ncvarid, &cache_size, &nelems, &preemption);
          printf("varid %d cache_size %lu nelems %lu preemption %g\n", ncvarid, cache_size, nelems, preemption);
          */
          size_t chunks[nvdims];
          int storage_in;
          if ( nc_inq_var_chunking(ncid, ncvarid, &storage_in, chunks) == NC_NOERR )
            {
              if ( storage_in == NC_CHUNKED )
                {
                  ncvars[ncvarid].chunked = 1;
                  for ( int i = 0; i < nvdims; ++i ) ncvars[ncvarid].chunks[i] = chunks[i];
                  if ( CDI_Debug )
                    {
                      fprintf(stderr, "%s: chunking %d %d %d  chunks ", name, storage_in, NC_CONTIGUOUS, NC_CHUNKED);
                      for ( int i = 0; i < nvdims; ++i ) fprintf(stderr, "%zu ", chunks[i]);
                      fprintf(stderr, "\n");
                    }
                  {
                    char *buf = ncvars[ncvarid].extra;
                    size_t pos = strlen(buf);
                    static const char prefix[] = "chunks=";
                    memcpy(buf + pos, prefix, sizeof (prefix));
                    pos += sizeof (prefix) - 1;
                    for ( int i = nvdims-1; i >= 0; --i )
                      {
                        pos += (size_t)(sprintf(buf + pos, "%zu%s", chunks[i], i > 0 ? "x" : ""));
                      }
                    buf[pos] = ' '; buf[pos + 1] = 0;
                  }
                }
            }
        }
#endif

      if ( nvdims > 0 )
        {
          if ( timedimid == dimidsp[0] )
            {
              ncvars[ncvarid].timetype = TIME_VARYING;
              cdf_set_dim(ncvars, ncvarid, 0, T_AXIS);
            }
          else
            {
              for ( ncdimid = 1; ncdimid < nvdims; ncdimid++ )
                {
                  if ( timedimid == dimidsp[ncdimid] )
                    {
                      Warning("Time must be the first dimension! Unsupported array structure, skipped variable %s!", ncvars[ncvarid].name);
                      ncvars[ncvarid].isvar = FALSE;
                    }
                }
            }
        }

      if (ncvars[ncvarid].natts == 0 && nvatts > 0)
        ncvars[ncvarid].atts = (int*) Malloc((size_t)nvatts*sizeof(int));

      for ( iatt = 0; iatt < nvatts; iatt++ )
        {
          int nc_cell_id = CDI_UNDEFID;

          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);
          const bool isText = xtypeIsText(atttype);
          const bool isNumber = xtypeIsFloat(atttype) || xtypeIsInt(atttype);
          if (isText)
            {
              cdfGetAttText(ncid, ncvarid, attname, sizeof(attstring), attstring);
              attstringsize = strlen(attstring) + 1;
              if ( attstringsize > CDI_MAX_NAME ) attstringsize = CDI_MAX_NAME;
            }

          if ( isText && strIsEqual(attname, "long_name") )
            {
              memcpy(ncvars[ncvarid].longname, attstring, attstringsize);
            }
          else if ( isText && strIsEqual(attname, "standard_name") )
            {
              memcpy(ncvars[ncvarid].stdname, attstring, attstringsize);
            }
          else if ( isText && strIsEqual(attname, "units") )
            {
              memcpy(ncvars[ncvarid].units, attstring, attstringsize);
            }
          else if ( isText && strIsEqual(attname, "calendar") )
            {
              ncvars[ncvarid].calendar = true;
            }
          else if ( isText && strIsEqual(attname, "param") )
            {
	      int pnum = 0, pcat = 255, pdis = 255;
	      sscanf(attstring, "%d.%d.%d", &pnum, &pcat, &pdis);
	      ncvars[ncvarid].param = cdiEncodeParam(pnum, pcat, pdis);
              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isNumber && strIsEqual(attname, "code") )
            {
              cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvars[ncvarid].code);
              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isNumber && strIsEqual(attname, "table") )
            {
              int tablenum;
              cdfGetAttInt(ncid, ncvarid, attname, 1, &tablenum);
              if ( tablenum > 0 )
                {
                  ncvars[ncvarid].tabnum = tablenum;
                  ncvars[ncvarid].tableID = tableInq(modelID, tablenum, NULL);
                  if ( ncvars[ncvarid].tableID == CDI_UNDEFID )
                    ncvars[ncvarid].tableID = tableDef(modelID, tablenum, NULL);
                }
              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && strIsEqual(attname, "trunc_type") )
            {
              if ( strIsEqual(attstring, "Triangular") )
                ncvars[ncvarid].gridtype = GRID_SPECTRAL;
            }
          else if ( isText && (strIsEqual(attname, "grid_type") || strIsEqual(attname, "CDI_grid_type")) )
            {
              strToLower(attstring);
              cdf_set_gridtype(attstring, &ncvars[ncvarid].gridtype);
              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && strIsEqual(attname, "CDI_grid_latitudes") )
            {
              int nc_yvar_id;
              int status = nc_inq_varid(ncid, attstring, &nc_yvar_id);
              if ( status == NC_NOERR )
                {
                  ncvars[ncvarid].yvarid = nc_yvar_id;
                  cdf_set_var(ncvars, ncvars[ncvarid].yvarid, FALSE);
                }
              else
                Warning("%s - %s", nc_strerror(status), attstring);

              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && strIsEqual(attname, "CDI_grid_reduced_points") )
            {
              int nc_rpvar_id;
              int status = nc_inq_varid(ncid, attstring, &nc_rpvar_id);
              if ( status == NC_NOERR )
                {
                  ncvars[ncvarid].rpvarid = nc_rpvar_id;
                  cdf_set_var(ncvars, ncvars[ncvarid].rpvarid, FALSE);
                }
              else
                Warning("%s - %s", nc_strerror(status), attstring);

              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isNumber && strIsEqual(attname, "CDI_grid_num_LPE") )
            {
              cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvars[ncvarid].numLPE);
            }
          else if ( isText && strIsEqual(attname, "level_type") )
            {
              strToLower(attstring);
              cdf_set_zaxistype(attstring, &ncvars[ncvarid].zaxistype);
              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isNumber && strIsEqual(attname, "trunc_count") )
            {
              cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvars[ncvarid].truncation);
            }
          else if ( isNumber && strIsEqual(attname, "truncation") )
            {
              cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvars[ncvarid].truncation);
            }
          else if ( isNumber && strIsEqual(attname, "number_of_grid_in_reference") )
            {
              cdfGetAttInt(ncid, ncvarid, attname, 1, &ncvars[ncvarid].position);
            }
          else if ( isNumber && strIsEqual(attname, "add_offset") )
            {
	      cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvars[ncvarid].addoffset);
	      /*
		if ( atttype != NC_BYTE && atttype != NC_SHORT && atttype != NC_INT )
		if ( ncvars[ncvarid].addoffset != 0 )
		Warning("attribute add_offset not supported for atttype %d", atttype);
	      */
	      /* (also used for lon/lat) cdf_set_var(ncvars, ncvarid, TRUE); */
            }
          else if ( isNumber && strIsEqual(attname, "scale_factor") )
            {
	      cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvars[ncvarid].scalefactor);
	      /*
		if ( atttype != NC_BYTE && atttype != NC_SHORT && atttype != NC_INT )
		if ( ncvars[ncvarid].scalefactor != 1 )
		Warning("attribute scale_factor not supported for atttype %d", atttype);
	      */
	      /* (also used for lon/lat) cdf_set_var(ncvars, ncvarid, TRUE); */
            }
          else if ( isText && strIsEqual(attname, "climatology") )
            {
              int ncboundsid;
              int status = nc_inq_varid(ncid, attstring, &ncboundsid);
              if ( status == NC_NOERR )
                {
                  ncvars[ncvarid].climatology = true;
                  ncvars[ncvarid].bounds = ncboundsid;
                  cdf_set_var(ncvars, ncvars[ncvarid].bounds, FALSE);
                  cdf_set_var(ncvars, ncvarid, FALSE);
                }
              else
                Warning("%s - %s", nc_strerror(status), attstring);
            }
          else if ( isText && strIsEqual(attname, "bounds") )
            {
              int ncboundsid;
              int status = nc_inq_varid(ncid, attstring, &ncboundsid);
              if ( status == NC_NOERR )
                {
                  ncvars[ncvarid].bounds = ncboundsid;
                  cdf_set_var(ncvars, ncvars[ncvarid].bounds, FALSE);
                  cdf_set_var(ncvars, ncvarid, FALSE);
                }
              else
                Warning("%s - %s", nc_strerror(status), attstring);
            }
          else if ( isText &&  strIsEqual(attname, "formula_terms") )
            {
              ncvars[ncvarid].lformulaterms = true;
            }
          else if ( isText && strIsEqual(attname, "cell_measures") && (nc_cell_id=cdf_get_cell_varid(attstring, ncid)) != CDI_UNDEFID )
            {
              ncvars[ncvarid].cellarea = nc_cell_id;
              ncvars[nc_cell_id].isvar = FALSE;
              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && (strIsEqual(attname, "associate") || strIsEqual(attname, "coordinates")) )
            {
              bool lstop = false;
              char *pstring = attstring;

              for ( int i = 0; i < MAX_COORDVARS; i++ )
                {
                  while ( isspace((int) *pstring) ) pstring++;
                  if ( *pstring == 0 ) break;
                  char *varname = pstring;
                  while ( !isspace((int) *pstring) && *pstring != 0 ) pstring++;
                  if ( *pstring == 0 ) lstop = true;
                  if ( *(pstring-1) == ',' ) *(pstring-1) = 0;
                  *pstring++ = 0;

                  int dimvarid;
                  int status = nc_inq_varid(ncid, varname, &dimvarid);
                  if ( status == NC_NOERR )
                    {
                      cdf_set_var(ncvars, dimvarid, FALSE);
                      if ( !CDI_Ignore_Att_Coordinates )
                        {
                          ncvars[ncvarid].coordvarids[i] = dimvarid;
                          ncvars[ncvarid].ncoordvars++;
                        }
                    }
                  else
                    {
                      if ( !CDI_Ignore_Att_Coordinates ) ncvars[ncvarid].ncoordvars++;

                      int k;
                      for ( k = 0; k < nchecked_vars; ++k )
                        if ( strIsEqual(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);
                        }
                    }

                  if ( lstop ) break;
                }

              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && strIsEqual(attname, "auxiliary_variable") )
            {
              bool lstop = false;
              char *pstring = attstring;

              for ( int i = 0; i < MAX_AUXVARS; i++ )
                {
                  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_inq_varid(ncid, varname, &dimvarid);
                  if ( status == NC_NOERR )
                    {
                      cdf_set_var(ncvars, dimvarid, FALSE);
                      //  if ( !CDI_Ignore_Att_Coordinates )
                        {
                          ncvars[ncvarid].auxvarids[i] = dimvarid;
                          ncvars[ncvarid].nauxvars++;
                        }
                    }
                  else
                    Warning("%s - %s", nc_strerror(status), varname);

                  if ( lstop ) break;
                }

              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && strIsEqual(attname, "grid_mapping") )
            {
              int nc_gmap_id;
              int status = nc_inq_varid(ncid, attstring, &nc_gmap_id);
              if ( status == NC_NOERR )
                {
                  ncvars[ncvarid].gmapid = nc_gmap_id;
                  cdf_set_var(ncvars, ncvars[ncvarid].gmapid, FALSE);
                }
              else
                Warning("%s - %s", nc_strerror(status), attstring);

              cdf_set_var(ncvars, ncvarid, TRUE);
            }
          else if ( isText && strIsEqual(attname, "positive") )
            {
              strToLower(attstring);

              if      ( strIsEqual(attstring, "down") ) ncvars[ncvarid].positive = POSITIVE_DOWN;
              else if ( strIsEqual(attstring, "up")   ) ncvars[ncvarid].positive = POSITIVE_UP;

              if ( ncvars[ncvarid].ndims == 1 )
                {
                  cdf_set_var(ncvars, ncvarid, FALSE);
                  cdf_set_dim(ncvars, ncvarid, 0, Z_AXIS);
                  ncdims[ncvars[ncvarid].dimids[0]].dimtype = Z_AXIS;
                }
              else if ( ncvars[ncvarid].ndims == 0 )
                {
                  cdf_set_var(ncvars, ncvarid, FALSE);
                  ncvars[ncvarid].islev = true;
                }
              else
                {
                  ncvars[ncvarid].atts[ncvars[ncvarid].natts++] = iatt;
                }
            }
          else if ( isNumber && strIsEqual(attname, "_FillValue") )
            {
	      cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvars[ncvarid].fillval);
	      ncvars[ncvarid].deffillval = true;
	      /* cdf_set_var(ncvars, ncvarid, TRUE); */
            }
          else if ( isNumber && strIsEqual(attname, "missing_value") )
            {
	      cdfGetAttDouble(ncid, ncvarid, attname, 1, &ncvars[ncvarid].missval);
	      ncvars[ncvarid].defmissval = true;
	      /* cdf_set_var(ncvars, ncvarid, TRUE); */
            }
          else if ( isNumber && strIsEqual(attname, "valid_range") && attlen == 2 )
            {
              if ( ncvars[ncvarid].lvalidrange == false )
                {
                  bool lignore = xtypeIsFloat(atttype) != xtypeIsFloat(xtype);
                  if ( !CDI_Ignore_Valid_Range && lignore == false )
                    {
                      cdfGetAttDouble(ncid, ncvarid, attname, 2, ncvars[ncvarid].validrange);
                      ncvars[ncvarid].lvalidrange = (ncvars[ncvarid].validrange[0] <= ncvars[ncvarid].validrange[1]);
                      if ( ((int)ncvars[ncvarid].validrange[0]) == 0 && ((int)ncvars[ncvarid].validrange[1]) == 255 )
                        ncvars[ncvarid].lunsigned = true;
                      /* cdf_set_var(ncvars, ncvarid, TRUE); */
                    }
                  else if ( lignore )
                    {
                      Warning("Inconsistent data type for attribute %s:valid_range, ignored!", name);
                    }
                }
            }
          else if ( isNumber && strIsEqual(attname, "valid_min") && attlen == 1 )
            {
              bool lignore = xtypeIsFloat(atttype) != xtypeIsFloat(xtype);
              if ( !CDI_Ignore_Valid_Range && lignore == false )
                {
                  cdfGetAttDouble(ncid, ncvarid, attname, 1, &(ncvars[ncvarid].validrange)[0]);
                  ncvars[ncvarid].lvalidrange = true;
                }
              else if ( lignore )
                {
                  Warning("Inconsistent data type for attribute %s:valid_min, ignored!", name);
                }
            }
          else if ( isNumber && strIsEqual(attname, "valid_max") && attlen == 1 )
            {
              bool lignore = xtypeIsFloat(atttype) != xtypeIsFloat(xtype);
              if ( !CDI_Ignore_Valid_Range && lignore == false )
                {
                  cdfGetAttDouble(ncid, ncvarid, attname, 1, &(ncvars[ncvarid].validrange)[1]);
                  ncvars[ncvarid].lvalidrange = true;
                }
              else if ( lignore )
                {
                  Warning("Inconsistent data type for attribute %s:valid_max, ignored!", name);
                }
            }
          else if ( isText && strIsEqual(attname, "_Unsigned") )
            {
              strToLower(attstring);
              if ( strIsEqual(attstring, "true") )
                {
                  ncvars[ncvarid].lunsigned = true;
                  /*
                  ncvars[ncvarid].lvalidrange = true;
                  ncvars[ncvarid].validrange[0] = 0;
                  ncvars[ncvarid].validrange[1] = 255;
                  */
                }
	      /* cdf_set_var(ncvars, ncvarid, TRUE); */
            }
          else if ( isText && strIsEqual(attname, "cdi") )
            {
	      strToLower(attstring);

	      if ( strIsEqual(attstring, "ignore") )
		{
		  ncvars[ncvarid].ignore = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		}
            }
	  else if ( isNumber &&
                    (strIsEqual(attname, "realization")       ||
                     strIsEqual(attname, "ensemble_members")  ||
                     strIsEqual(attname, "forecast_init_type")) )
	    {
	      int temp;
	      cdfGetAttInt(ncid, ncvarid, attname, 1, &temp);

	      if      ( strIsEqual(attname, "realization") )  ncvars[ncvarid].perturbationNumber = temp;
	      else if ( strIsEqual(attname, "ensemble_members") ) ncvars[ncvarid].numberOfForecastsInEnsemble = temp;
	      else if ( strIsEqual(attname, "forecast_init_type") ) ncvars[ncvarid].typeOfEnsembleForecast = temp;

	      cdf_set_var(ncvars, ncvarid, TRUE);
	    }
	  else
	    {
	      ncvars[ncvarid].atts[ncvars[ncvarid].natts++] = iatt;
	    }
	}
    }

  for ( int i = 0; i < max_check_vars; ++i ) if ( checked_vars[i] ) Free(checked_vars[i]);
}

static
void cdfVerifyVarAttr(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++)
    {
      const int ncid = ncvars[ncvarid].ncid;
      const int *dimidsp = ncvars[ncvarid].dimids;
      const int nvdims = ncvars[ncvarid].ndims;
      const int nvatts = ncvars[ncvarid].natts;

      for (int i = 0; i < nvatts; i++)
        {
          const int attnum = ncvars[ncvarid].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);
          const bool isText = xtypeIsText(atttype);
          // const bool isNumber = xtypeIsFloat(atttype) || xtypeIsInt(atttype);

          if (isText)
            {
              cdfGetAttText(ncid, ncvarid, attname, sizeof(attstring), attstring);
              attstringsize = strlen(attstring) + 1;
              if (attstringsize > CDI_MAX_NAME) attstringsize = CDI_MAX_NAME;

              if (strIsEqual(attname, "axis"))
                {
                  cdfScanAttrAxis(ncvars, ncdims, ncvarid, strToLower(attstring), nvdims, dimidsp);
                }
              /*
              else if (strIsEqual(attname, "standard_name"))
                {
                  memcpy(ncvars[ncvarid].stdname, attstring, attstringsize);
                }
              */
            }
        }
    }
}


static
void cdf_set_dimtype(int nvars, ncvar_t *ncvars, ncdim_t *ncdims)
{
  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if ( ncvars[ncvarid].isvar == TRUE )
	{
	  int ndims = ncvars[ncvarid].ndims;
	  for ( int i = 0; i < ndims; i++ )
	    {
	      int ncdimid = ncvars[ncvarid].dimids[i];
              int dimtype = ncdims[ncdimid].dimtype;
	      if ( dimtype >= X_AXIS && dimtype <= T_AXIS )
                cdf_set_dim(ncvars, ncvarid, i, dimtype);
	    }

	  if ( CDI_Debug )
	    {
	      Message("var %d %s", ncvarid, ncvars[ncvarid].name);
	      for ( int i = 0; i < ndims; i++ )
		printf("  dim%d type=%d  ", i, ncvars[ncvarid].dimtype[i]);
	      printf("\n");
	    }
          }
      }

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if ( ncvars[ncvarid].isvar == TRUE )
	{
	  bool lxdim = false, lydim = false, lzdim = false/* , ltdim = false */;
          int lcdim = 0;
	  int ndims = ncvars[ncvarid].ndims;
	  for ( int i = 0; i < ndims; i++ )
	    {
              int dimtype = ncvars[ncvarid].dimtype[i];
              lxdim = lxdim | (dimtype == X_AXIS);
	      lydim = lydim | (dimtype == Y_AXIS);
	      lzdim = lzdim | (dimtype == Z_AXIS);
              if ( ncvars[ncvarid].cvarids[i] != CDI_UNDEFID ) lcdim++;
	      /* else if ( ncvars[ncvarid].dimtype[i] == T_AXIS ) ltdim = true; */
	    }

          int allcdims = lcdim;

          if ( !lxdim && ncvars[ncvarid].xvarid != CDI_UNDEFID )
            {
              if ( ncvars[ncvars[ncvarid].xvarid].ndims == 0 ) lxdim = true;
            }

          if ( !lydim && ncvars[ncvarid].yvarid != CDI_UNDEFID )
            {
              if ( ncvars[ncvars[ncvarid].yvarid].ndims == 0 ) lydim = true;
            }

          if ( lxdim && (lydim || ncvars[ncvarid].gridtype == GRID_UNSTRUCTURED) )
            for ( int i = ndims-1; i >= 0; i-- )
              {
                if ( ncvars[ncvarid].dimtype[i] == -1 )
                  {
                    if ( !lzdim )
                      {
                        if ( lcdim )
                          {
                            int cdimvar = ncvars[ncvarid].cvarids[allcdims-lcdim];
                            ncvars[ncvarid].zvarid = cdimvar;
                            lcdim--;
		            ncvars[cdimvar].zaxistype = ZAXIS_CHAR;
                          }
                        cdf_set_dim(ncvars, ncvarid, i, Z_AXIS);
                        lzdim = true;
                        int ncdimid = ncvars[ncvarid].dimids[i];
                        if ( ncdims[ncdimid].dimtype == CDI_UNDEFID )
                          ncdims[ncdimid].dimtype = Z_AXIS;
                      }
                  }
              }
	}
    }

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      int ndims = ncvars[ncvarid].ndims;
      for ( int i = 0; i < ndims; i++ )
        {
          if ( ncvars[ncvarid].dimtype[i] == CDI_UNDEFID )
            {
              int ncdimid = ncvars[ncvarid].dimids[i];
              if ( ncdims[ncdimid].dimtype == Z_AXIS )
                {
                  ncvars[ncvarid].islev = true;
                  cdf_set_dim(ncvars, ncvarid, i, Z_AXIS);
                }
            }
        }
    }

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if ( ncvars[ncvarid].isvar == TRUE )
	{
	  bool lxdim = false, lydim = false, lzdim = false/* , ltdim = false */;
          int lcdim = 0;
	  int ndims = ncvars[ncvarid].ndims;
	  for ( int i = 0; i < ndims; i++ )
	    {
	      if      ( ncvars[ncvarid].dimtype[i] == X_AXIS ) lxdim = true;
	      else if ( ncvars[ncvarid].dimtype[i] == Y_AXIS ) lydim = true;
	      else if ( ncvars[ncvarid].dimtype[i] == Z_AXIS ) lzdim = true;
              else if ( ncvars[ncvarid].cvarids[i] != CDI_UNDEFID ) lcdim++;
	      /* else if ( ncvars[ncvarid].dimtype[i] == T_AXIS ) ltdim = true; */
	    }

          int allcdims = lcdim;

          if ( !lxdim && ncvars[ncvarid].xvarid != CDI_UNDEFID )
            {
              if ( ncvars[ncvars[ncvarid].xvarid].ndims == 0 ) lxdim = true;
            }

          if ( !lydim && ncvars[ncvarid].yvarid != CDI_UNDEFID )
            {
              if ( ncvars[ncvars[ncvarid].yvarid].ndims == 0 ) lydim = true;
            }

          //   if ( ndims > 1 )
            for ( int i = ndims-1; i >= 0; i-- )
              {
                if ( ncvars[ncvarid].dimtype[i] == -1 )
                  {
                    if ( !lxdim )
                      {
                        if ( lcdim && ncvars[ncvarid].xvarid == CDI_UNDEFID )
                          {
                            int cdimvar = ncvars[ncvarid].cvarids[allcdims-lcdim];
                            ncvars[ncvarid].xvarid = cdimvar;
                            lcdim--;
                          }
                        cdf_set_dim(ncvars, ncvarid, i, X_AXIS);
                        lxdim = true;
                      }
                    else if ( !lydim && ncvars[ncvarid].gridtype != GRID_UNSTRUCTURED )
                      {
                        if ( lcdim && ncvars[ncvarid].yvarid == CDI_UNDEFID )
                          {
                            int cdimvar = ncvars[ncvarid].cvarids[allcdims-lcdim];
                            ncvars[ncvarid].yvarid = cdimvar;
                            lcdim--;
                          }
                        cdf_set_dim(ncvars, ncvarid, i, Y_AXIS);
                        lydim = true;
                      }
                    else if ( !lzdim )
                      {
                        if ( lcdim > 0 )
                          {
                            int cdimvar = ncvars[ncvarid].cvarids[allcdims-lcdim];
                            ncvars[ncvarid].zvarid = cdimvar;
                            lcdim--;
		            ncvars[cdimvar].zaxistype = ZAXIS_CHAR;
                          }
                        cdf_set_dim(ncvars, ncvarid, i, Z_AXIS);
                        lzdim = true;
                      }
                  }
              }
	}
    }
}

/* verify coordinate vars - first scan (dimname == varname) */
static
void verify_coordinate_vars_1(int ncid, int ndims, ncdim_t *ncdims, ncvar_t *ncvars, int timedimid, bool *lhybrid_cf)
{
  for ( int ncdimid = 0; ncdimid < ndims; ncdimid++ )
    {
      int ncvarid = ncdims[ncdimid].ncvarid;
      if ( ncvarid != -1 )
	{
	  if ( ncvars[ncvarid].dimids[0] == timedimid )
	    {
              ncvars[ncvarid].istime = true;
	      ncdims[ncdimid].dimtype = T_AXIS;
	      continue;
	    }

          if ( isHybridSigmaPressureCoordinate(ncid, ncvarid, ncvars, ncdims) )
            {
              *lhybrid_cf = true;
              continue;
            }

	  if ( ncvars[ncvarid].units[0] != 0 )
	    {
	      if ( is_lon_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].islon = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		  cdf_set_dim(ncvars, ncvarid, 0, X_AXIS);
		  ncdims[ncdimid].dimtype = X_AXIS;
		}
	      else if ( is_lat_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].islat = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		  cdf_set_dim(ncvars, ncvarid, 0, Y_AXIS);
		  ncdims[ncdimid].dimtype = Y_AXIS;
		}
	      else if ( is_x_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].isx = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		  cdf_set_dim(ncvars, ncvarid, 0, X_AXIS);
		  ncdims[ncdimid].dimtype = X_AXIS;
		}
	      else if ( is_y_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].isy = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		  cdf_set_dim(ncvars, ncvarid, 0, Y_AXIS);
		  ncdims[ncdimid].dimtype = Y_AXIS;
		}
	      else if ( is_pressure_units(ncvars[ncvarid].units) )
		{
		  ncvars[ncvarid].zaxistype = ZAXIS_PRESSURE;
		}
	      else if ( strcmp(ncvars[ncvarid].units, "level") == 0 || strcmp(ncvars[ncvarid].units, "1") == 0 )
		{
		  if      ( strcmp(ncvars[ncvarid].longname, "hybrid level at layer midpoints") == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID;
		  else if ( strncmp(ncvars[ncvarid].longname, "hybrid level at midpoints", 25) == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID;
		  else if ( strcmp(ncvars[ncvarid].longname, "hybrid level at layer interfaces") == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID_HALF;
		  else if ( strncmp(ncvars[ncvarid].longname, "hybrid level at interfaces", 26) == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID_HALF;
		  else if ( strcmp(ncvars[ncvarid].units, "level") == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_GENERIC;
		}
	      else if ( is_DBL_axis(ncvars[ncvarid].longname) )
                {
                  ncvars[ncvarid].zaxistype = ZAXIS_DEPTH_BELOW_LAND;
		}
	      else if ( is_height_units(ncvars[ncvarid].units) )
		{
		  if ( is_depth_axis(ncvars[ncvarid].stdname, ncvars[ncvarid].longname) )
		    ncvars[ncvarid].zaxistype = ZAXIS_DEPTH_BELOW_SEA;
		  else if ( is_height_axis(ncvars[ncvarid].stdname, ncvars[ncvarid].longname) )
		    ncvars[ncvarid].zaxistype = ZAXIS_HEIGHT;
		  else if ( is_altitude_axis(ncvars[ncvarid].stdname, ncvars[ncvarid].longname) )
		    ncvars[ncvarid].zaxistype = ZAXIS_ALTITUDE;
		}
	    }
          else
            {
              if ( (strcmp(ncvars[ncvarid].longname, "generalized_height") == 0 ||
                    strcmp(ncvars[ncvarid].longname, "generalized height") == 0) &&
                   strcmp(ncvars[ncvarid].stdname, "height") == 0 )
                  ncvars[ncvarid].zaxistype = ZAXIS_REFERENCE;
              else if ( strIsEqual(ncvars[ncvarid].stdname, "air_pressure") )
		  ncvars[ncvarid].zaxistype = ZAXIS_PRESSURE;
            }

	  if ( !ncvars[ncvarid].islon && ncvars[ncvarid].longname[0] != 0 &&
               !ncvars[ncvarid].islat && ncvars[ncvarid].longname[1] != 0 )
	    {
	      if ( strStartsWith(ncvars[ncvarid].longname+1, "ongitude") )
		{
		  ncvars[ncvarid].islon = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		  cdf_set_dim(ncvars, ncvarid, 0, X_AXIS);
		  ncdims[ncdimid].dimtype = X_AXIS;
		  continue;
		}
	      else if ( strStartsWith(ncvars[ncvarid].longname+1, "atitude") )
		{
		  ncvars[ncvarid].islat = true;
		  cdf_set_var(ncvars, ncvarid, FALSE);
		  cdf_set_dim(ncvars, ncvarid, 0, Y_AXIS);
		  ncdims[ncdimid].dimtype = Y_AXIS;
		  continue;
		}
	    }

	  if ( ncvars[ncvarid].zaxistype != CDI_UNDEFID )
	    {
              ncvars[ncvarid].islev = true;
	      cdf_set_var(ncvars, ncvarid, FALSE);
	      cdf_set_dim(ncvars, ncvarid, 0, Z_AXIS);
	      ncdims[ncdimid].dimtype = Z_AXIS;
	    }
	}
    }
}

/* verify coordinate vars - second scan (all other variables) */
static
void verify_coordinate_vars_2(stream_t *streamptr, int nvars, ncvar_t *ncvars)
{
  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if ( ncvars[ncvarid].isvar == 0 )
	{
	  if ( ncvars[ncvarid].units[0] != 0 )
	    {
	      if ( is_lon_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].islon = true;
		  continue;
		}
	      else if ( is_lat_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].islat = true;
		  continue;
		}
	      else if ( is_x_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].isx = true;
		  continue;
		}
	      else if ( is_y_axis(ncvars[ncvarid].units, ncvars[ncvarid].stdname) )
		{
		  ncvars[ncvarid].isy = true;
		  continue;
		}
	      else if ( ncvars[ncvarid].zaxistype == CDI_UNDEFID &&
                        (strcmp(ncvars[ncvarid].units, "level") == 0 || strcmp(ncvars[ncvarid].units, "1") == 0) )
		{
		  if      ( strcmp(ncvars[ncvarid].longname, "hybrid level at layer midpoints") == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID;
		  else if ( strncmp(ncvars[ncvarid].longname, "hybrid level at midpoints", 25) == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID;
		  else if ( strcmp(ncvars[ncvarid].longname, "hybrid level at layer interfaces") == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID_HALF;
		  else if ( strncmp(ncvars[ncvarid].longname, "hybrid level at interfaces", 26) == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_HYBRID_HALF;
		  else if ( strcmp(ncvars[ncvarid].units, "level") == 0 )
		    ncvars[ncvarid].zaxistype = ZAXIS_GENERIC;
		  continue;
		}
	      else if ( ncvars[ncvarid].zaxistype == CDI_UNDEFID && is_pressure_units(ncvars[ncvarid].units) )
		{
		  ncvars[ncvarid].zaxistype = ZAXIS_PRESSURE;
		  continue;
		}
	      else if ( is_DBL_axis(ncvars[ncvarid].longname) )
		{
                  ncvars[ncvarid].zaxistype = ZAXIS_DEPTH_BELOW_LAND;
		  continue;
		}
	      else if ( is_height_units(ncvars[ncvarid].units) )
		{
		  if ( is_depth_axis(ncvars[ncvarid].stdname, ncvars[ncvarid].longname) )
		    ncvars[ncvarid].zaxistype = ZAXIS_DEPTH_BELOW_SEA;
		  else if ( is_height_axis(ncvars[ncvarid].stdname, ncvars[ncvarid].longname) )
		    ncvars[ncvarid].zaxistype = ZAXIS_HEIGHT;
		  continue;
		}
            }
          else if ( strcmp(ncvars[ncvarid].stdname, "region") == 0  ||
                    strcmp(ncvars[ncvarid].stdname, "area_type") == 0 ||
                    cdfInqDatatype(streamptr, ncvars[ncvarid].xtype, ncvars[ncvarid].lunsigned) == CDI_DATATYPE_UINT8 )
            {
              ncvars[ncvarid].isc = true;
            }
          else if ( strIsEqual(ncvars[ncvarid].stdname, "air_pressure") )
	    ncvars[ncvarid].zaxistype = ZAXIS_PRESSURE;

	  /* not needed anymore for rotated grids */
	  if ( !ncvars[ncvarid].islon && ncvars[ncvarid].longname[0] != 0 &&
               !ncvars[ncvarid].islat && ncvars[ncvarid].longname[1] != 0 )
	    {
	      if ( strStartsWith(ncvars[ncvarid].longname+1, "ongitude") )
		{
		  ncvars[ncvarid].islon = true;
		  continue;
		}
	      else if ( strStartsWith(ncvars[ncvarid].longname+1, "atitude") )
		{
		  ncvars[ncvarid].islat = true;
		  continue;
		}
	    }
	}
    }
}

static
void grid_set_chunktype(grid_t *grid, ncvar_t *ncvar)
{
  if ( ncvar->chunked )
    {
      int ndims = ncvar->ndims;

      if ( grid->type == GRID_UNSTRUCTURED )
        {
          ncvar->chunktype = ncvar->chunks[ndims-1] == grid->size
            ? CDI_CHUNK_GRID : CDI_CHUNK_AUTO;
        }
      else
        {
          if ( grid->x.size > 1 && grid->y.size > 1 && ndims > 1 &&
               grid->x.size == ncvar->chunks[ndims-1] &&
               grid->y.size == ncvar->chunks[ndims-2] )
            ncvar->chunktype = CDI_CHUNK_GRID;
          else if ( grid->x.size > 1 && grid->x.size == ncvar->chunks[ndims-1] )
            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,
                   int ntdims, 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 ( ntdims == 1 )
        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)
{
  if ( CDI_Netcdf_Lazy_Grid_Load )
    {
      cellBoundsGet->datasetNCId = ncvar->ncid;
      cellBoundsGet->varNCId  = ncvar->bounds;
      *gridbounds = cdfPendingLoad;
    }
  else
    {
      *gridbounds = (double*) Malloc(size*sizeof(double));
      cdf_get_var_double(ncvar->ncid, ncvar->bounds, *gridbounds);
    }
}

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_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 *dimtype, 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 ( dimtype[i] == X_AXIS && nxdims < 2 )
        {
          xdimids[nxdims] = dimids[i];
          nxdims++;
        }
      else if ( dimtype[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 ( islat && (islon || xsize == 0) )
    {
      double yinc = 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 && isGaussGrid(ysize, yinc, grid->y.vals) )
        {
          *gridtype = GRID_GAUSSIAN;
          grid->np = (int)(ysize/2);
        }
      else
        *gridtype = GRID_LONLAT;
    }
  else if ( islon && !islat && ysize == 0 )
    {
      *gridtype = GRID_LONLAT;
    }
  else
    *gridtype = GRID_GENERIC;
}

static
bool cdf_read_xcoord(stream_t *streamptr, struct cdfLazyGrid *restrict lazyGrid, ncdim_t *ncdims, ncvar_t *ncvar, int xvarid, ncvar_t *axisvar,
                     size_t *xsize, size_t ysize, int ntdims, 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;
  const int datatype = cdfInqDatatype(streamptr, axisvar->xtype, axisvar->lunsigned);

  if ( (ndims - ntdims) == 2 )
    {
      /* Check size of 2 dimensional coordinate variables */
      int dimid = axisvar->dimids[ndims-2];
      const size_t dimsize1 = ncdims[dimid].len;
      dimid = axisvar->dimids[ndims-1];
      const 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 - ntdims) == 1 )
    {
      size = *xsize;
      /* Check size of 1 dimensional coordinate variables */
      const int dimid = axisvar->dimids[ndims-1];
      const size_t dimsize = ncdims[dimid].len;
      skipvar = dimsize != size;
    }
  else if ( ndims == 0 && *xsize == 0 )
    {
      size = *xsize = 1;
      skipvar = false;
    }

  if ( skipvar )
    {
      Warning("Unsupported array structure, skipped variable %s!", ncvar->name);
      ncvar->isvar = -1;
      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
    cdf_load_vals(size, ndims, xvarid, axisvar, &grid->x.vals, &lazyGrid->xValsGet, ntdims, start, count);

  cdf_copy_grid_axis_attr(axisvar, &grid->x);

  return false;
}

static
bool cdf_read_ycoord(stream_t *streamptr, struct cdfLazyGrid *restrict lazyGrid, ncdim_t *ncdims, ncvar_t *ncvar, int yvarid, ncvar_t *axisvar,
                     size_t xsize, size_t *ysize, int ntdims, 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;
  const int datatype = cdfInqDatatype(streamptr, axisvar->xtype, axisvar->lunsigned);

  if ( (ndims - ntdims) == 2 )
    {
      /* Check size of 2 dimensional coordinate variables */
      int dimid = axisvar->dimids[ndims-2];
      const size_t dimsize1 = ncdims[dimid].len;
      dimid = axisvar->dimids[ndims-1];
      const 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 - ntdims) == 1 )
    {
      if ( *ysize == 0 ) size = xsize;
      else               size = *ysize;

      const int dimid = axisvar->dimids[ndims-1];
      const size_t dimsize = ncdims[dimid].len;
      skipvar = dimsize != size;
    }
  else if ( ndims == 0 && *ysize == 0 )
    {
      size = *ysize = 1;
      skipvar = false;
    }

  if ( skipvar )
    {
      Warning("Unsupported array structure, skipped variable %s!", ncvar->name);
      ncvar->isvar = -1;
      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
    cdf_load_vals(size, ndims, yvarid, axisvar, &grid->y.vals, &lazyGrid->yValsGet, ntdims, start, count);

  cdf_copy_grid_axis_attr(axisvar, &grid->y);

  return false;
}

static
bool cdf_read_coordinates(stream_t *streamptr, struct cdfLazyGrid *restrict lazyGrid, ncvar_t *ncvar, ncvar_t *ncvars, ncdim_t *ncdims,
                          int timedimid, int xvarid, int yvarid, size_t xsize, size_t ysize, int *vdimid)
{
  grid_t *grid = &lazyGrid->base;
  size_t size = 0;

  grid->datatype = CDI_DATATYPE_FLT64;

  if ( ncvar->gridtype == GRID_TRAJECTORY )
    {
      if ( ncvar->xvarid == CDI_UNDEFID ) Error("Longitude coordinate undefined for %s!", ncvar->name);
      if ( ncvar->yvarid == CDI_UNDEFID ) Error("Latitude coordinate undefined for %s!", ncvar->name);
    }
  else
    {
      size_t start[3], count[3];
      int ntdims = 0;

      if ( xvarid != CDI_UNDEFID && yvarid != CDI_UNDEFID )
        {
          const int ndims = ncvars[xvarid].ndims;
          if ( ndims != ncvars[yvarid].ndims && !ncvars[xvarid].isc && !ncvars[yvarid].isc )
            {
              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;
                      ntdims = 1;
                      start[0] = start[1] = start[2] = 0;
                      count[0] = 1; count[1] = ysize; count[ndims-1] = xsize;
                    }
                }
              else
                {
                  Warning("Unsupported grid structure for variable %s (grid dims > 2)!", ncvar->name);
                  ncvar->xvarid = xvarid = CDI_UNDEFID;
                  ncvar->yvarid = yvarid = CDI_UNDEFID;
                }
            }
        }

      if ( xvarid != CDI_UNDEFID )
        {
          if ( (ncvars[xvarid].ndims - ntdims) > 2 )
            {
              Warning("Coordinate variable %s has to many dimensions (%d), skipped!", ncvars[xvarid].name, ncvars[xvarid].ndims);
              //ncvar->xvarid = CDI_UNDEFID;
              xvarid = CDI_UNDEFID;
            }
        }

      if ( yvarid != CDI_UNDEFID )
        {
          if ( (ncvars[yvarid].ndims - ntdims) > 2 )
            {
              Warning("Coordinate variable %s has to 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, ntdims, start, count, &islon) )
          return true;

      if ( yvarid != CDI_UNDEFID )
        if ( cdf_read_ycoord(streamptr, lazyGrid, ncdims, ncvar, yvarid, &ncvars[yvarid],
                             xsize, &ysize, ntdims, start, count, &islat) )
          return true;

      if      ( ncvar->gridtype == GRID_UNSTRUCTURED ) 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;

      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 )
    {
      const int gmapvarid = ncvar->gmapid;
      if ( gmapvarid != CDI_UNDEFID && cdfCheckAttText(ncvar->ncid, gmapvarid, "grid_mapping_name") )
        {
          char attstring[CDI_MAX_NAME];
          cdfGetAttText(ncvar->ncid, gmapvarid, "grid_mapping_name", CDI_MAX_NAME, attstring);
          if ( strIsEqual(attstring, "latitude_longitude") ) 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 )
          {
            grid->x.flag = 1;
            int bvarid = ncvars[xvarid].bounds;
            if ( bvarid != CDI_UNDEFID )
              {
                int nbdims = ncvars[bvarid].ndims;
                if ( nbdims == 2 || nbdims == 3 )
                  {
                    *vdimid = ncvars[bvarid].dimids[nbdims-1];
                    grid->nvertex = (int)ncdims[*vdimid].len;
                    cdf_load_bounds(size*(size_t)grid->nvertex, &ncvars[xvarid], &grid->x.bounds, &lazyGrid->xBoundsGet);
                  }
              }
          }
        if ( yvarid != CDI_UNDEFID && CDI_Read_Cell_Corners )
          {
            grid->y.flag = 1;
            int bvarid = 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);
                  }
              }
          }

        if ( ncvar->cellarea != CDI_UNDEFID )
          cdf_load_cellarea(size, ncvar, &grid->area, &lazyGrid->cellAreaGet);

        if (gridtype == GRID_GAUSSIAN)
          {
            if (ncvar->numLPE > 0) grid->np = ncvar->numLPE;
          }

        break;
      }
    case GRID_GAUSSIAN_REDUCED:
      {
        if (ncvar->numLPE > 0 && ncvar->rpvarid != CDI_UNDEFID)
          {
            if (ncvars[ncvar->rpvarid].ndims == 1)
              {
                grid->size = size;
                int dimid = ncvars[ncvar->rpvarid].dimids[0];
                size_t len = ncdims[dimid].len;
                grid->y.size = len;
                grid->reducedPointsSize = len;
                grid->reducedPoints = (int*) Malloc(len*sizeof(int));
                cdf_get_var_int(ncvar->ncid, ncvar->rpvarid, grid->reducedPoints);
                grid->np = ncvar->numLPE;

                int bvarid = 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);
                      }
                  }
              }
          }
        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 )
    {
      const int ndims = ncvar->ndims;
      int *dimtype = ncvar->dimtype;
      if ( ndims == 0 ||
           (ndims == 1 && dimtype[0] == T_AXIS) ||
           (ndims == 1 && dimtype[0] == Z_AXIS) ||
           (ndims == 2 && dimtype[0] == T_AXIS && dimtype[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->isvar = -1;
          return true;
        }
    }

  return false;
}

static
bool cdf_set_unstructured_par(ncvar_t *ncvar, grid_t *grid, int *xdimid, int *ydimid, int number_of_grid_used, unsigned char *uuidOfHGrid)
{
  int ndims = ncvar->ndims;
  int *dimtype = ncvar->dimtype;

  int zdimid = CDI_UNDEFID;
  int xdimidx = CDI_UNDEFID, ydimidx = CDI_UNDEFID;

  for ( int i = 0; i < ndims; i++ )
    {
      if      ( dimtype[i] == X_AXIS ) xdimidx = i;
      else if ( dimtype[i] == Y_AXIS ) ydimidx = i;
      else if ( dimtype[i] == Z_AXIS ) zdimid = ncvar->dimids[i];
    }

  if ( *xdimid != CDI_UNDEFID && *ydimid != CDI_UNDEFID && zdimid == CDI_UNDEFID )
    {
      if ( grid->x.size > grid->y.size && grid->y.size < 1000 )
        {
          dimtype[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 )
        {
          dimtype[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->isvar = -1;
      return true;
    }

  if ( number_of_grid_used != CDI_UNDEFID ) cdiDefVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDUSED, number_of_grid_used);
  if ( ncvar->position > 0 ) cdiDefVarKeyInt(&grid->keys, CDI_KEY_NUMBEROFGRIDINREFERENCE, ncvar->position);
  if ( uuidOfHGrid[0] != 0 ) cdiDefVarKeyBytes(&grid->keys, CDI_KEY_UUID, uuidOfHGrid, CDI_UUID_SIZE);

  return false;
}

static
void cdf_read_mapping_atts(int ncid, int gmapvarid, int projID, const char *varname)
{
  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);
    }
  else
    {
      Warning("Text attribute %s:grid_mapping_name missing!", varname);
    }

  int nvatts;
  cdf_inq_varnatts(ncid, gmapvarid, &nvatts);
  for ( int attnum = 0; attnum < nvatts; ++attnum )
    cdf_set_cdi_attr(ncid, gmapvarid, attnum, projID, CDI_GLOBAL);
}

static
void cdf_set_grid_to_similar_vars(ncvar_t *ncvar1, ncvar_t *ncvar2, int gridtype, int xdimid, int ydimid)
{
  if ( ncvar2->isvar == TRUE && ncvar2->gridID == CDI_UNDEFID )
    {
      int xdimid2 = CDI_UNDEFID, ydimid2 = CDI_UNDEFID, zdimid2 = CDI_UNDEFID;
      int xdimidx = CDI_UNDEFID, ydimidx = CDI_UNDEFID;
      int ndims2 = ncvar2->ndims;

      int *dimtype2 = ncvar2->dimtype;
      int *dimids2 = ncvar2->dimids;
      for ( int i = 0; i < ndims2; i++ )
        {
          if      ( dimtype2[i] == X_AXIS ) { xdimid2 = dimids2[i]; xdimidx = i; }
          else if ( dimtype2[i] == Y_AXIS ) { ydimid2 = dimids2[i]; ydimidx = i; }
          else if ( dimtype2[i] == Z_AXIS ) { zdimid2 = dimids2[i]; }
        }

      if ( ncvar2->gridtype == CDI_UNDEFID && gridtype == GRID_UNSTRUCTURED )
        {
          if ( xdimid == xdimid2 && ydimid2 != CDI_UNDEFID && zdimid2 == CDI_UNDEFID )
            {
              ncvar2->dimtype[ydimidx] = Z_AXIS;
              ydimid2 = CDI_UNDEFID;
            }

          if ( xdimid == ydimid2 && xdimid2 != CDI_UNDEFID && zdimid2 == CDI_UNDEFID )
            {
              ncvar2->dimtype[xdimidx] = Z_AXIS;
              xdimid2 = ydimid2;
              ydimid2 = CDI_UNDEFID;
            }
        }

      if ( xdimid == xdimid2 && (ydimid == ydimid2 || (xdimid == ydimid && ydimid2 == CDI_UNDEFID)) )
        {
          bool same_grid = ncvar1->xvarid == ncvar2->xvarid
                        && ncvar1->yvarid == ncvar2->yvarid
                        && ncvar1->position == ncvar2->position;
          /*
            if ( xvarid != -1 && ncvar2->xvarid != CDI_UNDEFID &&
            xvarid != ncvar2->xvarid ) same_grid = false;

            if ( yvarid != -1 && ncvar2->yvarid != CDI_UNDEFID &&
            yvarid != ncvar2->yvarid ) same_grid = false;
          */

          if ( same_grid )
            {
              if ( CDI_Debug ) Message("Same gridID %d %s", ncvar1->gridID, ncvar2->name);
              ncvar2->gridID = ncvar1->gridID;
              ncvar2->chunktype = ncvar1->chunktype;
            }
        }
    }
}

static
int cdf_define_all_grids(stream_t *streamptr, ncgrid_t *ncgrid, int vlistID, ncdim_t *ncdims, int nvars, ncvar_t *ncvars,
                         int timedimid, unsigned char *uuidOfHGrid, char *gridfile, int number_of_grid_used)
{
  for ( int ncvarid = 0; ncvarid < nvars; ++ncvarid )
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if ( ncvar->isvar && ncvar->gridID == CDI_UNDEFID )
	{
          int ndims = ncvar->ndims;
          int *dimtype = ncvar->dimtype;
          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, dimtype, &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;
		}
	    }

          const int gmapvarid = ncvar->gmapid;
          bool lproj = gmapvarid != CDI_UNDEFID;

          if ( !lproj && xaxisid != CDI_UNDEFID && xaxisid != xvarid && yaxisid != CDI_UNDEFID && yaxisid != yvarid )
            {
              lproj = true;
            }

          const bool lgrid = !(lproj && ncvar->xvarid == CDI_UNDEFID);

          const bool lunstructured = xdimid != CDI_UNDEFID && xdimid == ydimid && nydims == 0;
	  if ( (ncvar->gridtype == CDI_UNDEFID || ncvar->gridtype == GRID_GENERIC) && lunstructured )
            ncvar->gridtype = GRID_UNSTRUCTURED;

          struct cdfLazyGrid *restrict lazyGrid = NULL, *restrict lazyProj = NULL;

          {
            const int gridtype = !lgrid ? 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;

          if ( cdf_read_coordinates(streamptr, lazyGrid, ncvar, ncvars, ncdims,
                                    timedimid, xvarid, yvarid, xsize, ysize, &vdimid) )
            continue;

	  if ( number_of_grid_used != CDI_UNDEFID &&
               (grid->type == CDI_UNDEFID || grid->type == GRID_GENERIC) &&
               xdimid != CDI_UNDEFID && xsize > 999 )
            grid->type = GRID_UNSTRUCTURED;

          if ( grid->type == GRID_UNSTRUCTURED )
            if ( cdf_set_unstructured_par(ncvar, grid, &xdimid, &ydimid, number_of_grid_used, uuidOfHGrid) )
              continue;

          if ( lproj && lgrid )
            {
              int dimid;
              cdf_read_coordinates(streamptr, lazyProj, ncvar, ncvars, ncdims, timedimid,
                                   xaxisid, yaxisid, xsize, ysize, &dimid);
	    }

	  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;

          const int gridID = ncvar->gridID;

          if ( lproj && gmapvarid != CDI_UNDEFID )
            {
              const int projID = lgrid ? grid->proj : gridID;
              const int ncid = ncvars[gmapvarid].ncid;
              const int gmapvartype = ncvars[gmapvarid].xtype;
              cdiDefKeyInt(projID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARTYPE, gmapvartype);
              const char *gmapvarname = ncvars[gmapvarid].name;
              cdf_read_mapping_atts(ncid, gmapvarid, projID, gmapvarname);
              cdiDefKeyString(projID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname);
              gridVerifyProj(projID);
            }

          if ( grid->type == GRID_UNSTRUCTURED && gridfile[0] != 0 )
            gridDefReference(gridID, gridfile);

          if ( ncvar->chunked ) grid_set_chunktype(grid, ncvar);

	  const int gridindex = vlistGridIndex(vlistID, gridID);
          ncgrid[gridindex].gridID = gridID;
          if (xdimid != CDI_UNDEFID) ncgrid[gridindex].ncIDs[CDF_DIMID_X] = ncdims[xdimid].dimid;
          if (ydimid != CDI_UNDEFID) ncgrid[gridindex].ncIDs[CDF_DIMID_Y] = ncdims[ydimid].dimid;

          if (grid->type == GRID_TRAJECTORY)
            {
              ncgrid[gridindex].ncIDs[CDF_VARID_X] = xvarid;
              ncgrid[gridindex].ncIDs[CDF_VARID_Y] = yvarid;
            }

          if ( xdimid == CDI_UNDEFID && ydimid == CDI_UNDEFID && grid->size == 1 )
            gridDefHasDims(gridID, FALSE);

          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 ( 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 )
            {
              if ( CDI_Netcdf_Lazy_Grid_Load ) cdfLazyGridDestroy(lazyGrid);
              if ( grid ) { grid_free(grid); Free(grid); }
            }

          if ( lazyProj )
            {
              if ( CDI_Netcdf_Lazy_Grid_Load ) cdfLazyGridDestroy(lazyProj);
              if ( proj ) { grid_free(proj); Free(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)
{
  char *pname, *plongname, *punits, *pstdname;
  size_t vctsize = vctsize_echam;
  double *vct = vct_echam;

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if ( ncvar->isvar == TRUE && ncvar->zaxisID == CDI_UNDEFID )
	{
          bool is_scalar = false;
	  bool with_bounds = 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;
              is_scalar = true;
            }
          else
            {
              for ( int i = 0; i < ndims; i++ )
                {
                  if ( ncvar->dimtype[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);

	  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 *restrict lbounds = NULL;
	  double *restrict ubounds = 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;
	      if ( ncvars[zvarid].xtype == NC_FLOAT ) zdatatype = CDI_DATATYPE_FLT32;
	      /* don't change the name !!! */
	      /*
	      if ( (len = strlen(pname)) > 2 )
		if ( pname[len-2] == '_' && isdigit((int) pname[len-1]) )
		  pname[len-2] = 0;
	      */
#ifndef USE_MPI
              if ( zaxisType == ZAXIS_CHAR )
                {
                  if ( 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 && 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 )
                {
                  zvar = (double*) Malloc(zsize*sizeof(double));
                  cdf_get_var_double(ncvars[zvarid].ncid, zvarid, zvar);
                }

	      if ( ncvars[zvarid].bounds != CDI_UNDEFID )
		{
		  const int nbdims = ncvars[ncvars[zvarid].bounds].ndims;
		  if ( nbdims == 2 || is_scalar )
		    {
		      const size_t nlevel  = is_scalar ? 1 : ncdims[ncvars[ncvars[zvarid].bounds].dimids[0]].len;
		      const int nvertex = (int)ncdims[ncvars[ncvars[zvarid].bounds].dimids[1-is_scalar]].len;
		      if ( nlevel == zsize && nvertex == 2 )
			{
			  with_bounds = true;
			  lbounds = (double *) Malloc(4 * nlevel*sizeof(double));
			  ubounds = lbounds + nlevel;
			  double *restrict zbounds = lbounds + 2 * nlevel;
			  cdf_get_var_double(ncvars[zvarid].ncid, ncvars[zvarid].bounds, 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;
	      plongname = NULL;
	      punits    = NULL;
	      pstdname   = 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, with_bounds, lbounds, ubounds,
                                       (int)vctsize, vct, pname, plongname, punits, zdatatype, 1, 0, -1);

          const int zaxisID = ncvar->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 ( uuidOfVGrid[0] != 0 )
            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 ( is_scalar ) 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 )
            {
              const int ncid = ncvars[zvarid].ncid;
              const int nvatts = ncvars[zvarid].natts;
              for ( int iatt = 0; iatt < nvatts; ++iatt )
                {
                  const int attnum = ncvars[zvarid].atts[iatt];
                  cdf_set_cdi_attr(ncid, zvarid, attnum, zaxisID, CDI_GLOBAL);
                }
            }

          const int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
	  streamptr->zaxisID[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].isvar == TRUE && 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].dimtype[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;
                      }
                  }
	      }
	}
    }

  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, const 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++ )
    {
      const 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);
      const 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);
  const 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);
}

// 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 *varids = (int *) Malloc((size_t)nvars * sizeof (int));
  int n = 0;
  for ( int ncvarid = 0; ncvarid < num_ncvars; ncvarid++ )
    if ( ncvars[ncvarid].isvar == TRUE ) 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++ )
    {
      const int ncvarid = varids[varID1];
      const int gridID  = ncvars[ncvarid].gridID;
      const int zaxisID = ncvars[ncvarid].zaxisID;

      stream_new_var(streamptr, gridID, zaxisID, CDI_UNDEFID);
      const int varID = vlistDefVar(vlistID, gridID, zaxisID, ncvars[ncvarid].timetype);

#ifdef HAVE_NETCDF4
      if (ncvars[ncvarid].ldeflate) vlistDefVarCompType(vlistID, varID, CDI_COMPRESS_ZIP);
      if (ncvars[ncvarid].lszip) vlistDefVarCompType(vlistID, varID, CDI_COMPRESS_SZIP);

      if ( ncvars[ncvarid].chunked && ncvars[ncvarid].chunktype != CDI_UNDEFID )
        vlistDefVarChunkType(vlistID, varID, ncvars[ncvarid].chunktype);
#endif

      streamptr->vars[varID1].defmiss = false;
      streamptr->vars[varID1].ncvarid = ncvarid;

      cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, ncvars[ncvarid].name);
      if ( ncvars[ncvarid].param != CDI_UNDEFID ) vlistDefVarParam(vlistID, varID, ncvars[ncvarid].param);
      if ( ncvars[ncvarid].code != CDI_UNDEFID )  vlistDefVarCode(vlistID, varID, ncvars[ncvarid].code);
      if ( ncvars[ncvarid].code != CDI_UNDEFID )
	{
	  int param = cdiEncodeParam(ncvars[ncvarid].code, ncvars[ncvarid].tabnum, 255);
	  vlistDefVarParam(vlistID, varID, param);
	}
      if ( ncvars[ncvarid].longname[0] )  cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, ncvars[ncvarid].longname);
      if ( ncvars[ncvarid].stdname[0] )   cdiDefKeyString(vlistID, varID, CDI_KEY_STDNAME, ncvars[ncvarid].stdname);
      if ( ncvars[ncvarid].units[0] )     cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, ncvars[ncvarid].units);

      if ( ncvars[ncvarid].lvalidrange )
        vlistDefVarValidrange(vlistID, varID, ncvars[ncvarid].validrange);

      if ( IS_NOT_EQUAL(ncvars[ncvarid].addoffset, 0) )
	vlistDefVarAddoffset(vlistID, varID, ncvars[ncvarid].addoffset);
      if ( IS_NOT_EQUAL(ncvars[ncvarid].scalefactor, 1) )
	vlistDefVarScalefactor(vlistID, varID, ncvars[ncvarid].scalefactor);

      vlistDefVarDatatype(vlistID, varID, cdfInqDatatype(streamptr, ncvars[ncvarid].xtype, ncvars[ncvarid].lunsigned));

      vlistDefVarInstitut(vlistID, varID, instID);
      vlistDefVarModel(vlistID, varID, modelID);
      if ( ncvars[ncvarid].tableID != CDI_UNDEFID )
	vlistDefVarTable(vlistID, varID, ncvars[ncvarid].tableID);

      if ( ncvars[ncvarid].deffillval == false && ncvars[ncvarid].defmissval )
        {
          ncvars[ncvarid].deffillval = true;
          ncvars[ncvarid].fillval    = ncvars[ncvarid].missval;
        }

      if ( ncvars[ncvarid].deffillval )
        vlistDefVarMissval(vlistID, varID, ncvars[ncvarid].fillval);

      if ( CDI_Debug )
	Message("varID = %d  gridID = %d  zaxisID = %d", varID,
		vlistInqVarGrid(vlistID, varID), vlistInqVarZaxis(vlistID, varID));

      const int gridindex = vlistGridIndex(vlistID, gridID);
      const int xdimid = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
      const int ydimid = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_Y];

      const int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
      const int zdimid = streamptr->zaxisID[zaxisindex];

      const int ndims = ncvars[ncvarid].ndims;
      int iodim = 0;
      int ixyz = 0;
      static const int ipow10[4] = {1, 10, 100, 1000};

      if ( ncvars[ncvarid].timetype != TIME_CONSTANT ) iodim++;

      const int *dimids = ncvars[ncvarid].dimids;

      if ( gridInqType(gridID) == GRID_UNSTRUCTURED && 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++ )
            {
              const int dimid = ncdims[dimids[idim]].dimid;
              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];
            }
        }

      vlistDefVarXYZ(vlistID, varID, ixyz);
      /*
      printf("ixyz %d\n", ixyz);
      printf("ndims %d\n", ncvars[ncvarid].ndims);
      for ( int i = 0; i < ncvars[ncvarid].ndims; ++i )
        printf("dimids: %d %d\n", i, dimids[i]);
      printf("xdimid, ydimid %d %d\n", xdimid, ydimid);
      */
      if ( ncvars[ncvarid].numberOfForecastsInEnsemble != -1 )
        {
          cdiDefKeyInt(vlistID, varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, ncvars[ncvarid].typeOfEnsembleForecast);
          cdiDefKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, ncvars[ncvarid].numberOfForecastsInEnsemble);
          cdiDefKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, ncvars[ncvarid].perturbationNumber);
        }

      if ( ncvars[ncvarid].extra[0] != 0 )
        {
          vlistDefVarExtra(vlistID, varID, ncvars[ncvarid].extra);
        }
    }

  for ( int varID = 0; varID < nvars; varID++ )
    {
      const int ncvarid = varids[varID];
      const int ncid = ncvars[ncvarid].ncid;

      const int nvatts = ncvars[ncvarid].natts;
      for ( int iatt = 0; iatt < nvatts; ++iatt )
        {
          const int attnum = ncvars[ncvarid].atts[iatt];
          cdf_set_cdi_attr(ncid, ncvarid, attnum, vlistID, varID);
        }

      if ( ncvars[ncvarid].atts )
        {
          Free(ncvars[ncvarid].atts);
          ncvars[ncvarid].atts = NULL;
        }

      if ( ncvars[ncvarid].vct )
        {
          Free(ncvars[ncvarid].vct);
          ncvars[ncvarid].vct = NULL;
        }
    }

  /* release mem of not freed attributes */
  for ( int ncvarid = 0; ncvarid < num_ncvars; ncvarid++ )
    if ( ncvars[ncvarid].atts ) Free(ncvars[ncvarid].atts);

  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);
  const 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);
  const 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 cdf_scan_global_attr(int fileID, int vlistID, int ngatts, int *instID, int *modelID, bool *ucla_les, unsigned char *uuidOfHGrid, unsigned char *uuidOfVGrid, char *gridfile, int *number_of_grid_used)
{
  nc_type xtype;
  size_t attlen;
  char attname[CDI_MAX_NAME];
  char attstring[65636];

  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) )
	{
	  cdfGetAttText(fileID, NC_GLOBAL, attname, sizeof(attstring), attstring);

          size_t attstrlen = strlen(attstring);

	  if ( attlen > 0 && attstring[0] != 0 )
	    {
	      if ( strIsEqual(attname, "institution") )
		{
		  *instID = institutInq(0, 0, NULL, attstring);
		  if ( *instID == CDI_UNDEFID )
		    *instID = institutDef(0, 0, NULL, attstring);
		}
	      else if ( strIsEqual(attname, "source") )
		{
		  *modelID = modelInq(-1, 0, attstring);
		  if ( *modelID == CDI_UNDEFID )
		    *modelID = modelDef(-1, 0, attstring);
		}
	      else if ( strIsEqual(attname, "Source") )
		{
		  if ( strncmp(attstring, "UCLA-LES", 8) == 0 )
		    *ucla_les = true;
		}
	      /*
	      else if ( strIsEqual(attname, "Conventions") )
		{
		}
	      */
	      else if ( strIsEqual(attname, "_NCProperties") )
		{
		}
	      else if ( strIsEqual(attname, "CDI") )
		{
		}
	      else if ( strIsEqual(attname, "CDO") )
		{
		}
              /*
	      else if ( strIsEqual(attname, "forecast_reference_time") )
		{
                  memcpy(fcreftime, attstring, attstrlen+1);
		}
              */
	      else if ( strIsEqual(attname, "grid_file_uri") )
		{
                  memcpy(gridfile, attstring, attstrlen+1);
		}
	      else if ( strIsEqual(attname, "uuidOfHGrid") && attstrlen == 36 )
		{
                  attstring[36] = 0;
                  cdiStr2UUID(attstring, uuidOfHGrid);
                  //   printf("uuid: %d %s\n", attlen, attstring);
		}
	      else if ( strIsEqual(attname, "uuidOfVGrid") && attstrlen == 36 )
		{
                  attstring[36] = 0;
                  cdiStr2UUID(attstring, uuidOfVGrid);
		}
	      else
		{
                  if ( strIsEqual(attname, "ICON_grid_file_uri") && gridfile[0] == 0 )
                    memcpy(gridfile, attstring, attstrlen+1);

		  cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int)attstrlen, attstring);
		}
	    }
	}
      else if ( xtype == NC_SHORT || xtype == NC_INT )
	{
	  if ( strIsEqual(attname, "number_of_grid_used") )
	    {
	      (*number_of_grid_used) = CDI_UNDEFID;
	      cdfGetAttInt(fileID, NC_GLOBAL, attname, 1, 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] && strIsEqual(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 *time_has_units, bool *time_has_bounds, bool *time_climatology)
{
  int ncvarid;

  if ( timedimid == CDI_UNDEFID )
    {
      char timeunits[CDI_MAX_NAME];

      for ( ncvarid = 0; ncvarid < nvars; ncvarid++ )
        {
          ncvar_t *ncvar = &ncvars[ncvarid];
          if ( ncvar->ndims == 0 && strIsEqual(ncvar->name, "time") )
            {
              if ( ncvars[ncvarid].units[0] )
                {
                  strcpy(timeunits, ncvar->units);
                  strToLower(timeunits);

                  if ( is_time_units(timeunits) )
                    {
                      streamptr->basetime.ncvarid = ncvarid;
                      break;
                    }
                }
            }
        }
    }
  else
    {
      bool ltimevar = false;

      if ( ncdims[timedimid].ncvarid != CDI_UNDEFID )
        {
          streamptr->basetime.ncvarid = ncdims[timedimid].ncvarid;
          ltimevar = 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->isvar = FALSE;

              if ( !ltimevar )
                {
                  streamptr->basetime.ncvarid = ncvarid;
                  ltimevar = true;
                  if ( CDI_Debug ) fprintf(stderr, "timevar %s\n", ncvar->name);
                }
              else
                {
                  Warning("Found more than one time variable, skipped variable %s!", ncvar->name);
                }
            }
        }

      if ( ltimevar == 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->istime = true;
                  streamptr->basetime.ncvarid = ncvarid;
                  streamptr->basetime.lwrf    = true;
                  break;
                }
            }
        }

      // time varID
      ncvarid = streamptr->basetime.ncvarid;

      if ( ncvarid == CDI_UNDEFID ) Warning("Time variable >%s< not found!", ncdims[timedimid].name);
    }

  // time varID
  ncvarid = streamptr->basetime.ncvarid;

  if ( ncvarid != CDI_UNDEFID && streamptr->basetime.lwrf == false )
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if ( ncvar->units[0] != 0 ) *time_has_units = 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] )
                {
                  *time_has_bounds = true;
                  streamptr->basetime.ncvarboundsid = ncvar->bounds;
                  if ( ncvar->climatology ) *time_climatology = 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;

  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if ( ncvars[ncvarid].ndims == 1 )
        {
          size_t len = strlen(ncvars[ncvarid].name);
          if ( len == 4 && ncvars[ncvarid].name[0] == 'h' && ncvars[ncvarid].name[1] == 'y' )
            {
              if ( ncvars[ncvarid].name[2] == 'a' && ncvars[ncvarid].name[3] == 'i' ) // hyai
                {
                  vcta_id = ncvarid;
                  nvcth_id = ncvars[ncvarid].dimids[0];
                  ncvars[ncvarid].isvar = FALSE;
                }
              else if ( ncvars[ncvarid].name[2] == 'b' && ncvars[ncvarid].name[3] == 'i' ) //hybi
                {
                  vctb_id = ncvarid;
                  nvcth_id = ncvars[ncvarid].dimids[0];
                  ncvars[ncvarid].isvar = FALSE;
                }
              else if ( (ncvars[ncvarid].name[2] == 'a' || ncvars[ncvarid].name[2] == 'b') && ncvars[ncvarid].name[3] == 'm' )
                {
                  ncvars[ncvarid].isvar = FALSE; // hyam or hybm
                }
            }
	}
    }

  /* read VCT */
  if ( nvcth_id != CDI_UNDEFID && vcta_id != CDI_UNDEFID && vctb_id != CDI_UNDEFID )
    {
      size_t vctsize = ncdims[nvcth_id].len;
      vctsize *= 2;
      *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;
    }
}

static
void cdf_set_ucla_dimtype(int ndims, ncdim_t *ncdims, ncvar_t *ncvars)
{
  for ( int ncdimid = 0; ncdimid < ndims; ncdimid++ )
    {
      int ncvarid = ncdims[ncdimid].ncvarid;
      if ( ncvarid != -1 )
        {
          if ( ncdims[ncdimid].dimtype == CDI_UNDEFID && ncvars[ncvarid].units[0] == 'm' )
            {
              if      ( ncvars[ncvarid].name[0] == 'x' ) ncdims[ncdimid].dimtype = X_AXIS;
              else if ( ncvars[ncvarid].name[0] == 'y' ) ncdims[ncdimid].dimtype = Y_AXIS;
              else if ( ncvars[ncvarid].name[0] == 'z' ) ncdims[ncdimid].dimtype = Z_AXIS;
            }
        }
    }
}

static
int cdfCheckVars(stream_t *streamptr, int nvars, ncvar_t *ncvars, size_t ntsteps, int timedimid)
{
  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if (ncvars[ncvarid].istime && ncvars[ncvarid].ndims == 2)
        {
          ncvars[ncvarid].isvar = 0;
          continue;
        }

      if ( timedimid != CDI_UNDEFID )
	if ( ncvars[ncvarid].isvar == -1 &&
	     ncvars[ncvarid].ndims > 1   &&
	     timedimid == ncvars[ncvarid].dimids[0] )
	  cdf_set_var(ncvars, ncvarid, TRUE);

      if ( ncvars[ncvarid].isvar == -1 )
        {
          if ( ncvars[ncvarid].ndims == 0 )
            cdf_set_var(ncvars, ncvarid, nvars == 1 ? TRUE : FALSE);
          else if ( ncvars[ncvarid].ndims > 0 )
            cdf_set_var(ncvars, ncvarid, TRUE);
          else
            {
              ncvars[ncvarid].isvar = 0;
              Warning("Variable %s has an unknown type, skipped!", ncvars[ncvarid].name);
            }
        }

      if ( ncvars[ncvarid].isvar == 0 ) continue;

      if ( ncvars[ncvarid].ndims > 4 )
	{
	  ncvars[ncvarid].isvar = 0;
	  Warning("%d dimensional variables are not supported, skipped variable %s!",
                  ncvars[ncvarid].ndims, ncvars[ncvarid].name);
	  continue;
	}

      if ( ncvars[ncvarid].ndims == 4 && timedimid == CDI_UNDEFID )
	{
	  ncvars[ncvarid].isvar = 0;
	  Warning("%d dimensional variables without time dimension are not supported, skipped variable %s!",
                  ncvars[ncvarid].ndims, ncvars[ncvarid].name);
	  continue;
	}

      if ( xtypeIsText(ncvars[ncvarid].xtype) )
	{
	  ncvars[ncvarid].isvar = 0;
	  Warning("Unsupported data type (char/string), skipped variable %s!", ncvars[ncvarid].name);
	  continue;
	}

      if ( cdfInqDatatype(streamptr, ncvars[ncvarid].xtype, ncvars[ncvarid].lunsigned) == -1 )
	{
	  ncvars[ncvarid].isvar = 0;
	  Warning("Unsupported data type, skipped variable %s!", ncvars[ncvarid].name);
	  continue;
	}

      if ( timedimid != CDI_UNDEFID && ntsteps == 0 && ncvars[ncvarid].ndims > 0 )
	{
	  if ( timedimid == ncvars[ncvarid].dimids[0] )
	    {
	      ncvars[ncvarid].isvar = 0;
	      Warning("Number of time steps undefined, skipped variable %s!", ncvars[ncvarid].name);
	      continue;
	    }
	}
    }

  return timedimid;
}

static
void cdfVerifyVars(int nvars, ncvar_t *ncvars, ncdim_t *ncdims)
{
  for ( int ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      if (ncvars[ncvarid].isvar == 1 && ncvars[ncvarid].ndims > 0 )
        {
          int ndims = 0;
          for (int i = 0; i < ncvars[ncvarid].ndims; ++i)
            {
              if      (ncvars[ncvarid].dimtype[i] == T_AXIS) ndims++;
              else if (ncvars[ncvarid].dimtype[i] == Z_AXIS) ndims++;
              else if (ncvars[ncvarid].dimtype[i] == Y_AXIS) ndims++;
              else if (ncvars[ncvarid].dimtype[i] == X_AXIS) ndims++;
            }

          if ( ncvars[ncvarid].ndims != ndims )
            {
              ncvars[ncvarid].isvar = 0;
              Warning("Inconsistent number of dimensions, skipped variable %s!", ncvars[ncvarid].name);
            }

          int zdimid = -1;
          for (int i = 0; i < ncvars[ncvarid].ndims; ++i)
            {
              if (ncvars[ncvarid].dimtype[i] == Z_AXIS) zdimid = ncvars[ncvarid].dimids[i];
            }

          int zaxisID = ncvars[ncvarid].zaxisID;
          if (zaxisInqScalar(zaxisID) && zdimid != -1)
            {
              ncvars[ncvarid].isvar = 0;
              Warning("Unsupported dimension >%s<, skipped variable %s!", ncdims[zdimid].name, ncvars[ncvarid].name);
            }
        }
    }
}


int cdfInqContents(stream_t *streamptr)
{
  int ndims, nvars, ngatts, unlimdimid;
  int ncvarid;
  bool time_has_units = false;
  bool time_has_bounds = false;
  bool time_climatology = false;
  int leadtime_id = CDI_UNDEFID;
  int instID  = CDI_UNDEFID;
  int modelID = CDI_UNDEFID;
  int calendar = CDI_UNDEFID;
  int format = 0;
  bool ucla_les = false;
  char gridfile[8912];
  char fcreftime[CDI_MAX_NAME];
  int number_of_grid_used = CDI_UNDEFID;

  unsigned char uuidOfHGrid[CDI_UUID_SIZE];
  unsigned char uuidOfVGrid[CDI_UUID_SIZE];
  memset(uuidOfHGrid, 0, CDI_UUID_SIZE);
  memset(uuidOfVGrid, 0, CDI_UUID_SIZE);
  gridfile[0] = 0;
  fcreftime[0] = 0;

  int vlistID = streamptr->vlistID;
  int fileID  = streamptr->fileID;

  if ( CDI_Debug ) Message("streamID = %d, fileID = %d", streamptr->self, fileID);

#ifdef HAVE_NETCDF4
  nc_inq_format(fileID, &format);
#endif

  cdf_inq(fileID, &ndims , &nvars, &ngatts, &unlimdimid);

  if ( CDI_Debug )
    Message("root: ndims %d, nvars %d, ngatts %d", ndims, nvars, ngatts);
  /*
  if ( ndims == 0 )
    {
      Warning("No dimensions found!");
      return CDI_EUFSTRUCT;
    }
  */
  // alloc ncdims
  ncdim_t *ncdims = ndims ? (ncdim_t *) Malloc((size_t)ndims * sizeof(ncdim_t)) : NULL;
  init_ncdims(ndims, ncdims);

  int ncdimid = 0;
  size_t dimlen;
  for ( int dimid = 0; dimid < NC_MAX_DIMS; ++dimid )
    {
      int status = nc_inq_dimlen(fileID, dimid, &dimlen);
      if ( status == NC_NOERR )
        {
          ncdims[ncdimid++].dimid = dimid;
          if ( ncdimid == ndims ) break;
        }
    }

#ifdef  HAVE_NETCDF4
  if ( format == NC_FORMAT_NETCDF4 )
    {
      int numgrps = 0;
      int ncids[NC_MAX_VARS];
      char gname[CDI_MAX_NAME];
      int gndims, gnvars, gngatts, gunlimdimid;
      nc_inq_grps(fileID, &numgrps, ncids);
      for ( int i = 0; i < numgrps; ++i )
        {
          int ncid = ncids[i];
          nc_inq_grpname(ncid, gname);
          cdf_inq(ncid, &gndims , &gnvars, &gngatts, &gunlimdimid);

          if ( CDI_Debug )
            Message("%s: ndims %d, nvars %d, ngatts %d", gname, gndims, gnvars, gngatts);

          if ( gndims == 0 )
            {
            }
        }
      if ( numgrps ) Warning("NetCDF4 groups not supported! Found %d root group%s.", numgrps, numgrps>1?"s":"");
    }
#endif

  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);

  for ( ncvarid = 0; ncvarid < nvars; ++ncvarid ) ncvars[ncvarid].ncid = fileID;


  // scan global attributes
  cdf_scan_global_attr(fileID, vlistID, ngatts, &instID, &modelID, &ucla_les,
                       uuidOfHGrid, uuidOfVGrid, gridfile, &number_of_grid_used);

  // find time dim
  int timedimid = (unlimdimid >= 0) ? unlimdimid : cdf_time_dimid(fileID, ndims, nvars, ncdims);

  streamptr->basetime.ncdimid = timedimid;

  size_t ntsteps = 0;
  if ( timedimid != CDI_UNDEFID ) cdf_inq_dimlen(fileID, ncdims[timedimid].dimid, &ntsteps);
  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);

  // read ncdims
  for ( ncdimid = 0; ncdimid < ndims; ncdimid++ )
    {
      cdf_inq_dimlen(fileID, ncdims[ncdimid].dimid, &ncdims[ncdimid].len);
      cdf_inq_dimname(fileID, ncdims[ncdimid].dimid, ncdims[ncdimid].name);
      if ( timedimid == ncdimid )
        ncdims[ncdimid].dimtype = T_AXIS;
    }

  if ( CDI_Debug ) cdf_print_vars(ncvars, nvars, "cdfScanVarAttr");

  // scan attributes of all variables
  cdfScanVarAttr(nvars, ncvars, ndims, ncdims, timedimid, modelID, format);

  cdfVerifyVarAttr(nvars, ncvars, ncdims);


  if ( CDI_Debug ) cdf_print_vars(ncvars, nvars, "find coordinate vars");

  // find coordinate vars
  for ( ncdimid = 0; ncdimid < ndims; ncdimid++ )
    {
      for ( ncvarid = 0; ncvarid < nvars; ncvarid++ )
	{
	  if ( ncvars[ncvarid].ndims == 1 )
	    {
	      if ( timedimid != CDI_UNDEFID && timedimid == ncvars[ncvarid].dimids[0] )
		{
		  if ( ncvars[ncvarid].isvar != FALSE ) cdf_set_var(ncvars, ncvarid, TRUE);
		}
	      else
		{
                  //  if ( ncvars[ncvarid].isvar != TRUE ) cdf_set_var(ncvars, ncvarid, FALSE);
		}
	      // if ( ncvars[ncvarid].isvar != TRUE ) cdf_set_var(ncvars, ncvarid, FALSE);

	      if ( ncdimid == ncvars[ncvarid].dimids[0] && ncdims[ncdimid].ncvarid == CDI_UNDEFID )
		if ( strIsEqual(ncvars[ncvarid].name, ncdims[ncdimid].name) )
		  {
		    ncdims[ncdimid].ncvarid = ncvarid;
		    ncvars[ncvarid].isvar = FALSE;
		  }
	    }
	}
    }

  // find time vars
  find_time_vars(nvars, ncvars, ncdims, timedimid, streamptr, &time_has_units, &time_has_bounds, &time_climatology);

  leadtime_id = find_leadtime(nvars, ncvars, timedimid);
  if ( leadtime_id != CDI_UNDEFID ) ncvars[leadtime_id].isvar = FALSE;

  // check ncvars
  timedimid = cdfCheckVars(streamptr, nvars, ncvars, ntsteps, timedimid);

  // verify coordinate vars - first scan (dimname == varname)
  bool lhybrid_cf = false;
  verify_coordinate_vars_1(fileID, ndims, ncdims, ncvars, timedimid, &lhybrid_cf);

  // verify coordinate vars - second scan (all other variables)
  verify_coordinate_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 ( ncdimid = 0; ncdimid < ndims; ncdimid++ )
    {
      ncvarid = ncdims[ncdimid].ncvarid;
      if ( ncvarid != -1 )
	{
	  printf("coord var %d %s %s\n", ncvarid, ncvars[ncvarid].name, ncvars[ncvarid].units);
	  if ( ncdims[ncdimid].dimtype == X_AXIS )
	    printf("coord var %d %s is x dim\n", ncvarid, ncvars[ncvarid].name);
	  if ( ncdims[ncdimid].dimtype == Y_AXIS )
	    printf("coord var %d %s is y dim\n", ncvarid, ncvars[ncvarid].name);
	  if ( ncdims[ncdimid].dimtype == Z_AXIS )
	    printf("coord var %d %s is z dim\n", ncvarid, ncvars[ncvarid].name);
	  if ( ncdims[ncdimid].dimtype == T_AXIS )
	    printf("coord var %d %s is t dim\n", ncvarid, ncvars[ncvarid].name);

	  if ( ncvars[ncvarid].islon )
	    printf("coord var %d %s is lon\n", ncvarid, ncvars[ncvarid].name);
	  if ( ncvars[ncvarid].islat )
	    printf("coord var %d %s is lat\n", ncvarid, ncvars[ncvarid].name);
	  if ( ncvars[ncvarid].islev )
	    printf("coord var %d %s is lev\n", ncvarid, ncvars[ncvarid].name);
	}
    }
  */

  // Set coordinate varids (att: associate)
  for ( ncvarid = 0; ncvarid < nvars; ncvarid++ )
    {
      ncvar_t *ncvar = &ncvars[ncvarid];
      if ( ncvar->isvar == TRUE && ncvar->ncoordvars )
	{
	  for ( int i = 0; i < ncvar->ncoordvars; i++ )
	    {
              const int cvarid = ncvar->coordvarids[i];
              if ( ncvar->coordvarids[i] != CDI_UNDEFID )
                {
                  if      (ncvars[cvarid].islon || ncvars[cvarid].isx)   ncvar->xvarid = cvarid;
                  else if (ncvars[cvarid].islat || ncvars[cvarid].isy)   ncvar->yvarid = cvarid;
                  else if (ncvars[cvarid].islev)  ncvar->zvarid = cvarid;
                  else if (ncvars[cvarid].istime) ncvar->tvarid = cvarid;
                  else if (ncvars[cvarid].isc)    ncvar->cvarids[i] = cvarid;
                  else if (ncvars[cvarid].warn == false)
                    {
                      Warning("Coordinates variable %s can't be assigned!", ncvars[cvarid].name);
                      ncvars[ncvarid].warn = true;
                    }
                }
            }
	}
    }

  // set dim type
  cdf_set_dimtype(nvars, ncvars, ncdims);

  // read ECHAM VCT if present
  size_t vctsize = 0;
  double *vct = NULL;
  if ( !lhybrid_cf ) read_vct_echam(fileID, nvars, ncvars, ncdims, &vct, &vctsize);


  if ( CDI_Debug ) cdf_print_vars(ncvars, nvars, "cdf_define_all_grids");

  // define all grids
  int status;
  status = cdf_define_all_grids(streamptr, streamptr->ncgrid, vlistID, ncdims, nvars, ncvars, timedimid, uuidOfHGrid, gridfile, number_of_grid_used);
  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, ncdims);

  // select vars
  int nvars_data = 0;
  for ( ncvarid = 0; ncvarid < nvars; ncvarid++ )
    if ( ncvars[ncvarid].isvar == TRUE ) nvars_data++;

  if ( CDI_Debug ) Message("time varid = %d", streamptr->basetime.ncvarid);
  if ( CDI_Debug ) Message("ntsteps = %zu", ntsteps);
  if ( CDI_Debug ) Message("nvars_data = %d", nvars_data);

  if ( nvars_data == 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;

  streamptr->ntsteps = (long)ntsteps;

  // define all data variables
  cdf_define_all_vars(streamptr, vlistID, instID, modelID, nvars_data, nvars, ncvars, ncdims);


  cdiCreateTimesteps(streamptr);

  // time varID
  int nctimevarid = streamptr->basetime.ncvarid;

  if (nctimevarid != CDI_UNDEFID && (!time_has_units || streamptr->basetime.lwrf)) ncvars[nctimevarid].units[0] = 0;
  if (nctimevarid != CDI_UNDEFID && time_has_units) streamptr->basetime.lunits = true;

  if ( time_has_units )
    {
      taxis_t *taxis = &streamptr->tsteps[0].taxis;

      if ( setBaseTime(ncvars[nctimevarid].units, taxis) == 1 )
        {
          nctimevarid = CDI_UNDEFID;
          streamptr->basetime.ncvarid = CDI_UNDEFID;
          streamptr->basetime.lunits = false;
        }

      if ( leadtime_id != CDI_UNDEFID && taxis->type == TAXIS_RELATIVE )
        {
          streamptr->basetime.leadtimeid = leadtime_id;
          taxis->type = TAXIS_FORECAST;

          int timeunit = -1;
          if ( ncvars[leadtime_id].units[0] != 0 ) timeunit = scanTimeUnit(ncvars[leadtime_id].units);
          if ( timeunit == -1 ) timeunit = taxis->unit;
          taxis->fc_unit = timeunit;

          setForecastTime(fcreftime, taxis);
        }
    }

  if ( time_has_bounds )
    {
      streamptr->tsteps[0].taxis.has_bounds = true;
      if ( time_climatology ) streamptr->tsteps[0].taxis.climatology = true;
    }

  if ( nctimevarid != CDI_UNDEFID )
    {
      taxis_t *taxis = &streamptr->tsteps[0].taxis;
      ptaxisDefName(taxis, ncvars[nctimevarid].name);

      if ( ncvars[nctimevarid].longname[0] )
        ptaxisDefLongname(taxis, ncvars[nctimevarid].longname);

      if ( ncvars[nctimevarid].units[0] )
        ptaxisDefUnits(taxis, 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(taxis, datatype);
    }

  if ( nctimevarid != CDI_UNDEFID )
    if ( ncvars[nctimevarid].calendar == true )
      {
        char attstring[1024];
	cdfGetAttText(fileID, nctimevarid, "calendar", sizeof(attstring), attstring);
	strToLower(attstring);
        cdf_set_calendar(attstring, &calendar);
      }

  int taxisID;
  if ( streamptr->tsteps[0].taxis.type == TAXIS_FORECAST )
    {
      taxisID = taxisCreate(TAXIS_FORECAST);
    }
  else if ( streamptr->tsteps[0].taxis.type == TAXIS_RELATIVE )
    {
      taxisID = taxisCreate(TAXIS_RELATIVE);
    }
  else
    {
      taxisID = taxisCreate(TAXIS_ABSOLUTE);
      if ( !time_has_units )
	{
	  taxisDefTunit(taxisID, TUNIT_DAY);
	  streamptr->tsteps[0].taxis.unit = TUNIT_DAY;
	}
    }


  if ( calendar == CDI_UNDEFID && streamptr->tsteps[0].taxis.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 )
    {
      taxis_t *taxis = &streamptr->tsteps[0].taxis;
      taxis->calendar = calendar;
      taxisDefCalendar(taxisID, calendar);
    }
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)
#pragma GCC diagnostic pop
#endif

  vlistDefTaxis(vlistID, taxisID);

  streamptr->curTsID = 0;
  streamptr->rtsteps = 1;

  (void) cdfInqTimestep(streamptr, 0);

  cdfCreateRecords(streamptr, 0);

  // free ncdims
  if ( ncdims ) Free(ncdims);

  // free ncvars
  if ( ncvars ) Free(ncvars);

  return 0;
}

static
void wrf_read_timestep(int fileID, int nctimevarid, int tsID, taxis_t *taxis)
{
  size_t start[2], count[2];
  char stvalue[128];
  start[0] = (size_t) tsID; start[1] = 0;
  count[0] = 1; count[1] = 19;
  stvalue[0] = 0;
  cdf_get_vara_text(fileID, nctimevarid, start, count, stvalue);
  stvalue[19] = 0;
  {
    int year = 1, month = 1, day = 1 , hour = 0, minute = 0, second = 0;
    if ( strlen(stvalue) == 19 )
      sscanf(stvalue, "%d-%d-%d_%d:%d:%d", &year, &month, &day, &hour, &minute, &second);
    taxis->vdate = cdiEncodeDate(year, month, day);
    taxis->vtime = cdiEncodeTime(hour, minute, second);
    taxis->type = TAXIS_ABSOLUTE;
  }
}

static
double get_timevalue(int fileID, int nctimevarid, int tsID, timecache_t *tcache)
{
  double timevalue = 0;

  if ( tcache )
    {
      if ( tcache->size == 0 || (tsID < (int)tcache->startid || tsID > (int)(tcache->startid+tcache->size-1)) )
        {
          size_t maxvals = MAX_TIMECACHE_SIZE;
          if (maxvals > tcache->maxvals) maxvals = tcache->maxvals;
          tcache->startid = tsID;
          //tcache->startid = (tsID/MAX_TIMECACHE_SIZE)*MAX_TIMECACHE_SIZE;
          //if ( (tcache->startid + maxvals) > tcache->maxvals ) maxvals = (tcache->maxvals)%MAX_TIMECACHE_SIZE;
          tcache->size = maxvals;
          //fprintf(stderr, "fill time cache: %d %d %d %d %d\n", tcache->maxvals, tsID, tcache->startid, tcache->startid+maxvals-1, maxvals);
          cdf_get_vara_double(fileID, nctimevarid, &tcache->startid, &maxvals, tcache->cache);
        }

      //timevalue = tcache->cache[tsID%MAX_TIMECACHE_SIZE];
      timevalue = tcache->cache[tsID];
    }
  else
    {
      size_t index = (size_t) tsID;
      cdf_get_var1_double(fileID, nctimevarid, &index, &timevalue);
    }

  if ( timevalue >= NC_FILL_DOUBLE || timevalue < -NC_FILL_DOUBLE ) timevalue = 0;

  return timevalue;
}


int cdfInqTimestep(stream_t * streamptr, int tsID)
{
  if ( CDI_Debug ) Message("streamID = %d  tsID = %d", streamptr->self, tsID);

  if ( tsID < 0 ) Error("unexpected tsID = %d", tsID);

  if ( tsID < streamptr->ntsteps && streamptr->ntsteps > 0 )
    {
      cdfCreateRecords(streamptr, tsID);

      taxis_t *taxis = &streamptr->tsteps[tsID].taxis;
      if ( tsID > 0 )
	ptaxisCopy(taxis, &streamptr->tsteps[0].taxis);

      double timevalue = tsID;

      int nctimevarid = streamptr->basetime.ncvarid;
      if (nctimevarid != CDI_UNDEFID && streamptr->basetime.lunits)
	{
	  int fileID = streamptr->fileID;
	  size_t index = (size_t)tsID;

	  if ( streamptr->basetime.lwrf )
	    {
              wrf_read_timestep(fileID, nctimevarid, tsID, taxis);
	    }
	  else
	    {
#ifdef USE_TIMECACHE
              if ( streamptr->basetime.timevar_cache == NULL )
                {
                  streamptr->basetime.timevar_cache = (timecache_t *) Malloc(sizeof(timecache_t));
                  streamptr->basetime.timevar_cache->size = 0;
                  streamptr->basetime.timevar_cache->maxvals = streamptr->ntsteps;
                }
#endif
              timevalue = get_timevalue(fileID, nctimevarid, tsID, streamptr->basetime.timevar_cache);
	      cdiDecodeTimeval(timevalue, taxis, &taxis->vdate, &taxis->vtime);
	    }

	  int nctimeboundsid = streamptr->basetime.ncvarboundsid;
	  if ( nctimeboundsid != CDI_UNDEFID )
	    {
	      size_t start[2], count[2];
              start[0] = index; count[0] = 1; start[1] = 0; count[1] = 1;
	      cdf_get_vara_double(fileID, nctimeboundsid, start, count, &timevalue);
              if ( timevalue >= NC_FILL_DOUBLE || timevalue < -NC_FILL_DOUBLE ) timevalue = 0;

	      cdiDecodeTimeval(timevalue, taxis, &taxis->vdate_lb, &taxis->vtime_lb);

              start[0] = index; count[0] = 1; start[1] = 1; count[1] = 1;
	      cdf_get_vara_double(fileID, nctimeboundsid, start, count, &timevalue);
              if ( timevalue >= NC_FILL_DOUBLE || timevalue < -NC_FILL_DOUBLE ) timevalue = 0;

	      cdiDecodeTimeval(timevalue, taxis, &taxis->vdate_ub, &taxis->vtime_ub);
	    }

          int leadtimeid = streamptr->basetime.leadtimeid;
          if ( leadtimeid != CDI_UNDEFID )
            {
              timevalue = get_timevalue(fileID, leadtimeid, tsID, NULL);
              cdiSetForecastPeriod(timevalue, taxis);
            }
	}
    }

  streamptr->curTsID = tsID;
  long nrecs = streamptr->tsteps[tsID].nrecs;

  return (int) nrecs;
}

#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



#define  POSITIVE_UP    1
#define  POSITIVE_DOWN  2


static const char bndsName[] = "bnds";


void cdfCopyRecord(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].records[recID].varID;
  int gridID   = vlistInqVarGrid(vlistID1, ivarID);
  size_t datasize = 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 nmiss;
  cdf_read_record(streamptr1, memtype, data, &nmiss);
  cdf_write_record(streamptr2, memtype, data, nmiss);

  Free(data);
}


void cdfDefRecord(stream_t *streamptr)
{
  (void)streamptr;
}

static
void cdfDefTimeValue(stream_t *streamptr, int tsID)
{
  const int fileID = streamptr->fileID;

  if ( CDI_Debug ) Message("streamID = %d, fileID = %d", streamptr->self, fileID);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if ( streamptr->ncmode == 1 )
    {
      cdf_enddef(fileID);
      streamptr->ncmode = 2;
    }

  double timevalue = cdiEncodeTimeval(taxis->vdate, taxis->vtime, &streamptr->tsteps[0].taxis);
  if ( CDI_Debug ) Message("tsID = %d  timevalue = %f", tsID, timevalue);

  int ncvarid = streamptr->basetime.ncvarid;
  size_t index = (size_t)tsID;
  cdf_put_var1_double(fileID, ncvarid, &index, &timevalue);

  if ( taxis->has_bounds )
    {
      ncvarid = streamptr->basetime.ncvarboundsid;
      if ( ncvarid == CDI_UNDEFID ) Error("Call to taxisWithBounds() missing!");

      timevalue = cdiEncodeTimeval(taxis->vdate_lb, taxis->vtime_lb, &streamptr->tsteps[0].taxis);
      size_t start[2], count[2];
      start[0] = (size_t)tsID; count[0] = 1; start[1] = 0; count[1] = 1;
      cdf_put_vara_double(fileID, ncvarid, start, count, &timevalue);

      timevalue = cdiEncodeTimeval(taxis->vdate_ub, taxis->vtime_ub, &streamptr->tsteps[0].taxis);
      start[0] = (size_t)tsID; count[0] = 1; start[1] = 1; count[1] = 1;
      cdf_put_vara_double(fileID, ncvarid, start, count, &timevalue);
    }

  ncvarid = streamptr->basetime.leadtimeid;
  if ( taxis->type == TAXIS_FORECAST && ncvarid != CDI_UNDEFID )
    {
      timevalue = taxis->fc_period;
      cdf_put_var1_double(fileID, ncvarid, &index, &timevalue);
    }
}

void cdfDefTimestep(stream_t *streamptr, int tsID)
{
  cdfDefTimeValue(streamptr, tsID);
}

static
void cdfDefComplex(stream_t *streamptr, int gridID, int gridindex)
{
  int dimID;
  ncgrid_t *ncgrid = streamptr->ncgrid;

  for ( int index = 0; index < gridindex; ++index )
    {
      if ( ncgrid[index].ncIDs[CDF_DIMID_X] != CDI_UNDEFID )
        {
          const int gridID0 = ncgrid[index].gridID;
          const int gridtype0 = gridInqType(gridID0);
          if ( gridtype0 == GRID_SPECTRAL || gridtype0 == GRID_FOURIER )
            {
              dimID = ncgrid[index].ncIDs[CDF_DIMID_X];
              goto dimIDEstablished;
            }
        }
    }

  {
    static const char axisname[] = "nc2";
    const size_t dimlen = 2;
    const int fileID  = streamptr->fileID;

    if ( streamptr->ncmode == 2 ) cdf_redef(fileID);
    cdf_def_dim(fileID, axisname, dimlen, &dimID);
    cdf_enddef(fileID);
    streamptr->ncmode = 2;
  }
  dimIDEstablished:
  ncgrid[gridindex].gridID = gridID;
  ncgrid[gridindex].ncIDs[CDF_DIMID_X] = dimID;
}

struct idSearch
{
  int numNonMatching, foundID;
  size_t foundIdx;
};

static inline struct idSearch
cdfSearchIDBySize(size_t startIdx, size_t numIDs, const ncgrid_t ncgrid[/*numIDs*/],
                  int ncIDType, int searchType, int searchSize,
                  int (*typeInq)(int id), size_t (*sizeInq)(int id))
{
  int numNonMatching = 0,
    foundID = CDI_UNDEFID;
  size_t foundIdx = SIZE_MAX;
  for ( size_t index = startIdx; index < numIDs; index++ )
    {
      if ( ncgrid[index].ncIDs[ncIDType] != CDI_UNDEFID )
        {
          int id0 = ncgrid[index].gridID,
            id0Type = typeInq(id0);
          if ( id0Type == searchType )
            {
              int size0 = sizeInq(id0);
              if ( searchSize == size0 )
                {
                  foundID = ncgrid[index].ncIDs[ncIDType];
                  foundIdx = index;
                  break;
                }
              numNonMatching++;
            }
        }
    }
  return (struct idSearch){ .numNonMatching = numNonMatching,
      .foundID = foundID, .foundIdx = foundIdx };
}

static size_t
cdfGridInqHalfSize(int gridID)
{
  return gridInqSize(gridID)/2;
}

static void
cdfDefSPorFC(stream_t *streamptr, int gridID, int gridindex, char *restrict axisname, int gridRefType)
{
  ncgrid_t *ncgrid = streamptr->ncgrid;

  const size_t dimlen = gridInqSize(gridID)/2;

  struct idSearch search
    = cdfSearchIDBySize(0, (size_t)gridindex, ncgrid, CDF_DIMID_Y,
                        gridRefType, (int)dimlen, gridInqType, cdfGridInqHalfSize);
  int dimID = search.foundID;
  const int iz = search.numNonMatching;

  if ( dimID == CDI_UNDEFID )
    {
      int fileID  = streamptr->fileID;
      if ( iz == 0 ) axisname[3] = '\0';
      else           sprintf(&axisname[3], "%1d", iz+1);

      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      cdf_def_dim(fileID, axisname, dimlen, &dimID);

      cdf_enddef(fileID);
      streamptr->ncmode = 2;
    }

  ncgrid[gridindex].gridID = gridID;
  ncgrid[gridindex].ncIDs[CDF_DIMID_Y] = dimID;
}

static
void cdfDefSP(stream_t *streamptr, int gridID, int gridindex)
{
  // char longname[] = "Spherical harmonic coefficient";
  char axisname[5] = "nspX";
  cdfDefSPorFC(streamptr, gridID, gridindex, axisname, GRID_SPECTRAL);
}


static
void cdfDefFC(stream_t *streamptr, int gridID, int gridindex)
{
  char axisname[5] = "nfcX";
  cdfDefSPorFC(streamptr, gridID, gridindex, axisname, GRID_FOURIER);
}

static const struct cdfDefGridAxisInqs {
  size_t (*axisSize)(int gridID);
  double (*axisVal)(int gridID, size_t index);
  const double *(*axisValsPtr)(int gridID);
  const double *(*axisBoundsPtr)(int gridID);
} gridInqsX = {
  .axisSize = gridInqXsize,
  .axisVal = gridInqXval,
  .axisValsPtr = gridInqXvalsPtr,
  .axisBoundsPtr = gridInqXboundsPtr,
}, gridInqsY = {
  .axisSize = gridInqYsize,
  .axisVal = gridInqYval,
  .axisValsPtr = gridInqYvalsPtr,
  .axisBoundsPtr = gridInqYboundsPtr,
};

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];
  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];
  length = CDI_MAX_NAME;
  axisKey = (dimtype == 'Z') ? CDI_GLOBAL : ((dimtype == 'X') ? CDI_XAXIS : CDI_YAXIS);
  cdiInqKeyString(gridID, axisKey, CDI_KEY_UNITS, units, &length);
  if ( units[0] && (len = strlen(units)) )
    cdf_put_att_text(fileID, ncvarid, "units", len, units);
}

static void
cdfDefTrajLatLon(stream_t *streamptr, int gridID, int gridindex,
                 const struct cdfDefGridAxisInqs *inqs, int dimtype)
{
  const nc_type xtype = (gridInqDatatype(gridID) == CDI_DATATYPE_FLT32) ? NC_FLOAT : NC_DOUBLE;
  ncgrid_t *ncgrid = streamptr->ncgrid;

  const size_t dimlen = inqs->axisSize(gridID);
  if ( dimlen != 1 )
    Error("%c size isn't 1 for %s grid!", dimtype, gridNamePtr(gridInqType(gridID)));

  int ncvarid = ncgrid[gridindex].ncIDs[dimtype == 'X' ? CDF_DIMID_X : CDF_DIMID_Y];

  if ( ncvarid == CDI_UNDEFID )
    {
      int dimNcID = streamptr->basetime.ncvarid;
      const int fileID  = streamptr->fileID;
      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      char axisname[CDI_MAX_NAME];
      const int axistype = (dimtype == '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, dimtype);
      cdf_enddef(fileID);
      streamptr->ncmode = 2;
    }

  ncgrid[gridindex].gridID = gridID;
  /* var ID for trajectory !!! */
  ncgrid[gridindex].ncIDs[dimtype == 'X' ? CDF_DIMID_X : CDF_DIMID_Y] = ncvarid;
}

static
void cdfDefTrajLon(stream_t *streamptr, int gridID, int gridindex)
{
  cdfDefTrajLatLon(streamptr, gridID, gridindex, &gridInqsX, 'X');
}


static
void cdfDefTrajLat(stream_t *streamptr, int gridID, int gridindex)
{
  cdfDefTrajLatLon(streamptr, gridID, gridindex, &gridInqsY, 'Y');
}

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];

  const size_t len = strlen(dimname);
  memcpy(name, dimname, len + 1);

  do
    {
      if ( iz ) sprintf(name + len, "_%u", iz+1);

      int dimid0;
      const 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 ) sprintf(dimname + len, "_%u", iz+1);

  return dimid;
}

static
void checkGridName(char *axisname, int fileID)
{
  int ncdimid;
  char axisname2[CDI_MAX_NAME];

  /* check that the name is not already defined */
  unsigned iz = 0;

  const size_t axisnameLen = strlen(axisname);
  memcpy(axisname2, axisname, axisnameLen + 1);

  do
    {
      if ( iz ) sprintf(axisname2 + axisnameLen, "_%u", iz+1);

      if ( nc_inq_varid(fileID, axisname2, &ncdimid) != NC_NOERR ) break;

      ++iz;
    }
  while ( iz <= 99 );

  if ( iz ) sprintf(axisname + axisnameLen, "_%u", iz+1);
}

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;

  const size_t axisnameLen = strlen(axisname);
  memcpy(axisname2, axisname, axisnameLen + 1);
  do
    {
      if ( iz ) sprintf(axisname2 + axisnameLen, "_%u", iz+1);

      int ncdimid;
      const 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++ )
                {
                  const int zaxisID0 = vlistZaxis(vlistID, index);
                  if ( zaxisID != zaxisID0 )
                    {
                      const char *axisname0 = zaxisInqNamePtr(zaxisID0);
                      if ( strcmp(axisname0, axisname2) == 0 ) goto nextSuffix;
                    }
                }
            }
          break;
        }
      nextSuffix:
      ++iz;
    }
  while (iz <= 99);


  if ( iz ) sprintf(axisname + axisnameLen, "_%u", iz+1);

  return (int)iz;
}

static void
cdfDefAxisCommon(stream_t *streamptr, int gridID, int gridindex, int ndims, bool addVarToGrid,
                 const struct cdfDefGridAxisInqs *gridAxisInq, int axisKey, char axisLetter,
                 void (*finishCyclicBounds)(double *pbounds, size_t dimlen, const double *pvals))
{
  int dimID = CDI_UNDEFID;
  const int fileID  = streamptr->fileID;
  const size_t dimlen = gridAxisInq->axisSize(gridID);
  const nc_type xtype = (nc_type)cdfDefDatatype(gridInqDatatype(gridID), streamptr);

  ncgrid_t *ncgrid = streamptr->ncgrid;

  const double *pvals = gridAxisInq->axisValsPtr(gridID);
  char dimname[CDI_MAX_NAME+3]; dimname[0] = 0;
  int length = sizeof(dimname);
  if ( ndims && pvals == NULL ) cdiInqKeyString(gridID, axisKey, CDI_KEY_DIMNAME, dimname, &length);

  for ( int index = 0; index < gridindex; ++index )
    {
      const int gridID0 = ncgrid[index].gridID;
      assert(gridID0 != CDI_UNDEFID);
      const int gridtype0 = gridInqType(gridID0);
      if ( gridtype0 == GRID_GAUSSIAN    ||
           gridtype0 == GRID_LONLAT      ||
           gridtype0 == GRID_PROJECTION  ||
           gridtype0 == GRID_GENERIC )
        {
          const size_t 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);
          const bool lname = dimname0[0] ? strcmp(dimname, dimname0) == 0 : true;
          if ( dimlen == dimlen0 && lname )
            {
              double (*inqVal)(int gridID, size_t index) = gridAxisInq->axisVal;
              if ( IS_EQUAL(inqVal(gridID0, 0), inqVal(gridID, 0)) &&
                   IS_EQUAL(inqVal(gridID0, dimlen-1), inqVal(gridID, dimlen-1)) )
                {
                  dimID = ncgrid[index].ncIDs[axisLetter == 'X' ? CDF_DIMID_X : CDF_DIMID_Y];
                  break;
                }
            }
        }
    }

  if ( dimID == CDI_UNDEFID )
    {
      int ncvarid = CDI_UNDEFID, ncbvarid = CDI_UNDEFID;
      char axisname[CDI_MAX_NAME];
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, axisKey, CDI_KEY_NAME, axisname, &length);
      if ( axisname[0] == 0 ) Error("axis name undefined!");

      checkGridName(axisname, fileID);
      const size_t axisnameLen = strlen(axisname);

      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      if ( ndims )
        {
          if ( dimname[0] == 0 ) strcpy(dimname, axisname);
          dimID = checkDimName(fileID, dimlen, dimname);

          if ( dimID == CDI_UNDEFID ) cdf_def_dim(fileID, dimname, dimlen, &dimID);
        }

      bool gen_bounds = false;
      const bool grid_is_cyclic = gridIsCircular(gridID) > 0;
      double *pbounds = NULL;
      if ( pvals )
        {
          cdf_def_var(fileID, axisname, xtype, ndims, &dimID, &ncvarid);

          cdfPutGridStdAtts(fileID, ncvarid, gridID, axisLetter);
          {
            char axisStr[2] = { axisLetter, '\0' };
            cdf_put_att_text(fileID, ncvarid, "axis", 1, axisStr);
          }

          size_t nvertex = gridInqNvertex(gridID);
          pbounds = (double *)gridAxisInq->axisBoundsPtr(gridID);

          if ( CDI_CMOR_Mode && grid_is_cyclic && !pbounds )
            {
              gen_bounds = true;
              nvertex = 2;
              pbounds = (double*) Malloc(2*dimlen*sizeof(double));
              for ( size_t i = 0; i < dimlen-1; ++i )
                {
                  pbounds[i*2+1]   = (pvals[i] + pvals[i+1])/2;
                  pbounds[(i+1)*2] = (pvals[i] + pvals[i+1])/2;
                }
              finishCyclicBounds(pbounds, dimlen, pvals);
            }

          int nvdimID = CDI_UNDEFID;
          if ( pbounds )
            {
              if ( nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR )
                cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);
            }
          if ( pbounds && 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 };
              cdf_def_var(fileID, boundsname, xtype, 2, dimIDs, &ncbvarid);
              cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), boundsname);
            }
        }

      cdf_enddef(fileID);
      streamptr->ncmode = 2;

      if ( ncvarid  != CDI_UNDEFID ) cdf_put_var_double(fileID, ncvarid, pvals);
      if ( ncbvarid != CDI_UNDEFID ) cdf_put_var_double(fileID, ncbvarid, pbounds);
      if ( gen_bounds ) Free(pbounds);

      if (ndims == 0 || addVarToGrid)
        ncgrid[gridindex].ncIDs[axisLetter == 'X' ? CDF_VARID_X : CDF_VARID_Y] = ncvarid;
    }

  ncgrid[gridindex].gridID = gridID;
  ncgrid[gridindex].ncIDs[axisLetter == 'X' ? CDF_DIMID_X : CDF_DIMID_Y] = dimID;
}

static
void finishCyclicXBounds(double *pbounds, size_t 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, size_t dimlen, const double *pvals)
{
  pbounds[0] = copysign(90.0, pvals[0]);
  pbounds[2*dimlen-1] = copysign(90.0, pvals[dimlen-1]);
}

static
void cdfDefXaxis(stream_t *streamptr, int gridID, int gridindex, int ndims, bool addVarToGrid)
{
  cdfDefAxisCommon(streamptr, gridID, gridindex, ndims, addVarToGrid, &gridInqsX, CDI_XAXIS, 'X', finishCyclicXBounds);
}

static
void cdfDefYaxis(stream_t *streamptr, int gridID, int gridindex, int ndims, bool addVarToGrid)
{
  cdfDefAxisCommon(streamptr, gridID, gridindex, ndims, addVarToGrid, &gridInqsY, CDI_YAXIS, 'Y', finishCyclicYBounds);
}

static
void cdfGridCompress(int fileID, int ncvarid, size_t gridsize, int filetype, int comptype)
{
#ifdef HAVE_NETCDF4
  if ( gridsize > 1 && comptype == CDI_COMPRESS_ZIP && (filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C) )
    {
      cdf_def_var_chunking(fileID, ncvarid, NC_CHUNKED, NULL);
      cdfDefVarDeflate(fileID, ncvarid, 1);
    }
#endif
}

static
void cdfDefGridReference(stream_t *streamptr, int gridID)
{
  const 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 uuidOfHGrid[CDI_UUID_SIZE];
  size_t len = CDI_UUID_SIZE;
  memset(uuidOfHGrid, 0, len);
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuidOfHGrid, &length);
  if ( !cdiUUIDIsNull(uuidOfHGrid) )
    {
      char uuidOfHGridStr[37];
      cdiUUID2Str(uuidOfHGrid, uuidOfHGridStr);
      if ( uuidOfHGridStr[0] != 0 && strlen(uuidOfHGridStr) == 36 )
        {
          int fileID  = streamptr->fileID;
          //if ( streamptr->ncmode == 2 ) cdf_redef(fileID);
          cdf_put_att_text(fileID, NC_GLOBAL, "uuidOfHGrid", 36, uuidOfHGridStr);
          //if ( streamptr->ncmode == 2 ) cdf_enddef(fileID);
        }
    }
}

struct cdfDefIrregularGridCommonIDs
{
  int xdimID, ydimID, ncxvarid, ncyvarid, ncavarid;
};

static struct cdfDefIrregularGridCommonIDs
cdfDefIrregularGridCommon(stream_t *streamptr, int gridID,
                          size_t xdimlen, size_t ydimlen,
                          int ndims, const char *xdimname_default,
                          size_t nvertex, const char *vdimname_default,
                          bool setVdimname)
{
  nc_type xtype = (nc_type)cdfDefDatatype(gridInqDatatype(gridID), streamptr);
  int xdimID = CDI_UNDEFID;
  int ydimID = CDI_UNDEFID;
  int ncxvarid = CDI_UNDEFID, ncyvarid = CDI_UNDEFID, ncavarid = CDI_UNDEFID;
  int ncbxvarid = CDI_UNDEFID, ncbyvarid = CDI_UNDEFID;
  int fileID  = streamptr->fileID;
  if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

  {
    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, xdimlen, xdimname);
    if ( xdimID == CDI_UNDEFID ) cdf_def_dim(fileID, xdimname, xdimlen, &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, ydimlen, ydimname);
      if ( ydimID == CDI_UNDEFID ) cdf_def_dim(fileID, ydimname, ydimlen, &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);
          nvdimID = dimIDs[ndims-1];
        }
    }

  if ( ndims == 3 )
    {
      dimIDs[0] = ydimID;
      dimIDs[1] = xdimID;
    }
  else /* ndims == 2 */
    {
      dimIDs[0] = xdimID;
      cdfDefGridReference(streamptr, gridID);
      cdfDefGridUUID(streamptr, gridID);
    }

  const double *xvalsPtr = gridInqXvalsPtr(gridID),
    *xboundsPtr = NULL;
  if ( xvalsPtr )
    {
      char xaxisname[CDI_MAX_NAME];
      int length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_NAME, xaxisname, &length);
      checkGridName(xaxisname, fileID);
      cdf_def_var(fileID, xaxisname, xtype, ndims-1, dimIDs, &ncxvarid);
      cdfGridCompress(fileID, ncxvarid, xdimlen*ydimlen, streamptr->filetype, streamptr->comptype);

      cdfPutGridStdAtts(fileID, ncxvarid, gridID, 'X');

      /* attribute for Panoply */
      if ( ndims == 3 )
        cdf_put_att_text(fileID, ncxvarid, "_CoordinateAxisType", 3, "Lon");

      if ( (xboundsPtr = gridInqXboundsPtr(gridID)) && nvdimID != CDI_UNDEFID )
        {
          const size_t xaxisnameLen = strlen(xaxisname);
          xaxisname[xaxisnameLen] = '_';
          memcpy(xaxisname + xaxisnameLen + 1, bndsName, sizeof (bndsName));
          cdf_def_var(fileID, xaxisname, xtype, ndims, dimIDs, &ncbxvarid);
          cdfGridCompress(fileID, ncbxvarid, xdimlen*ydimlen, streamptr->filetype, streamptr->comptype);

          cdf_put_att_text(fileID, ncxvarid, "bounds", xaxisnameLen + sizeof (bndsName), xaxisname);
        }
    }

  const double *yvalsPtr = gridInqYvalsPtr(gridID),
    *yboundsPtr = NULL;
  if ( yvalsPtr )
    {
      char yaxisname[CDI_MAX_NAME];
      gridInqYname(gridID, yaxisname);
      checkGridName(yaxisname, fileID);

      cdf_def_var(fileID, yaxisname, xtype, ndims - 1, dimIDs, &ncyvarid);
      cdfGridCompress(fileID, ncyvarid, xdimlen*ydimlen, streamptr->filetype, streamptr->comptype);

      cdfPutGridStdAtts(fileID, ncyvarid, gridID, 'Y');

      /* attribute for Panoply */
      if ( ndims == 3 )
        cdf_put_att_text(fileID, ncyvarid, "_CoordinateAxisType", 3, "Lat");

      if ( (yboundsPtr = gridInqYboundsPtr(gridID)) && nvdimID != CDI_UNDEFID )
        {
          const size_t yaxisnameLen = strlen(yaxisname);
          yaxisname[yaxisnameLen] = '_';
          memcpy(yaxisname + yaxisnameLen + 1, bndsName, sizeof (bndsName));
          cdf_def_var(fileID, yaxisname, xtype, ndims, dimIDs, &ncbyvarid);
          cdfGridCompress(fileID, ncbyvarid, xdimlen*ydimlen, streamptr->filetype, streamptr->comptype);

          cdf_put_att_text(fileID, ncyvarid, "bounds", yaxisnameLen + sizeof (bndsName), yaxisname);
        }
    }

  const double *areaPtr = gridInqAreaPtr(gridID);
  if ( areaPtr )
    {
      static const char yaxisname_[] = "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, yaxisname_, 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);
    }

  cdf_enddef(fileID);
  streamptr->ncmode = 2;

  if ( ncxvarid  != CDI_UNDEFID ) cdf_put_var_double(fileID, ncxvarid,  xvalsPtr);
  if ( ncbxvarid != CDI_UNDEFID ) cdf_put_var_double(fileID, ncbxvarid, xboundsPtr);
  if ( ncyvarid  != CDI_UNDEFID ) cdf_put_var_double(fileID, ncyvarid,  yvalsPtr);
  if ( ncbyvarid != CDI_UNDEFID ) cdf_put_var_double(fileID, ncbyvarid, yboundsPtr);
  if ( ncavarid  != CDI_UNDEFID ) cdf_put_var_double(fileID, ncavarid,  areaPtr);

  return (struct cdfDefIrregularGridCommonIDs) {
    .xdimID=xdimID, .ydimID = ydimID,
    .ncxvarid=ncxvarid, .ncyvarid=ncyvarid, .ncavarid=ncavarid
  };
}

static
void cdfDefCurvilinear(stream_t *streamptr, int gridID, int gridindex)
{
  ncgrid_t *ncgrid = streamptr->ncgrid;

  const size_t dimlen = gridInqSize(gridID);
  const size_t xdimlen = gridInqXsize(gridID);
  const size_t 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, ncgrid, CDF_DIMID_X,
                          GRID_CURVILINEAR, (int)dimlen, gridInqType, gridInqSize);
    const size_t index = search.foundIdx;
    if ( index != SIZE_MAX )
      {
        const int gridID0 = ncgrid[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 = ncgrid[index].ncIDs[CDF_DIMID_X];
            ydimID = ncgrid[index].ncIDs[CDF_DIMID_Y];
            ncxvarid = ncgrid[index].ncIDs[CDF_VARID_X];
            ncyvarid = ncgrid[index].ncIDs[CDF_VARID_Y];
            break;
          }
        ofs = search.foundIdx;
        if ( ofs < (size_t)gridindex )
          continue;
      }
  } while (false);

  if ( xdimID == CDI_UNDEFID || ydimID == CDI_UNDEFID )
    {
      struct cdfDefIrregularGridCommonIDs createdIDs
        = cdfDefIrregularGridCommon(streamptr, gridID,
                                    xdimlen, ydimlen, 3, "x", 4, "nv4",
                                    gridInqXboundsPtr(gridID)
                                    || gridInqYboundsPtr(gridID));
      xdimID = createdIDs.xdimID;
      ydimID = createdIDs.ydimID;
      ncxvarid = createdIDs.ncxvarid;
      ncyvarid = createdIDs.ncyvarid;
      ncavarid = createdIDs.ncavarid;
    }

  ncgrid[gridindex].gridID = gridID;
  ncgrid[gridindex].ncIDs[CDF_DIMID_X] = xdimID;
  ncgrid[gridindex].ncIDs[CDF_DIMID_Y] = ydimID;
  ncgrid[gridindex].ncIDs[CDF_VARID_X] = ncxvarid;
  ncgrid[gridindex].ncIDs[CDF_VARID_Y] = ncyvarid;
  ncgrid[gridindex].ncIDs[CDF_VARID_A] = ncavarid;
}


static
void cdfDefUnstructured(stream_t *streamptr, int gridID, int gridindex)
{
  ncgrid_t *ncgrid = streamptr->ncgrid;

  const size_t 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, ncgrid, CDF_DIMID_X,
                          GRID_UNSTRUCTURED, (int)dimlen, gridInqType, gridInqSize);
    const size_t index = search.foundIdx;
    if ( index != SIZE_MAX )
      {
        const int gridID0 = ncgrid[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 = ncgrid[index].ncIDs[CDF_DIMID_X];
            ncxvarid = ncgrid[index].ncIDs[CDF_VARID_X];
            ncyvarid = ncgrid[index].ncIDs[CDF_VARID_Y];
            ncavarid = ncgrid[index].ncIDs[CDF_VARID_A];
            break;
          }
        ofs = search.foundIdx;
        if ( ofs < (size_t)gridindex )
          continue;
      }
  } while (false);

  if ( dimID == CDI_UNDEFID )
    {
      const size_t nvertex = (size_t)gridInqNvertex(gridID);
      struct cdfDefIrregularGridCommonIDs createdIDs
        = cdfDefIrregularGridCommon(streamptr, gridID,
                                    dimlen, 1, 2, "ncells",
                                    nvertex, "vertices", nvertex > 0);
      dimID = createdIDs.xdimID;
      ncxvarid = createdIDs.ncxvarid;
      ncyvarid = createdIDs.ncyvarid;
      ncavarid = createdIDs.ncavarid;
    }

  ncgrid[gridindex].gridID = gridID;
  ncgrid[gridindex].ncIDs[CDF_DIMID_X] = dimID;
  ncgrid[gridindex].ncIDs[CDF_VARID_X] = ncxvarid;
  ncgrid[gridindex].ncIDs[CDF_VARID_Y] = ncyvarid;
  ncgrid[gridindex].ncIDs[CDF_VARID_A] = ncavarid;
}

struct attTxtTab2
{
  const char *attName, *attVal;
  size_t valLen;
};

static
void cdf_def_vct_echam(stream_t *streamptr, int zaxisID)
{
  const int type = zaxisInqType(zaxisID);
  if ( type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF )
    {
      const int ilev = zaxisInqVctSize(zaxisID)/2;
      if ( ilev == 0 ) return;

      const int mlev = ilev - 1;

      if ( streamptr->vct.ilev > 0 )
        {
          if ( streamptr->vct.ilev != ilev )
            Error("More than one VCT for each file unsupported!");
          return;
        }

      const int fileID = streamptr->fileID;

      if ( streamptr->ncmode == 2 ) 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);
        }

      streamptr->vct.ilev   = ilev;
      streamptr->vct.mlev   = mlev;
      streamptr->vct.mlevID = ncdimid;
      streamptr->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]) };
        const 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]) };
        const 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);
      }

      cdf_enddef(fileID);
      streamptr->ncmode = 2;

      const double *vctptr = zaxisInqVctPtr(zaxisID);

      cdf_put_var_double(fileID, hyaiid, vctptr);
      cdf_put_var_double(fileID, hybiid, vctptr+ilev);

      size_t start;
      size_t count = 1;
      double mval;
      for ( int i = 0; i < mlev; i++ )
        {
          start = (size_t)i;
          mval = (vctptr[i] + vctptr[i+1]) * 0.5;
          cdf_put_vara_double(fileID, hyamid, &start, &count, &mval);
          mval = (vctptr[ilev+i] + vctptr[ilev+i+1]) * 0.5;
          cdf_put_vara_double(fileID, hybmid, &start, &count, &mval);
        }
    }
}

static
void cdf_def_vct_cf(stream_t *streamptr, int zaxisID, int nclevID, int ncbndsID, int p0status, double p0value)
{
  const int type = zaxisInqType(zaxisID);
  if ( type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF )
    {
      const int ilev = zaxisInqVctSize(zaxisID)/2;
      if ( ilev == 0 ) return;

      const int mlev = ilev - 1;
      int hyaiid = 0, hybiid = 0, hyamid, hybmid;

      if ( streamptr->vct.ilev > 0 )
        {
          if ( streamptr->vct.ilev != ilev )
            Error("more than one VCT for each file unsupported!");
          return;
        }

      const int fileID = streamptr->fileID;

      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      int dimIDs[2];
      dimIDs[0] = nclevID;
      dimIDs[1] = ncbndsID;

      streamptr->vct.mlev   = mlev;
      streamptr->vct.ilev   = ilev;
      streamptr->vct.mlevID = nclevID;
      streamptr->vct.ilevID = nclevID;

      if ( p0status == 0 )
        cdf_def_var(fileID, "a", NC_DOUBLE, 1, dimIDs,  &hyamid);
      else
        cdf_def_var(fileID, "ap", NC_DOUBLE, 1, dimIDs,  &hyamid);
      cdf_def_var(fileID, "b",  NC_DOUBLE, 1, dimIDs,  &hybmid);

      {
        static const char lname[] = "vertical coordinate formula term: ap(k)";
        cdf_put_att_text(fileID, hyamid, "long_name", sizeof (lname) - 1, lname);

        static const char units[] = "Pa";
        cdf_put_att_text(fileID, hyamid, "units", sizeof (units) - 1, units);
      }
      {
        static const char lname[] = "vertical coordinate formula term: b(k)";
        cdf_put_att_text(fileID, hybmid, "long_name", sizeof (lname) - 1, lname);

        static const char units[] = "1";
        cdf_put_att_text(fileID, hybmid, "units", sizeof (units) - 1, units);
      }

      if ( ncbndsID != -1 )
        {
          if ( p0status == 0 )
            cdf_def_var(fileID, "a_bnds", NC_DOUBLE, 2, dimIDs, &hyaiid);
          else
            cdf_def_var(fileID, "ap_bnds", NC_DOUBLE, 2, dimIDs, &hyaiid);
          cdf_def_var(fileID, "b_bnds",  NC_DOUBLE, 2, dimIDs, &hybiid);

          {
            static const char lname[] = "vertical coordinate formula term: ap(k+1/2)";
            cdf_put_att_text(fileID, hyaiid, "long_name", sizeof (lname) - 1, lname);

            static const char units[] = "Pa";
            cdf_put_att_text(fileID, hyaiid, "units", sizeof (units) - 1, units);
          }
          {
            static const char lname[] = "vertical coordinate formula term: b(k+1/2)";
            cdf_put_att_text(fileID, hybiid, "long_name", sizeof (lname) - 1, lname);

            static const char units[] = "1";
            cdf_put_att_text(fileID, hybiid, "units", sizeof (units) - 1, units);
          }
        }

      cdf_enddef(fileID);
      streamptr->ncmode = 2;

      const int vctsize = zaxisInqVctSize(zaxisID);
      double *vct = (double*) malloc(vctsize*sizeof(double));;
      zaxisInqVct(zaxisID, vct);

      if ( p0status == 0 && IS_NOT_EQUAL(p0value,0) )
        for ( int i = 0; i < vctsize/2; ++i ) vct[i] /= p0value;

      double *tarray = (double*) malloc(ilev*2*sizeof(double));

      if ( ncbndsID != -1 )
        {
          for ( int i = 0; i < mlev; ++i )
            {
              tarray[2*i  ] = vct[i];
              tarray[2*i+1] = vct[i+1];
            }
          cdf_put_var_double(fileID, hyaiid, tarray);

          for ( int i = 0; i < mlev; ++i )
            {
              tarray[2*i  ] = vct[ilev+i];
              tarray[2*i+1] = vct[ilev+i+1];
            }
          cdf_put_var_double(fileID, hybiid, tarray);
        }

      for ( int i = 0; i < mlev; ++i )
        tarray[i] = (vct[i] + vct[i+1]) * 0.5;
      cdf_put_var_double(fileID, hyamid, tarray);

      for ( int i = 0; i < mlev; ++i )
        tarray[i] = (vct[ilev+i] + vct[ilev+i+1]) * 0.5;
      cdf_put_var_double(fileID, hybmid, tarray);

      free(tarray);
      free(vct);
    }
}

struct attTxtTab { const char *txt; size_t txtLen; };

static
void 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)
{
  const int fileID = streamptr->fileID;

  if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

  cdf_def_dim(fileID, axisname, dimlen, dimID);
  cdf_def_var(fileID, axisname, (nc_type) xtype, 1, dimID,  ncvaridp);
  const 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 }
      }
    };

    const 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);
  }

  cdf_enddef(fileID);
  streamptr->ncmode = 2;

  cdf_put_var_double(fileID, ncvarid, zaxisInqLevelsPtr(zaxisID));

  cdf_def_vct_echam(streamptr, zaxisID);

  if ( *dimID == CDI_UNDEFID )
    streamptr->zaxisID[zaxisindex] = type == ZAXIS_HYBRID
      ? streamptr->vct.mlevID : streamptr->vct.ilevID;
}

static
void 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)
{
  const int fileID = streamptr->fileID;
  if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

  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");

  char p0name[CDI_MAX_NAME]; p0name[0] = 0;
  double p0value = 1;
  int p0varid = CDI_UNDEFID;
  const 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", strlen(longname), longname);
      static const char units[] = "Pa";
      cdf_put_att_text(fileID, p0varid, "units", strlen(units), 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;
  if ( zlongname[0] == 0 ) strcpy(zlongname, "hybrid sigma pressure coordinate");
  length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, zunits, &length);
  if ( zunits[0] == 0 ) strcpy(zunits, "1");

  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",
      sname_v[] = "atmosphere_hybrid_sigma_pressure_coordinate",
      axis[] = "axis",
      axis_v[] = "Z",
      direction[] = "positive",
      direction_v[] = "down";
    struct attTxtTab2 tab[] = {
      { sname, sname_v, sizeof (sname_v) - 1 },
      { axis, axis_v, sizeof (axis_v) - 1 },
      { direction, direction_v, sizeof (direction_v) - 1 },
    };
    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);

    cdf_put_att_text(fileID, ncvarid, "long_name", strlen(zlongname), zlongname);
    cdf_put_att_text(fileID, ncvarid, "units", strlen(zunits), zunits);
  }

  size_t len = 0;
  char txt[CDI_MAX_NAME];
  if ( p0status == 0 )
    len = (size_t)(sprintf(txt, "%s%s %s%s", "a: a b: b p0: ", p0name, "ps: ", psname));
  else
    len = (size_t)(sprintf(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(4*dimlen*sizeof(double));
  double *levels = buffer;
  double *lbounds = buffer + 2*dimlen;
  double *ubounds = buffer + 3*dimlen;

  if ( zaxisInqLevels(zaxisID, NULL) )
    zaxisInqLevels(zaxisID, levels);
  else
    for ( size_t i = 0; i < dimlen; ++i ) levels[i] = 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) )
    {
      const 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);
          {
            static const char sname[] = "standard_name",
              sname_v[] = "atmosphere_hybrid_sigma_pressure_coordinate";
            struct attTxtTab2 tab[] = {
              { sname, sname_v, sizeof (sname_v) - 1 },
            };
            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);
            cdf_put_att_text(fileID, ncbvarid, "units", strlen(zunits), zunits);
          }

          if ( p0status == 0 )
            len = (size_t)(sprintf(txt, "%s%s %s%s", "a: a_bnds b: b_bnds p0: ", p0name, "ps: ", psname));
          else
            len = (size_t)(sprintf(txt, "%s%s", "ap: ap_bnds b: b_bnds ps: ", psname));
          cdf_put_att_text(fileID, ncbvarid, "formula_terms", len, txt);
        }
    }

  cdf_enddef(fileID);
  streamptr->ncmode = 2;

  cdf_put_var_double(fileID, ncvarid, levels);

  if ( p0varid != CDI_UNDEFID ) cdf_put_var_double(fileID, p0varid, &p0value);

  if ( ncbvarid != CDI_UNDEFID )
    {
      double *zbounds = buffer;
      for ( size_t i = 0; i < dimlen; ++i )
        {
          zbounds[2*i  ] = lbounds[i];
          zbounds[2*i+1] = ubounds[i];
        }
      cdf_put_var_double(fileID, ncbvarid, zbounds);
    }

  cdf_def_vct_cf(streamptr, zaxisID, *dimID, nvdimID, p0status, p0value);

  if ( *dimID == CDI_UNDEFID )
    streamptr->zaxisID[zaxisindex] = type == ZAXIS_HYBRID
      ? streamptr->vct.mlevID : streamptr->vct.ilevID;

  free(buffer);
}

static
void cdf_def_zaxis_hybrid(stream_t *streamptr, int type, int *ncvarid, int zaxisID, int zaxisindex, int xtype, size_t dimlen, int *dimID, char *axisname)
{
  void (*def_zaxis_hybrid_delegate)(stream_t *streamptr, int type, int *ncvarid, int zaxisID, int zaxisindex, int xtype, size_t dimlen, int *dimID, char *axisname)
    = ( (!CDI_CMOR_Mode && CDI_Convention == CDI_CONVENTION_ECHAM)
        || type == ZAXIS_HYBRID_HALF )
    ? cdf_def_zaxis_hybrid_echam : cdf_def_zaxis_hybrid_cf;
  def_zaxis_hybrid_delegate(streamptr, type, ncvarid, zaxisID, zaxisindex, xtype, dimlen, dimID, axisname);
}

static
void cdfDefZaxisUUID(stream_t *streamptr, int zaxisID)
{
  unsigned char uuidOfVGrid[CDI_UUID_SIZE];
  int length = CDI_UUID_SIZE;
  memset(uuidOfVGrid, 0, length);
  cdiInqKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuidOfVGrid, &length);
  if ( uuidOfVGrid[0] != 0 )
    {
      char uuidOfVGridStr[37];
      cdiUUID2Str(uuidOfVGrid, uuidOfVGridStr);
      if ( uuidOfVGridStr[0] != 0 && strlen(uuidOfVGridStr) == 36 )
        {
          const int fileID  = streamptr->fileID;
          if ( streamptr->ncmode == 2 ) cdf_redef(fileID);
          cdf_put_att_text(fileID, NC_GLOBAL, "uuidOfVGrid", 36, uuidOfVGridStr);
          if ( streamptr->ncmode == 2 ) cdf_enddef(fileID);
        }
    }
}

#ifndef USE_MPI
static
void cdfDefZaxisChar(stream_t *streamptr, int zaxisID, char *axisname, int *dimID, size_t dimlen, int zaxisindex)
{
  const int fileID  = streamptr->fileID;
  int ncvarID = CDI_UNDEFID;
  if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

  /* Check StrlenID */
  char strlen[8] = "strlen\0";
  const 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(zaxisID, CDI_GLOBAL, fileID, ncvarID);

      streamptr->nczvarID[zaxisindex] = ncvarID;
      cdf_enddef(fileID);

      /* Write Stringvalues */
      size_t start[2], count[2];
      start[1] = 0;
      count[0] = 1;
      count[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
void cdfDefZaxis(stream_t *streamptr, int zaxisID)
{
  /*  char zaxisname0[CDI_MAX_NAME]; */
  int dimID = CDI_UNDEFID;
  int ncvarid = CDI_UNDEFID, ncbvarid = CDI_UNDEFID;
  const int xtype = zaxisInqDatatype(zaxisID) == CDI_DATATYPE_FLT32 ? NC_FLOAT : NC_DOUBLE;

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);

  const int nzaxis = vlistNzaxis(vlistID);

  const size_t dimlen = (size_t)zaxisInqSize(zaxisID);
  const int type = zaxisInqType(zaxisID);

  int ndims = 1;

  if ( dimlen == 1 )
    {
      bool is_scalar = zaxisInqScalar(zaxisID) > 0;
      if ( !is_scalar && CDI_CMOR_Mode )
        {
          is_scalar = true;
          zaxisDefScalar(zaxisID);
        }

      if ( is_scalar ) ndims = 0;
      if ( CDI_Reduce_Dim ) return;

      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;
        }
    }

  char axisname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, axisname, &length);

  if ( dimID == CDI_UNDEFID )
    {
      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);

      if ( type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF )
        {
          cdf_def_zaxis_hybrid(streamptr, type, &ncvarid, zaxisID, zaxisindex, xtype, dimlen, &dimID, axisname);

          int natts;
          cdiInqNatts(zaxisID, CDI_GLOBAL, &natts);
          if ( natts > 0 && streamptr->ncmode == 2 ) cdf_redef(fileID);
          cdfDefineAttributes(zaxisID, CDI_GLOBAL, fileID, ncvarid);
          if ( natts > 0 && streamptr->ncmode == 2 ) cdf_enddef(fileID);
        }
#ifndef USE_MPI
      else if ( type == ZAXIS_CHAR )
        cdfDefZaxisChar(streamptr, zaxisID, axisname, &dimID, dimlen, zaxisindex);
#endif
      else
        {
          dimID = checkDimName(fileID, dimlen, dimname);

          if ( streamptr->ncmode == 2 ) 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');

              {
                const 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 )
                  {
                    const 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");

              if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
                {
                  int nvdimID = CDI_UNDEFID;
                  const size_t nvertex = 2;
                  if ( nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR )
                    cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);

                  if ( nvdimID != CDI_UNDEFID )
                    {
                      const 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", strlen(axisname), axisname);
                    }
                }

              cdfDefineAttributes(zaxisID, CDI_GLOBAL, fileID, ncvarid);
            }

          cdf_enddef(fileID);
          streamptr->ncmode = 2;

          if ( zaxisInqLevels(zaxisID, NULL) )
            {
              cdf_put_var_double(fileID, ncvarid, zaxisInqLevelsPtr(zaxisID));

              if ( ncbvarid != CDI_UNDEFID )
                {
                  double *buffer = (double *) malloc(4*dimlen*sizeof(double));
                  double *zbounds = buffer;
                  double *lbounds = buffer + 2*dimlen;
                  double *ubounds = buffer + 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];
                    }

                  cdf_put_var_double(fileID, ncbvarid, zbounds);

                  free(buffer);
                }

              if ( ndims == 0 ) streamptr->nczvarID[zaxisindex] = ncvarid;
            }
        }
    }

  if ( dimID != CDI_UNDEFID )
    streamptr->zaxisID[zaxisindex] = dimID;
}

static
void cdf_def_mapping(stream_t *streamptr, int gridID)
{
  char gmapname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
  if ( gmapname[0] )
    {
      nc_type gmapvartype = NC_INT;
      int datatype = -1;
      int status = cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARTYPE, &datatype);
      if (status == CDI_NOERR) gmapvartype = (nc_type) datatype;
      char gmapvarname[CDI_MAX_NAME];
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname, &length);

      const int fileID = streamptr->fileID;
      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      int ncvarid;
      const int ncerrcode = nc_def_var(fileID, gmapvarname, (nc_type) gmapvartype, 0, NULL, &ncvarid);
      if ( ncerrcode == NC_NOERR )
        cdfDefineAttributes(gridID, CDI_GLOBAL, fileID, ncvarid);

      cdf_enddef(fileID);

      if ( ncerrcode == NC_NOERR && gmapvartype != NC_CHAR )
        {
          int dummy = 1;
          cdf_put_var_int(fileID, ncvarid, &dummy);
        }
    }
}

static
void cdfDefCharacter(stream_t *streamptr, int gridID, int gridindex, int xory, int strlen)
{
  if ( streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X] != CDI_UNDEFID ) return;

  const size_t dimlen = ( xory == 0 ) ? gridInqXsize(gridID) : gridInqYsize(gridID);
  int dimID, strlenID;
  ncgrid_t *ncgrid = streamptr->ncgrid;

  // Check for all grids up to gridindex whether it already is defined

  for ( int index = 0; index < gridindex; index++ )
    {
      const int gridID0 = ncgrid[index].gridID;
      const 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;
        }
    }

  const 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, (xory == 0) ? CDI_XAXIS : CDI_YAXIS, CDI_KEY_DIMNAME, dimname, &length);
  if ( dimname[0] == 0 ) { memcpy(dimname, "region", 7); dimname[6] = 0; }
  dimID = checkDimName(fileID, dimlen, dimname);
  if ( dimID == CDI_UNDEFID ) cdf_def_dim(fileID, dimname, dimlen, &dimID);

  // Define strlength dim

  strcpy(dimname, "strlen");
  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(dimlen * sizeof(char *));
  for ( size_t i = 0; i < dimlen; i++ )
    cvals[i] = (char*) Malloc(strlen * sizeof(char));
  int ncaxisid;
  if ( xory == 0 )
    {
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_NAME, axisname, &length);
      gridInqXCvals(gridID, cvals);
    }
  else
    {
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_NAME, axisname, &length);
      gridInqXCvals(gridID, cvals);
    }
  int status = nc_inq_varid(fileID, axisname, &ncaxisid);
  if ( status != NC_NOERR )
    {
      cdf_def_var(fileID, axisname, NC_CHAR, 2, dimIDs, &ncaxisid);
      if ( xory == 0 )
        cdfPutGridStdAtts(fileID, ncaxisid, gridID, 'X');
      else
        cdfPutGridStdAtts(fileID, ncaxisid, gridID, 'Y');
    }
  else
    return;
  cdf_enddef(fileID);

  // Write Var

  size_t start[2], count[2];
  start[1] = 0;
  count[0] = 1;
  count[1] = strlen;
  for (size_t i = 0; i < dimlen; i++)
    {
      start[0] = i;
      status = nc_put_vara_text(fileID, ncaxisid, start, count, cvals[i]);
    }

  ncgrid[gridindex].gridID = gridID;
  if ( xory == 0 )
    {
      ncgrid[gridindex].ncIDs[CDF_DIMID_X] = dimID;
      ncgrid[gridindex].ncIDs[CDF_VARID_X] = ncaxisid;
    }
  else
    {
      ncgrid[gridindex].ncIDs[CDF_DIMID_Y] = dimID;
      ncgrid[gridindex].ncIDs[CDF_VARID_Y] = ncaxisid;
    }
  streamptr->ncmode = 2;
}

static
void cdfDefRgrid(stream_t *streamptr, int gridID, int gridindex)
{
  ncgrid_t *ncgrid = streamptr->ncgrid;

  ncgrid[gridindex].gridID = gridID;

  {
    const size_t dimlen = gridInqSize(gridID);

    struct idSearch search = cdfSearchIDBySize(0, (size_t)gridindex, ncgrid, CDF_DIMID_X,
                                               GRID_GAUSSIAN_REDUCED, (int)dimlen, gridInqType, gridInqSize);
    const int iz = search.numNonMatching;
    int dimID = search.foundID;

    if ( dimID == CDI_UNDEFID )
      {
        const int fileID  = streamptr->fileID;

        char axisname[16] = "rgridX";
        if ( iz == 0 ) axisname[5] = '\0';
        else           sprintf(&axisname[5], "%1d", iz+1);

        if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

        cdf_def_dim(fileID, axisname, dimlen, &dimID);

        cdf_enddef(fileID);
        streamptr->ncmode = 2;
      }

    ncgrid[gridindex].ncIDs[CDF_DIMID_X] = dimID;
  }

  {
    const size_t dimlen = gridInqYsize(gridID);

    struct idSearch search = cdfSearchIDBySize(0, (size_t)gridindex, ncgrid, CDF_DIMID_RP,
                                               GRID_GAUSSIAN_REDUCED, (int)dimlen, gridInqType, gridInqSize);
    const int iz = search.numNonMatching;
    int dimID = search.foundID;

    if ( dimID == CDI_UNDEFID )
      {
        const int fileID  = streamptr->fileID;

        char axisname[32] = "reduced_pointsX";
        if ( iz == 0 ) axisname[14] = '\0';
        else           sprintf(&axisname[5], "%1d", iz+1);

        if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

        cdf_def_dim(fileID, axisname, dimlen, &dimID);

        int ncvarid = CDI_UNDEFID;

        int ndims = 1;
        const nc_type xtype = NC_INT;
        cdf_def_var(fileID, axisname, xtype, ndims, &dimID, &ncvarid);

        cdf_enddef(fileID);
        streamptr->ncmode = 2;

        int *reducedPoints = (int*) Malloc(dimlen*sizeof(int));
        gridInqReducedPoints(gridID, reducedPoints);
        cdf_put_var_int(fileID, ncvarid, reducedPoints);
        Free(reducedPoints);

        ncgrid[gridindex].ncIDs[CDF_VARID_RP] = ncvarid;
      }

    ncgrid[gridindex].ncIDs[CDF_DIMID_RP] = dimID;
  }
}

static
void cdfDefGdim(stream_t *streamptr, int gridID, int gridindex)
{
  ncgrid_t *ncgrid = streamptr->ncgrid;
  int dimID = CDI_UNDEFID;

  const size_t dimlen = gridInqSize(gridID);

  if ( gridInqYsize(gridID) == 0 )
    {
      struct idSearch search
        = cdfSearchIDBySize(0, (size_t)gridindex, ncgrid, CDF_DIMID_X,
                            GRID_GENERIC, (int)dimlen, gridInqType, gridInqSize);
      dimID = search.foundID;
    }

  if ( gridInqXsize(gridID) == 0 )
    {
      struct idSearch search
        = cdfSearchIDBySize(0, (size_t)gridindex, ncgrid, CDF_DIMID_Y,
                            GRID_GENERIC, (int)dimlen, gridInqType, gridInqSize);
      dimID = search.foundID;
    }

  if ( dimID == CDI_UNDEFID )
    {
      int fileID  = streamptr->fileID;
      char dimname[CDI_MAX_NAME];
      strcpy(dimname, "gsize");

      dimID = checkDimName(fileID, dimlen, dimname);

      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      if ( dimID == CDI_UNDEFID ) cdf_def_dim(fileID, dimname, dimlen, &dimID);

      cdf_enddef(fileID);
      streamptr->ncmode = 2;
    }

  ncgrid[gridindex].gridID = gridID;
  ncgrid[gridindex].ncIDs[CDF_DIMID_X] = dimID;
}

static
void cdfDefGrid(stream_t *streamptr, int gridID, int gridindex)
{
  if ( streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X] != CDI_UNDEFID ) return;

  const int gridtype = gridInqType(gridID);
  const size_t size = gridInqSize(gridID);

  if ( CDI_Debug )
    Message("gridtype = %d  size = %zu", gridtype, size);

  if ( CDI_Reduce_Dim && size == 1 ) // no grid information
    {
      streamptr->ncgrid[gridindex].gridID = gridID;
      return;
    }

  if ( gridtype == GRID_GAUSSIAN    ||
       gridtype == GRID_LONLAT      ||
       gridtype == GRID_PROJECTION  ||
       gridtype == GRID_GENERIC )
    {
      if ( gridtype == GRID_GENERIC )
        {
          if ( size == 1 && gridInqXsize(gridID) == 0 && gridInqYsize(gridID) == 0 )
            {
              // no grid information
              streamptr->ncgrid[gridindex].gridID = gridID;
            }
          else
            {
              bool lx = false, ly = false;
              if ( gridInqXsize(gridID) /*&& gridInqXvals(gridID, NULL) */ )
                {
                  cdfDefXaxis(streamptr, gridID, gridindex, 1, false);
                  lx = true;
                }

              if ( gridInqYsize(gridID) /*&& gridInqYvals(gridID, NULL) */ )
                {
                  cdfDefYaxis(streamptr, gridID, gridindex, 1, false);
                  ly = true;
                }

              if ( !lx && !ly ) cdfDefGdim(streamptr, gridID, gridindex);
            }
        }
      else
        {
          const int ndims = !(gridtype == GRID_LONLAT && size == 1 && !gridInqHasDims(gridID));

          if ( gridInqXsize(gridID) ) cdfDefXaxis(streamptr, gridID, gridindex, ndims, false);
          if ( gridInqYsize(gridID) ) cdfDefYaxis(streamptr, gridID, gridindex, ndims, false);

          cdf_def_mapping(streamptr, gridID);
        }
    }
  else if ( gridtype == GRID_CURVILINEAR )
    {
      cdfDefCurvilinear(streamptr, gridID, gridindex);
    }
  else if ( gridtype == GRID_UNSTRUCTURED )
    {
      cdfDefUnstructured(streamptr, gridID, gridindex);
    }
  else if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      cdfDefRgrid(streamptr, gridID, gridindex);
      if ( gridInqYsize(gridID) ) cdfDefYaxis(streamptr, gridID, gridindex, 1, true);
    }
  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);
    }
  else if ( gridtype == GRID_CHARXY )
    {
      int strlen = 0;
      if ( (strlen = gridInqXIsc(gridID)) )
        cdfDefCharacter(streamptr, gridID, gridindex, 0, strlen);
      else
        if ( gridInqXsize(gridID) ) cdfDefXaxis(streamptr, gridID, gridindex, 1, false);
      if ( (strlen = gridInqYIsc(gridID)) )
        cdfDefCharacter(streamptr, gridID, gridindex, 1, strlen);
      else
        if ( gridInqYsize(gridID) ) cdfDefYaxis(streamptr, gridID, gridindex, 1, false);
    }
  else
    {
      Error("Unsupported grid type: %s", gridNamePtr(gridtype));
    }
}


void cdfDefCoordinateVars(stream_t *streamptr)
{
  const int vlistID = streamptr->vlistID;
  if ( vlistID == CDI_UNDEFID ) Error("Internal problem! vlist undefined for streamptr %p", streamptr);

  if ( vlistHasTime(vlistID) ) cdfDefTime(streamptr);

  const int ngrids = vlistNgrids(vlistID);
  if ( 2*ngrids > MAX_GRIDS_PS ) Error("Internal problem! Too many grids per stream (max=%d)\n", MAX_GRIDS_PS);
  for ( int index = 0; index < 2*ngrids; ++index )
    {
      streamptr->ncgrid[index].gridID = CDI_UNDEFID;
      for (size_t i = 0; i < CDF_SIZE_ncIDs; ++i)
        streamptr->ncgrid[index].ncIDs[i] = CDI_UNDEFID;
    }

  for ( int index = 0; index < ngrids; ++index )
    {
      const int gridID = vlistGrid(vlistID, index);
      cdfDefGrid(streamptr, gridID, index);
    }
  {
    int index = ngrids-1;
    for ( int i = 0; i < ngrids; ++i )
      {
        int gridID = vlistGrid(vlistID, i);
        int projID = gridInqProj(gridID);
        if ( projID != CDI_UNDEFID ) cdfDefGrid(streamptr, projID, ++index);
      }
  }
  const int nzaxis = vlistNzaxis(vlistID);
  for ( int index = 0; index < nzaxis; ++index )
    {
      const int zaxisID = vlistZaxis(vlistID, index);
      if ( streamptr->zaxisID[index] == CDI_UNDEFID ) cdfDefZaxis(streamptr, zaxisID);
    }

  if ( streamptr->ncmode != 2 )
    {
      cdf_enddef(streamptr->fileID);
      streamptr->ncmode = 2;
    }
}

#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 <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;

  /* fprintf(stderr, "time has bounds\n"); */
  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
void cdfDefTimeUnits(char *unitstr, taxis_t *taxis)
{
  if ( taxis->units && taxis->units[0] )
    {
      strcpy(unitstr, taxis->units);
    }
  else
    {
      unitstr[0] = 0;

      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));
          strcpy(unitstr, unitstrfmt[fmtidx]);
        }
      else
        {
          int year, month, day, hour, minute, second;
          cdiDecodeDate(taxis->rdate, &year, &month, &day);
          cdiDecodeTime(taxis->rtime, &hour, &minute, &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;

          sprintf(unitstr, "%s since %d-%d-%d %02d:%02d:%02d",
                  tunitNamePtr(timeunit), year, month, day, hour, minute, second);
        }
    }
}

static
void cdfDefForecastTimeUnits(char *unitstr, int timeunit)
{
  unitstr[0] = 0;

  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;

  strcpy(unitstr, 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 default_name[] = "time";

  if ( streamptr->basetime.ncvarid != CDI_UNDEFID ) return;

  const int fileID = streamptr->fileID;

  if ( streamptr->ncmode == 0 ) streamptr->ncmode = 1;
  if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

  taxis_t *taxis = &streamptr->tsteps[0].taxis;

  const char *taxis_name = (taxis->name && taxis->name[0]) ? taxis->name : default_name ;
  int time_dimid;
  cdf_def_dim(fileID, taxis_name, NC_UNLIMITED, &time_dimid);
  streamptr->basetime.ncdimid = time_dimid;

  const int datatype = taxis->datatype;
  const nc_type xtype = (datatype == CDI_DATATYPE_INT32) ? NC_INT : ((datatype == CDI_DATATYPE_FLT32) ? NC_FLOAT : NC_DOUBLE);

  int time_varid;
  cdf_def_var(fileID, taxis_name, xtype, 1, &time_dimid, &time_varid);
  streamptr->basetime.ncvarid = time_varid;

#ifdef  HAVE_NETCDF4
  if ( streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C )
    {
      const size_t chunk = 512;
      cdf_def_var_chunking(fileID, time_varid, NC_CHUNKED, &chunk);
    }
#endif

  static const char timeStr[] = "time";
  cdf_put_att_text(fileID, time_varid, "standard_name", sizeof(timeStr) - 1, timeStr);

  if ( taxis->longname && taxis->longname[0] )
    cdf_put_att_text(fileID, time_varid, "long_name", strlen(taxis->longname), taxis->longname);

  if ( taxis->has_bounds )
    {
      int time_bndsid = cdfDefTimeBounds(fileID, time_varid, time_dimid, taxis_name, taxis);
      streamptr->basetime.ncvarboundsid = time_bndsid;
    }

  char unitstr[CDI_MAX_NAME];
  cdfDefTimeUnits(unitstr, taxis);
  size_t len = strlen(unitstr);
  if (len) cdf_put_att_text(fileID, time_varid, "units", len, unitstr);

  if ( taxis->calendar != -1 )
    {
      cdfDefCalendar(fileID, time_varid, taxis->calendar);
    }

  if ( taxis->type == TAXIS_FORECAST )
    {
      int leadtimeid;
      cdf_def_var(fileID, "leadtime", xtype, 1, &time_dimid, &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 lname[] = "Time elapsed since the start of the forecast";
      cdf_put_att_text(fileID, leadtimeid, "long_name", sizeof(lname) - 1, lname);

      cdfDefForecastTimeUnits(unitstr, taxis->fc_unit);
      len = strlen(unitstr);
      if ( len ) cdf_put_att_text(fileID, leadtimeid, "units", len, unitstr);
    }

  cdf_put_att_text(fileID, time_varid, "axis", 1, "T");

  if ( streamptr->ncmode == 2 ) cdf_enddef(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 CDI_DATETIME_H
#define CDI_DATETIME_H

typedef struct
{
  long date;
  long time;
}
DateTime;

static inline int
datetimeDiffer(DateTime dt1, DateTime dt2)
{
  return (2 * ((dt1.date > dt2.date) - (dt1.date < dt2.date))
          + (dt1.time > dt2.time) - (dt1.time < dt2.time)) != 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:
 */
#if HAVE_CONFIG_H
#endif

#include <limits.h>
#include <stdio.h>


#ifdef HAVE_LIBCGRIBEX



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 {
  int param;
  int level1;
  int level2;
  int ltype;
  int 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 cgribexInit(cgribexrec_t *cgribexp)
{
  cgribexp->sec2len = 4096;
  cgribexp->sec2 = (int *) Malloc(cgribexp->sec2len*sizeof(int));
}


void *cgribexNew()
{
  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);
    }
}


int grib1Sections(unsigned char *gribbuffer, long gribbufsize, unsigned char **pdsp,
		  unsigned char **gdsp, unsigned char **bmsp, unsigned char **bdsp, long *gribrecsize);

static
size_t cgribexSection2Length(void *gribbuffer, size_t gribbuffersize)
{
  long 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 = (unsigned) ((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 )
	{
	  Message("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 )
	{
	  Message("Time range indicator %d unsupported, set to 0!", timerange);
	  lprint = false;
	}
      break;
    }
  // clang-format on

  return tsteptype;
}

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 )
    {
      const bool ijDirectionIncrementGiven = gribbyte_get_bit(ISEC2_ResFlag, 1);
      uvRelativeToGrid = gribbyte_get_bit(ISEC2_ResFlag, 5);

      const size_t nvalues = (size_t) ISEC4_NumValues;
      const size_t nlon = (size_t) ISEC2_NumLon;
      const 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 / (grid->x.size-1);

            /* correct xinc if necessary */
            if ( ISEC2_FirstLon == 0 && ISEC2_LastLon > 354000 && ISEC2_LastLon < 360000 )
              {
                double xinc = 360. / 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 / (grid->y.size - 1);
          }
        grid->y.first = ISEC2_FirstLat * 0.001;
        grid->y.last  = ISEC2_LastLat  * 0.001;
        grid->y.flag  = 2;
      }
    }
  else if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      const bool ijDirectionIncrementGiven = gribbyte_get_bit(ISEC2_ResFlag, 1);
      uvRelativeToGrid = gribbyte_get_bit(ISEC2_ResFlag, 5);
      grid->np      = ISEC2_NumPar;
      grid->size    = (size_t)ISEC4_NumValues;
      grid->reducedPoints  = ISEC2_ReducedPointsPtr;
      grid->reducedPointsSize = (size_t)ISEC2_NumLat;
      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 ( grid->x.size > 1 )
          {
            if ( ISEC2_LastLon < ISEC2_FirstLon && ISEC2_LastLon < 0 ) ISEC2_LastLon += 360000;

            if ( ijDirectionIncrementGiven && ISEC2_LonIncr > 0 )
              grid->x.inc = ISEC2_LonIncr * 0.001;
            else
              grid->x.inc = (ISEC2_LastLon - ISEC2_FirstLon) * 0.001 / (grid->x.size - 1);
          }
        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 / (grid->y.size - 1);
          }
        grid->y.first = ISEC2_FirstLat * 0.001;
        grid->y.last  = ISEC2_LastLat  * 0.001;
        grid->y.flag  = 2;
      }
    }
  else if ( projtype == CDI_PROJ_LCC )
    {
      uvRelativeToGrid = gribbyte_get_bit(ISEC2_ResFlag, 5);

      const size_t nvalues = (size_t) ISEC4_NumValues;
      const size_t nlon = (size_t) ISEC2_NumLon;
      const 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;
    }
  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 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;
  if ( datatype > 32 ) datatype = CDI_DATATYPE_PACK32;
  if ( datatype <  0 ) datatype = CDI_DATATYPE_PACK;

  const int vlistID = streamptr->vlistID;
  const int tsID    = streamptr->curTsID;
  const int recID   = recordNewEntry(streamptr, tsID);
  record_t *record = &streamptr->tsteps[tsID].records[recID];

  const 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     = leveltype;
  record->tsteptype = (short)tsteptype;

  grid_t *gridptr = (grid_t*) Malloc(sizeof(*gridptr));
  const bool uvRelativeToGrid = cgribexGetGrid(streamptr, isec2, isec4, gridptr, iret);

  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, gridptr, 0);
  int gridID = gridAdded.Id;
  if ( gridAdded.isNew )
    {
      if ( gridptr->reducedPointsSize )
        {
          const size_t reducedPointsSize = (size_t) gridptr->reducedPointsSize;
          int *reducedPoints = gridptr->reducedPoints;
          gridptr->reducedPoints = (int*) Malloc(reducedPointsSize * sizeof(int));
          memcpy(gridptr->reducedPoints, reducedPoints, reducedPointsSize * sizeof(int));
        }
      else if ( gridptr->projtype == CDI_PROJ_RLL )
        {
          const double xpole =   ISEC2_LonSP*0.001 - 180;
          const double ypole = - ISEC2_LatSP*0.001;
          const double angle = - FSEC2_RotAngle;
          gridDefParamRLL(gridID, xpole, ypole, angle);
        }
      else if ( gridptr->projtype == CDI_PROJ_LCC )
        {
          const bool earthIsOblate = gribbyte_get_bit(ISEC2_ResFlag, 2);
          const double a = earthIsOblate ? 6378160. : 6367470.;
          const double rf = earthIsOblate ? 297.0 : 0;
          const double xval_0 = ISEC2_FirstLon * 0.001;
          const double yval_0 = ISEC2_FirstLat * 0.001;
          const double lon_0  = ISEC2_Lambert_Lov * 0.001;
          double lat_1  = ISEC2_Lambert_LatS1 * 0.001;
          double lat_2  = ISEC2_Lambert_LatS2 * 0.001;
          const bool lsouth = gribbyte_get_bit(ISEC2_Lambert_ProjFlag, 1);
          if ( lsouth ) { lat_1 = -lat_1; lat_2 = -lat_2; }

          const double lat_0 = lat_2;
          double x_0 = CDI_Grid_Missval;
          double y_0 = CDI_Grid_Missval;

          if ( proj_lonlat_to_lcc_func )
            {
              x_0 = xval_0; y_0 = yval_0;
              proj_lonlat_to_lcc_func(CDI_Grid_Missval, lon_0, lat_0, lat_1, lat_2, a, rf, (size_t)1, &x_0, &y_0);
              if ( IS_NOT_EQUAL(x_0, CDI_Grid_Missval) && IS_NOT_EQUAL(y_0, CDI_Grid_Missval) )
                { x_0 = -x_0; y_0 = -y_0; }
            }
          gridDefParamLCC(gridID, CDI_Grid_Missval, lon_0, lat_0, lat_1, lat_2, a, rf, xval_0, yval_0, x_0, y_0);
        }
    }
  else
    Free(gridptr);

  const int zaxistype = grib1ltypeToZaxisType(leveltype);
  if ( zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_HYBRID_HALF )
    {
      const size_t vctsize = (size_t)ISEC2_NumVCP;
      double *vctptr = &fsec2[10];
      varDefVCT(vctsize, vctptr);
    }

  const 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);

  record->varID = (short)varID;
  record->levelID = (short)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 )
    {
      const int center = ISEC1_CenterID;
      const 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;

  memset(isec1, 0, 256*sizeof(int));
  memset(isec2, 0, 32*sizeof(int));

  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)
{
  int tsteptype = cgribexGetTsteptype(trange);

  compvar_t compVar;
  compVar.param     = param;
  compVar.level1    = level1;
  compVar.level2    = level2;
  compVar.ltype     = leveltype;
  compVar.tsteptype = tsteptype;

  return compVar;
}

static inline
int cgribexVarCompare(compvar_t compVar, 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)
    |           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(const int fileID)
{
  int nskip = CDI_Skip_Records;
  while (nskip-- > 0)
    {
      const size_t recsize = gribGetSize(fileID);
      if (recsize == 0) Error("Skipping of %d records failed!", CDI_Skip_Records);

      const off_t recpos = fileGetPos(fileID);
      fileSetPos(fileID, recpos, SEEK_CUR);
    }
}

int cgribexScanTimestep1(stream_t *streamptr)
{
  int lmv = 0, iret = 0;
  off_t recpos = 0;
  void *gribbuffer = NULL;
  size_t buffersize = 0;
  int leveltype = 0, level1 = 0, level2 = 0;
  DateTime datetime0 = { .date = 10101, .time = 0 };
  unsigned recID;
  int nrecs_scanned = 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->cgribexp;

  const int tsID = tstepsNewEntry(streamptr);
  if (tsID != 0) Error("Internal problem! tstepsNewEntry returns %d", tsID);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  const int fileID = streamptr->fileID;

  if (CDI_Skip_Records) cgribexSkipRecords(fileID);

  unsigned nrecs = 0;
  while (true)
    {
      const size_t recsize = gribGetSize(fileID);
      recpos = fileGetPos(fileID);

      if ( recsize == 0 )
	{
	  if ( nrecs == 0 ) Error("No GRIB records found!");
	  streamptr->ntsteps = 1;
	  break;
	}

      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;

      const int comptype = grbDecompress(recsize, &buffersize, &gribbuffer);

      const 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;

      nrecs_scanned++;
      cgribexDecodeHeader(cgribexp, (int *) gribbuffer, (int)recsize, &lmv, &iret);

      const int param = cdiEncodeParam(ISEC1_Parameter, ISEC1_CodeTable, 255);
      cdiParamToString(param, paramstr, sizeof(paramstr));

      cgribexGetLevel(isec1, &leveltype, &level1, &level2);

      int vdate, sdate;
      int vtime, stime;
      gribDateTimeX(isec1, &vdate, &vtime, &sdate, &stime);
      DateTime datetime = { .date = vdate, .time = vtime };

      if ( nrecs == 0 )
	{
          datetime0 = datetime;
	  fcast = cgribexTimeIsFC(isec1);
	  taxis->unit = cgribexGetTimeUnit(isec1);
	  taxis->rdate = gribRefDate(isec1);
	  taxis->rtime = gribRefTime(isec1);
          taxis->sdate = sdate;
          taxis->stime = stime;
          taxis->vdate = vdate;
          taxis->vtime = vtime;
	}
      else
	{
	  compvar_t compVar = cgribexVarSet(param, level1, level2, leveltype, ISEC1_TimeRange);
          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 ( datetimeDiffer(datetime, datetime0) )
		  {
                    gribWarning("Inconsistent verification time!", nrecs_scanned, tsID+1, paramstr, level1, level2);
		    warn_time = false;
		  }
	    }
	  else
	    {
	      if ( datetimeDiffer(datetime, datetime0) ) break;

	      if ( recID < nrecs )
		{
		  gribWarning("Parameter already exist, skipped!", nrecs_scanned, 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) %8d %6d", nrecs_scanned, paramstr, level1, level2, vdate, vtime);

      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;
  const int taxisID = taxisCreate(taxis->type);

  const 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)
{
  int lmv = 0, iret = 0;
  off_t recpos = 0;
  int leveltype = 0, level1 = 0, level2 = 0;
  DateTime datetime0 = { LONG_MIN, LONG_MIN };
  int recID = 0;
  bool warn_numavg = true;
  char paramstr[32];

  streamptr->curTsID = 1;

  cgribexrec_t *cgribexp = (cgribexrec_t *) streamptr->record->cgribexp;
  int *isec1 = cgribexp->sec1;
  int *isec2 = cgribexp->sec2;

  const int fileID  = streamptr->fileID;
  const 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);
  record_t *records = streamptr->tsteps[tsID].records;

  const int nrecords = streamScanInitRecords2(streamptr);

  int nrecs_scanned = nrecords;
  int rindex = 0;
  while (true)
    {
      if ( rindex > nrecords ) break;

      const size_t recsize = gribGetSize(fileID);
      recpos = fileGetPos(fileID);
      if ( recsize == 0 )
	{
	  streamptr->ntsteps = 2;
	  break;
	}

      ensureBufferSize(recsize, &buffersize, &gribbuffer);

      size_t readsize = recsize;
      if (gribRead(fileID, gribbuffer, &readsize)) break;

      grbDecompress(recsize, &buffersize, &gribbuffer);

      nrecs_scanned++;
      cgribexDecodeHeader(cgribexp, (int *) gribbuffer, (int)recsize, &lmv, &iret);

      const int param = cdiEncodeParam(ISEC1_Parameter, ISEC1_CodeTable, 255);
      cdiParamToString(param, paramstr, sizeof(paramstr));

      cgribexGetLevel(isec1, &leveltype, &level1, &level2);

      int vdate, sdate;
      int vtime, stime;
      gribDateTimeX(isec1, &vdate, &vtime, &sdate, &stime);
      DateTime datetime = { .date = vdate, .time = vtime };

      if ( rindex == 0 )
	{
          datetime0 = datetime;
          const int taxisID = vlistInqTaxis(vlistID);
	  if ( taxisInqType(taxisID) == TAXIS_RELATIVE )
	    {
	      taxis->type  = TAXIS_RELATIVE;
	      taxis->rdate = gribRefDate(isec1);
	      taxis->rtime = gribRefTime(isec1);
	    }
	  else
	    {
	      taxis->type  = TAXIS_ABSOLUTE;
	    }
	  taxis->unit  = cgribexGetTimeUnit(isec1);
	  taxis->vdate = vdate;
	  taxis->vtime = vtime;
          taxis->sdate = sdate;
          taxis->stime = stime;
	}

      const 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;
	}

      compvar_t compVar = cgribexVarSet(param, level1, level2, leveltype, ISEC1_TimeRange);

      for ( recID = 0; recID < nrecords; recID++ )
	{
	  if ( cgribexVarCompare(compVar, records[recID], 0) == 0 ) break;
	}

      if ( recID == nrecords )
	{
	  gribWarning("Parameter not defined at timestep 1!", nrecs_scanned, tsID+1, paramstr, level1, level2);
	  return CDI_EUFSTRUCT;
	}

      if ( CDI_Inventory_Mode == 1 )
	{
	  if ( records[recID].used )
	    {
	      break;
	    }
	  else
	    {
	      records[recID].used = true;
	      streamptr->tsteps[tsID].recIDs[rindex] = recID;
	    }
	}
      else
	{
	  if ( records[recID].used )
	    {
	      if ( datetimeDiffer(datetime, datetime0) ) break;

              gribWarning("Parameter already exist, skipped!", nrecs_scanned, tsID+1, paramstr, level1, level2);
	      continue;
	    }
	  else
	    {
	      records[recID].used = true;
	      streamptr->tsteps[tsID].recIDs[rindex] = recID;
	    }
	}

      if ( CDI_Debug )
	Message("Read record %2d (id=%s lev1=%d lev2=%d) %8d %6d", nrecs_scanned, paramstr, level1, level2, vdate, vtime);

      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;

      const int varID = records[recID].varID;
      const 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 ( ! records[recID].used )
	{
          vlistDefVarTimetype(vlistID, records[recID].varID, TIME_CONSTANT);
	}
      else
	{
	  nrecs++;
	}
    }
  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)
{
  int lmv = 0, iret = 0;
  off_t recpos = 0;
  int leveltype = 0, level1 = 0, level2 = 0;
  DateTime datetime0 = { LONG_MIN, LONG_MIN };
  int vrecID, recID = 0;
  bool warn_numavg = true;
  int nrecs = 0;
  char paramstr[32];

  cgribexrec_t *cgribexp = (cgribexrec_t *) streamptr->record->cgribexp;
  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);
      record_t *records = streamptr->tsteps[tsID].records;

      nrecs = streamScanInitRecords(streamptr, tsID);

      const int fileID = streamptr->fileID;

      fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

      int nrecs_scanned = streamptr->tsteps[0].nallrecs + streamptr->tsteps[1].nrecs*(tsID-1);
      int rindex = 0;
      while ( true )
	{
	  if ( rindex > nrecs ) break;

	  const size_t recsize = gribGetSize(fileID);
	  recpos = fileGetPos(fileID);
	  if ( recsize == 0 )
	    {
	      streamptr->ntsteps = streamptr->rtsteps + 1;
	      break;
	    }

          if ( rindex >= nrecs ) break;

          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);

          nrecs_scanned++;
	  cgribexDecodeHeader(cgribexp, (int *) gribbuffer, (int)recsize, &lmv, &iret);

	  const int param = cdiEncodeParam(ISEC1_Parameter, ISEC1_CodeTable, 255);
          cdiParamToString(param, paramstr, sizeof(paramstr));

          cgribexGetLevel(isec1, &leveltype, &level1, &level2);

          int vdate, sdate;
          int vtime, stime;
	  gribDateTimeX(isec1, &vdate, &vtime, &sdate, &stime);
          DateTime datetime = { .date = vdate, .time = vtime };

	  if ( rindex == nrecs ) break;

	  if ( rindex == 0 )
	    {
              datetime0 = datetime;
              const int vlistID = streamptr->vlistID;
	      const int taxisID = vlistInqTaxis(vlistID);
	      if ( taxisInqType(taxisID) == TAXIS_RELATIVE )
		{
		  taxis->type  = TAXIS_RELATIVE;
		  taxis->rdate = gribRefDate(isec1);
		  taxis->rtime = gribRefTime(isec1);
		}
	      else
		{
		  taxis->type  = TAXIS_ABSOLUTE;
		}
	      taxis->unit  = cgribexGetTimeUnit(isec1);
	      taxis->vdate = vdate;
	      taxis->vtime = vtime;
              taxis->sdate = sdate;
              taxis->stime = stime;
	    }

	  if ( ISEC1_AvgNum )
	    {
	      if (  taxis->numavg && warn_numavg && (taxis->numavg != ISEC1_AvgNum) )
                warn_numavg = false;
	      else
                taxis->numavg = ISEC1_AvgNum;
	    }

	  datetime.date = vdate;
	  datetime.time = vtime;

	  compvar_t compVar = cgribexVarSet(param, level1, level2, leveltype, ISEC1_TimeRange);

	  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!", nrecs_scanned, tsID+1, paramstr, level1, level2);

	      if ( CDI_Inventory_Mode == 1 )
		return CDI_EUFSTRUCT;
	      else
		continue;
	    }

	  if ( CDI_Inventory_Mode == 1 )
	    {
	      records[recID].used = true;
	      streamptr->tsteps[tsID].recIDs[rindex] = recID;
	    }
	  else
	    {
	      if ( records[recID].used )
		{
		  char paramstr_[32];
		  cdiParamToString(param, paramstr_, sizeof(paramstr_));

		  if ( datetimeDiffer(datetime, datetime0) ) break;

		  if ( CDI_Debug )
                    gribWarning("Parameter already exist, skipped!", nrecs_scanned, tsID+1, paramstr_, level1, level2);

		  continue;
		}
	      else
		{
		  records[recID].used = true;
		  streamptr->tsteps[tsID].recIDs[rindex] = recID;
		}
	    }

	  if ( CDI_Debug )
            Message("Read record %2d (id=%s lev1=%d lev2=%d) %8d %6d", nrecs_scanned, paramstr, level1, level2, vdate, vtime);

	  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 ( ! records[recID].used ) break;
	}

      if ( vrecID < nrecs )
	{
	  cdiParamToString(records[recID].param, paramstr, sizeof(paramstr));
	  gribWarning("Paramameter not found!", nrecs_scanned, 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;
    }

  return 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 *nmiss, 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);

  *nmiss = (ISEC1_Sec2Or3Flag & 64) ? ISEC4_NumValues - 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);

      *nmiss = 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;
                (*nmiss)++;
              }
        }
      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;
                (*nmiss)++;
              }
        }
    }

  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, int rdate, int rtime,
			int vdate, int vtime, int sdate, int stime, int *pip1, int *pip2)
{
  int year, month, day, hour, minute, second;
  int64_t julday1, julday2, days;
  int secofday1, secofday2, secs;

  cdiDecodeDate(rdate, &year, &month, &day);
  cdiDecodeTime(rtime, &hour, &minute, &second);
  encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday1, &secofday1);

  cdiDecodeDate(vdate, &year, &month, &day);
  cdiDecodeTime(vtime, &hour, &minute, &second);
  encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday2, &secofday2);

  (void) julday_sub(julday1, secofday1, julday2, secofday2, &days, &secs);

  int timerange = -1;
  int ip1 = 0, ip2 = 0;
  if ( !(int)(fmod(days*86400.0 + secs, factor)) )
    {
      const int ip = (int) ((days*86400.0 + secs)/factor);
      if ( (ip > 255) && (tsteptype == TSTEP_INSTANT) ) tsteptype = TSTEP_INSTANT3;

      int ipx = 0;
      if (sdate != 0 && (tsteptype == TSTEP_RANGE || tsteptype == TSTEP_AVG || tsteptype == TSTEP_ACCUM || tsteptype == TSTEP_DIFF))
        {
          cdiDecodeDate(sdate, &year, &month, &day);
          cdiDecodeTime(stime, &hour, &minute, &second);
          encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday2, &secofday2);

          (void) julday_sub(julday1, secofday1, julday2, secofday2, &days, &secs);

          ipx = (int) ((days*86400.0 + secs)/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, int date, int time)
{
  int year, month, day, hour, minute, second;
  cdiDecodeDate(date, &year, &month, &day);
  cdiDecodeTime(time, &hour, &minute, &second);

  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, int vdate, int vtime, 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 )
    {
      const int calendar = taxisInqCalendar(taxisID);

      int rdate = taxisInqRdate(taxisID);
      int rtime = taxisInqRtime(taxisID);
      if (vdate < rdate || (vdate == rdate && vtime < rtime))
        {
          rdate = vdate;
          rtime = vtime;
        }

      int sdate = taxisInqSdate(taxisID);
      int stime = taxisInqStime(taxisID);

      int factor = cgribexDefDateTime(isec1, timeunit, rdate, rtime);
      int ip1 = 0, ip2 = 0;
      int timerange = cgribexDefTimerange(tsteptype, factor, calendar, rdate, rtime, vdate, vtime, sdate, stime, &ip1, &ip2);

      if ( ip2 > 0xFF && timeunit < TUNIT_YEAR )
        {
          timeunit++;
          factor = cgribexDefDateTime(isec1, timeunit, rdate, rtime);
          timerange = cgribexDefTimerange(tsteptype, factor, calendar, rdate, rtime, vdate, vtime, sdate, stime, &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;
	}
      /*
      static bool lwarn = true;
      if ( lwarn && timetype == TAXIS_ABSOLUTE )
        {
          lwarn = false;
          Warning("Can't store forecast time %ld %d relative to %ld %d, converted to absolute time!", (long)vdate, vtime, (long)rdate, rtime);
        }
      */
      ISEC1_TimeRange   = timerange;
      ISEC1_TimePeriod1 = ip1;
      ISEC1_TimePeriod2 = ip2;
    }

  if ( timetype == TAXIS_ABSOLUTE )
    {
      (void) cgribexDefDateTime(isec1, timeunit, vdate, vtime);

      /*
      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, xlast = 0, xinc = 0;
  double yfirst = 0, ylast = 0, yinc = 0;

  int nlon = (int)gridInqXsize(gridID);
  int nlat = (int)gridInqYsize(gridID);

  if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      ISEC2_Reduced = true;
      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);
  if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      ISEC2_FirstLon = 0;
      ISEC2_LastLon  = (int)lround(1000*(360.-360./(nlat*2)));
      //ISEC2_LonIncr  = (int)lround(1000*360./(nlat*2)); // gribapi gridType detector doesn't like lonIncr
    }
  else
    {
      ISEC2_FirstLon = (int)lround(xfirst*1000);
      ISEC2_LastLon  = (int)lround(xlast*1000);
      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)
{
  const int xsize = (int)gridInqXsize(gridID);
  const int ysize = (int)gridInqYsize(gridID);

  double lon_0, lat_0, lat_1, lat_2, a, rf, xval_0, yval_0, x_0, y_0;
  gridInqParamLCC(gridID, CDI_Grid_Missval, &lon_0, &lat_0, &lat_1, &lat_2, &a, &rf, &xval_0, &yval_0, &x_0, &y_0);
  gridVerifyGribParamLCC(CDI_Grid_Missval, &lon_0, &lat_0, &lat_1, &lat_2, &a, &rf, &xval_0, &yval_0, &x_0, &y_0);
  bool lsouth = (lat_1 < 0);
  if ( lsouth ) { lat_1 = -lat_2; lat_2 = -lat_2; }

  const double xinc = gridInqXinc(gridID);
  const double yinc = gridInqYinc(gridID);

  ISEC2_GridType = GRIB1_GTYPE_LCC;
  ISEC2_NumLon   = xsize;
  ISEC2_NumLat   = ysize;
  ISEC2_FirstLon       = (int)lround(xval_0 * 1000);
  ISEC2_FirstLat       = (int)lround(yval_0 * 1000);
  ISEC2_Lambert_Lov    = (int)lround(lon_0 * 1000);
  ISEC2_Lambert_LatS1  = (int)lround(lat_1 * 1000);
  ISEC2_Lambert_LatS2  = (int)lround(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);

  const bool earthIsOblate = (IS_EQUAL(a, 6378160.) && IS_EQUAL(rf, 297.));
  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)
{
  memset(isec2, 0, 16*sizeof(int));
  ISEC1_Sec2Or3Flag = 128;
  ISEC1_GridDefinition = 255;
  ISEC2_Reduced  = false;
  ISEC2_ScanFlag = 0;

  int gridsize = (int) 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;
      }
    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
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)
{
  char units[CDI_MAX_NAME];

  int zaxistype = zaxisInqType(zaxisID);
  int ltype = 0;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_TYPEOFFIRSTFIXEDSURFACE, &ltype);

  if ( zaxistype == ZAXIS_GENERIC && ltype == 0 )
    {
      Message("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, (int)(zaxisInqLevel(zaxisID, levelID)), 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:
      {
	if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
          isec1DefLevel(isec1, GRIB1_LTYPE_HYBRID_LAYER, (int)(zaxisInqLbound(zaxisID, levelID)),
                        (int)(zaxisInqUbound(zaxisID, levelID)));
	else
          isec1DefLevel(isec1, GRIB1_LTYPE_HYBRID, (int)(zaxisInqLevel(zaxisID, levelID)), 0);

	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:
      {
	double level = zaxisInqLevel(zaxisID, levelID);
	if ( level < 0 ) Warning("Pressure level of %f Pa is below zero!", level);

        int length = CDI_MAX_NAME;
        cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
	if ( units[0] && (units[0] != 'P') | (units[1] != 'a') ) level *= 100;

	double dum;
	if ( level < 32768 && (level < 100 || modf(level/100, &dum) > 0) )
          grib_ltype = GRIB1_LTYPE_ISOBARIC_PA;
	else
          level = level/100;

        isec1DefLevel(isec1, grib_ltype, (int) level, 0);
	break;
      }
    case ZAXIS_HEIGHT:
      {
	double level = zaxisInqLevel(zaxisID, levelID);

        int length = CDI_MAX_NAME;
        cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
        if ( units[1] == 'm' && !units[2] )
          {
            // clang-format off
            if      ( units[0] == 'c' ) level *= 0.01;
            else if ( units[0] == 'd' ) level *= 0.1;
            else if ( units[0] == 'k' ) level *= 1000;
            // clang-format on
          }

        isec1DefLevel(isec1, grib_ltype, (int) level, 0);
	break;
      }
    case ZAXIS_SIGMA:
      {
	if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
          isec1DefLevel(isec1, GRIB1_LTYPE_SIGMA_LAYER, (int)(zaxisInqLbound(zaxisID, levelID)),
                        (int)(zaxisInqUbound(zaxisID, levelID)));
	else
          isec1DefLevel(isec1, GRIB1_LTYPE_SIGMA, (int)(zaxisInqLevel(zaxisID, levelID)), 0);

	break;
      }
    case ZAXIS_DEPTH_BELOW_LAND:
      {
        int length = CDI_MAX_NAME;
        cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);

	double factor = 100; // default: meter
        // clang-format off
        if      ( units[0] == 'm' && units[1] == 'm' ) factor =   0.1;
        else if ( units[0] == 'c' && units[1] == 'm' ) factor =   1;
        else if ( units[0] == 'd' && units[1] == 'm' ) factor =  10;
        // clang-format on

	if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
          isec1DefLevel(isec1, GRIB1_LTYPE_LANDDEPTH_LAYER, (int) (factor*zaxisInqLbound(zaxisID, levelID)),
                        (int) (factor*zaxisInqUbound(zaxisID, levelID)));
	else
          isec1DefLevel(isec1, GRIB1_LTYPE_LANDDEPTH, (int) (factor*zaxisInqLevel(zaxisID, levelID)), 0);

	break;
      }
    case ZAXIS_GENERIC:
      {
        isec1DefLevel(isec1, ltype, (int)(zaxisInqLevel(zaxisID, levelID)), 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

  int perturbationNumber, numberOfForecastsInEnsemble, typeOfEnsembleForecast;
  const int r1 = cdiInqKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, &perturbationNumber);
  const int r2 = cdiInqKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, &numberOfForecastsInEnsemble);
  const int r3 = cdiInqKeyInt(vlistID, varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, &typeOfEnsembleForecast);

  if ( r1 == 0 && r2 == 0 && r3 == 0 )
    {
      if ( ISEC1_CenterID == 252 )
        {
          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,
		     int vdate, int vtime, int tsteptype, int numavg,
		     size_t datasize, const void *data, size_t nmiss, void *gribbuffer, size_t gribbuffersize)
{
  cgribexrec_t *cgribexp = (cgribexrec_t *)cgribexNew();

  const size_t sec2len = 1024 + 2*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)];

  memset(isec1, 0, 256*sizeof(int));
  fsec2[0] = 0; fsec2[1] = 0;
  fsec2f[0] = 0; fsec2f[1] = 0;

  const int gribsize = (int)(gribbuffersize / sizeof(int));
  const int param    = vlistInqVarParam(vlistID, varID);

  cgribexDefaultSec0(isec0);
  cgribexDefaultSec1(isec1);
  cgribexDefaultSec4(isec4);

  cgribexDefInstitut(isec1, vlistID, varID);
  cgribexDefModel(isec1, vlistID, varID);

  const int datatype = vlistInqVarDatatype(vlistID, varID);

  int uvRelativeToGrid = -1;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_UVRELATIVETOGRID, &uvRelativeToGrid);

  cgribexDefParam(isec1, param);
  cgribexDefTime(isec1, vdate, vtime, 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 ( nmiss > 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);

  const 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)
{
  int datatype;

  if ( number == 2 )
    datatype = (prec == EXSE_DOUBLE_PRECISION) ? CDI_DATATYPE_CPX64 : CDI_DATATYPE_CPX32;
  else
    datatype = (prec == EXSE_DOUBLE_PRECISION) ? CDI_DATATYPE_FLT64 : CDI_DATATYPE_FLT32;

  return datatype;
}

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_DOUBLE_PRECISION : EXSE_SINGLE_PRECISION;
}

/* not used
int extInqRecord(stream_t *streamptr, int *varID, int *levelID)
{
  int status;
  int fileID;
  int icode, ilevel;
  int zaxisID = -1;
  int header[4];
  int vlistID;
  void *extp = streamptr->record->exsep;

  vlistID = streamptr->vlistID;
  fileID  = streamptr->fileID;

  *varID   = -1;
  *levelID = -1;

  status = extRead(fileID, extp);
  if ( status != 0 ) return 0;

  extInqHeader(extp, header);

  icode  = header[1];
  ilevel = header[2];

  *varID = vlistInqVarID(vlistID, icode);

  if ( *varID == CDI_UNDEFID ) Error("Code %d undefined", icode);

  zaxisID = vlistInqVarZaxis(vlistID, *varID);

  *levelID = zaxisInqLevelID(zaxisID, (double) ilevel);

  return 1;
}
*/

void extReadRecord(stream_t *streamptr, double *data, size_t *nmiss)
{
  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].records[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *extp = streamptr->record->exsep;
  int status = extRead(fileID, extp);
  if ( status != 0 ) Error("Failed to read EXTRA record");

  int header[4];
  extInqHeader(extp, header);
  extInqDataDP(extp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  int gridID  = vlistInqVarGrid(vlistID, varID);
  size_t size = gridInqSize(gridID);

  streamptr->numvals += size;

  *nmiss = 0;
  if ( vlistInqVarNumber(vlistID, varID) == CDI_REAL )
    {
      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;
	    (*nmiss)++;
	  }
    }
  else
    {
      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;
	    (*nmiss)++;
	  }
    }
}


void extCopyRecord(stream_t *streamptr2, stream_t *streamptr1)
{
  streamFCopyRecord(streamptr2, streamptr1, "EXTRA");
}


void extDefRecord(stream_t *streamptr)
{
  Record *record = streamptr->record;

  int pdis, pcat, pnum;
  cdiDecodeParam(record->param, &pnum, &pcat, &pdis);

  int header[4];
  header[0] = record->date;
  header[1] = pnum;
  header[2] = record->level;
  int gridID = record->gridID;
  cdi_check_gridsize_int_limit("EXTRA", gridInqSize(gridID));
  header[3] = (int) gridInqSize(gridID);

  extrec_t *extp = (extrec_t*) record->exsep;
  extDefDatatype(record->prec, &extp->prec, &extp->number);
  extDefHeader(extp, header);
}


void extWriteRecord(stream_t *streamptr, const double *data)
{
  void *extp = streamptr->record->exsep;
  extDefDataDP(extp, data);
  extWrite(streamptr->fileID, extp);
}

static
void extAddRecord(stream_t *streamptr, int param, int level, size_t xysize,
		  size_t recsize, off_t position, int prec, int number)
{
  const int vlistID = streamptr->vlistID;
  const int tsID    = streamptr->curTsID;
  const int recID   = recordNewEntry(streamptr, tsID);
  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);
  const int gridID = gridAdded.Id;
  if (!gridAdded.isNew) Free(grid);

  const int leveltype = ZAXIS_GENERIC;
  const 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);

  record->varID   = (short)varID;
  record->levelID = (short)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)
{
  int header[4];
  DateTime datetime0 = { LONG_MIN, LONG_MIN };
  off_t recpos = 0;
  extrec_t *extp = (extrec_t*) streamptr->record->exsep;

  streamptr->curTsID = 0;

  int tsID  = tstepsNewEntry(streamptr);
  if ( tsID != 0 ) Error("Internal problem! tstepsNewEntry returns %d", tsID);
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  const int fileID = streamptr->fileID;

  int nrecs = 0;
  while ( true )
    {
      recpos = fileGetPos(fileID);
      if ( extRead(fileID, extp) != 0 )
	{
	  streamptr->ntsteps = 1;
	  break;
	}

      const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

      extInqHeader(extp, header);

      const int vdate   = header[0];
      const int vtime   = 0;
      const int rcode   = header[1];
      const int rlevel  = header[2];
      const int rxysize = header[3];
      const int param   = cdiEncodeParam(rcode, 255, 255);
      DateTime datetime = { .date = vdate, .time = vtime};

      if ( nrecs == 0 )
	{
	  datetime0 = datetime;
          taxis->vdate = vdate;
          taxis->vtime = vtime;
	}
      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 ( datetimeDiffer(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, rxysize, recsize, recpos, extp->prec, extp->number);
    }

  tstepScanLoopFinished:
  streamptr->rtsteps = 1;

  cdi_generate_vars(streamptr);

  const int taxisID = taxisCreate(TAXIS_ABSOLUTE);
  taxis->type  = TAXIS_ABSOLUTE;
  taxis->rdate = taxis->vdate;
  taxis->rtime = taxis->vtime;

  const 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->exsep;

  streamptr->curTsID = 1;

  const int fileID  = streamptr->fileID;
  const 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);
  record_t *records = streamptr->tsteps[tsID].records;

  const int nrecords = streamScanInitRecords2(streamptr);

  for ( int rindex = 0; rindex <= nrecords; rindex++ )
    {
      recpos = fileGetPos(fileID);
      if ( extRead(fileID, extp) != 0 )
	{
	  streamptr->ntsteps = 2;
	  break;
	}

      const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

      extInqHeader(extp, header);

      const int vdate  = header[0];
      const int vtime  = 0;
      const int rcode  = header[1];
      const int rlevel = header[2];
      const int param  = cdiEncodeParam(rcode, 255, 255);

      if ( rindex == 0 )
	{
	  taxis->type  = TAXIS_ABSOLUTE;
	  taxis->vdate = vdate;
	  taxis->vtime = vtime;
	}

      bool nextstep = false;
      int recID;
      for ( recID = 0; recID < nrecords; recID++ )
	{
	  if ( param == records[recID].param && rlevel == records[recID].ilevel )
	    {
	      if ( records[recID].used )
		{
		  nextstep = true;
		}
	      else
		{
		  records[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 ( ! records[recID].used )
	{
          vlistDefVarTimetype(vlistID, records[recID].varID, TIME_CONSTANT);
	}
      else
	{
	  nrecs++;
	}
    }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  return 0;
}


int extInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  extScanTimestep1(streamptr);

  const 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->exsep;

  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if ( streamptr->tsteps[tsID].recordSize == 0 )
    {
      cdi_create_records(streamptr, tsID);
      record_t *records = streamptr->tsteps[tsID].records;

      nrecs = streamScanInitRecords(streamptr, tsID);

      const 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;
	    }

	  const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

	  extInqHeader(extp, header);

	  const int vdate  = header[0];
	  const int vtime  = 0;
	  const int rcode  = header[1];
	  const int rlevel = header[2];
	  const int param  = cdiEncodeParam(rcode, 255, 255);

	  // if ( rindex == nrecs ) break; gcc-4.5 internal compiler error
	  if ( rindex == nrecs ) continue;
	  const int recID = streamptr->tsteps[tsID].recIDs[rindex];

	  if ( rindex == 0 )
	    {
	      taxis->type  = TAXIS_ABSOLUTE;
	      taxis->vdate = vdate;
	      taxis->vtime = 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 *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  void *extp = streamptr->record->exsep;

  int vlistID  = streamptr->vlistID;
  int fileID   = streamptr->fileID;
  /* NOTE: tiles are not supported here! */
  double missval = vlistInqVarMissval(vlistID, varID);
  size_t gridsize = gridInqSize(vlistInqVarGrid(vlistID, varID));
  int tsid = streamptr->curTsID;

  off_t currentfilepos = fileGetPos(fileID);

  /* NOTE: tiles are not supported here! */
  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);
  extInqDataDP(extp, data);

  fileSetPos(fileID, currentfilepos, SEEK_SET);

  *nmiss = 0;
  if ( vlistInqVarNumber(vlistID, varID) == CDI_REAL )
    {
      for ( size_t i = 0; i < gridsize; i++ )
	if ( DBL_IS_EQUAL(data[i], missval) || DBL_IS_EQUAL(data[i], (float)missval) )
	  {
	    data[i] = missval;
	    (*nmiss)++;
	  }
    }
  else
    {
      for ( size_t i = 0; i < 2*gridsize; i+=2 )
	if ( DBL_IS_EQUAL(data[i], missval) || DBL_IS_EQUAL(data[i], (float)missval) )
	  {
	    data[i] = missval;
	    (*nmiss)++;
	  }
    }
}


void extReadVarDP(stream_t *streamptr, int varID, double *data, size_t *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = 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], nmiss);
}


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;

  int pdis, pcat, pnum;
  cdiDecodeParam(vlistInqVarParam(vlistID, varID), &pnum, &pcat, &pdis);

  int header[4];
  header[0] = streamptr->tsteps[tsID].taxis.vdate;
  header[1] = pnum;
  header[2] = (int)(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->exsep;
  extDefDatatype(vlistInqVarDatatype(vlistID, varID), &extp->prec, &extp->number);
  extDefHeader(extp, header);

  extDefDataDP(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 = 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 gribapiScanTimestep1(stream_t * streamptr);
int gribapiScanTimestep2(stream_t * streamptr);
int gribapiScanTimestep(stream_t * streamptr);

int gribapiDecode(void *gribbuffer, size_t gribsize, double *data, size_t datasize,
		  int unreduced, size_t *nmiss, double missval);

size_t gribapiEncode(int varID, int levelID, int vlistID, int gridID, int zaxisID,
		     int64_t vdate, int vtime, int tsteptype, int numavg,
		     size_t datasize, const double *data, size_t nmiss, 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


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;
}


int grib1ltypeToZaxisType(int grib_ltype)
{
  int zaxistype = ZAXIS_GENERIC;
  // clang-format off
  switch ( grib_ltype )
    {
    case GRIB1_LTYPE_SURFACE:            zaxistype = ZAXIS_SURFACE;                break;
    case GRIB1_LTYPE_CLOUD_BASE:         zaxistype = ZAXIS_CLOUD_BASE;             break;
    case GRIB1_LTYPE_CLOUD_TOP:          zaxistype = ZAXIS_CLOUD_TOP;              break;
    case GRIB1_LTYPE_ISOTHERM0:          zaxistype = ZAXIS_ISOTHERM_ZERO;          break;
    case GRIB1_LTYPE_TROPOPAUSE:         zaxistype = ZAXIS_TROPOPAUSE;             break;
    case GRIB1_LTYPE_TOA:                zaxistype = ZAXIS_TOA;                    break;
    case GRIB1_LTYPE_SEA_BOTTOM:         zaxistype = ZAXIS_SEA_BOTTOM;             break;
    case GRIB1_LTYPE_ATMOSPHERE:         zaxistype = ZAXIS_ATMOSPHERE;             break;
    case GRIB1_LTYPE_MEANSEA:            zaxistype = ZAXIS_MEANSEA;                break;
    case GRIB1_LTYPE_99:
    case GRIB1_LTYPE_ISOBARIC_PA:
    case GRIB1_LTYPE_ISOBARIC:           zaxistype = ZAXIS_PRESSURE;               break;
    case GRIB1_LTYPE_HEIGHT:             zaxistype = ZAXIS_HEIGHT;                 break;
    case GRIB1_LTYPE_ALTITUDE:           zaxistype = ZAXIS_ALTITUDE;	           break;
    case GRIB1_LTYPE_SIGMA:
    case GRIB1_LTYPE_SIGMA_LAYER:        zaxistype = ZAXIS_SIGMA;	           break;
    case GRIB1_LTYPE_HYBRID:
    case GRIB1_LTYPE_HYBRID_LAYER:       zaxistype = ZAXIS_HYBRID;	           break;
    case GRIB1_LTYPE_LANDDEPTH:
    case GRIB1_LTYPE_LANDDEPTH_LAYER:    zaxistype = ZAXIS_DEPTH_BELOW_LAND;       break;
    case GRIB1_LTYPE_ISENTROPIC:         zaxistype = ZAXIS_ISENTROPIC;             break;
    case GRIB1_LTYPE_SEADEPTH:           zaxistype = ZAXIS_DEPTH_BELOW_SEA;        break;
    case GRIB1_LTYPE_LAKE_BOTTOM:        zaxistype = ZAXIS_LAKE_BOTTOM;            break;
    case GRIB1_LTYPE_SEDIMENT_BOTTOM:    zaxistype = ZAXIS_SEDIMENT_BOTTOM;        break;
    case GRIB1_LTYPE_SEDIMENT_BOTTOM_TA: zaxistype = ZAXIS_SEDIMENT_BOTTOM_TA;     break;
    case GRIB1_LTYPE_SEDIMENT_BOTTOM_TW: zaxistype = ZAXIS_SEDIMENT_BOTTOM_TW;     break;
    case GRIB1_LTYPE_MIX_LAYER:          zaxistype = ZAXIS_MIX_LAYER;              break;
    }
  // clang-format on
  return zaxistype;
}


int grib2ltypeToZaxisType(int grib_ltype)
{
  int zaxistype = ZAXIS_GENERIC;
  // clang-format off
  switch ( grib_ltype )
    {
    case GRIB2_LTYPE_SURFACE:            zaxistype = ZAXIS_SURFACE;                break;
    case GRIB2_LTYPE_CLOUD_BASE:         zaxistype = ZAXIS_CLOUD_BASE;             break;
    case GRIB2_LTYPE_CLOUD_TOP:          zaxistype = ZAXIS_CLOUD_TOP;              break;
    case GRIB2_LTYPE_ISOTHERM0:          zaxistype = ZAXIS_ISOTHERM_ZERO;          break;
    case GRIB2_LTYPE_TROPOPAUSE:         zaxistype = ZAXIS_TROPOPAUSE;             break;
    case GRIB2_LTYPE_TOA:                zaxistype = ZAXIS_TOA;                    break;
    case GRIB2_LTYPE_SEA_BOTTOM:         zaxistype = ZAXIS_SEA_BOTTOM;             break;
    case GRIB2_LTYPE_ATMOSPHERE:         zaxistype = ZAXIS_ATMOSPHERE;             break;
    case GRIB2_LTYPE_MEANSEA:            zaxistype = ZAXIS_MEANSEA;                break;
    case GRIB2_LTYPE_ISOBARIC:           zaxistype = ZAXIS_PRESSURE;               break;
    case GRIB2_LTYPE_HEIGHT:             zaxistype = ZAXIS_HEIGHT;                 break;
    case GRIB2_LTYPE_ALTITUDE:           zaxistype = ZAXIS_ALTITUDE;               break;
    case GRIB2_LTYPE_SIGMA:              zaxistype = ZAXIS_SIGMA;                  break;
    case GRIB2_LTYPE_HYBRID:
 /* case GRIB2_LTYPE_HYBRID_LAYER: */    zaxistype = ZAXIS_HYBRID;                 break;
    case GRIB2_LTYPE_LANDDEPTH:
 /* case GRIB2_LTYPE_LANDDEPTH_LAYER: */ zaxistype = ZAXIS_DEPTH_BELOW_LAND;       break;
    case GRIB2_LTYPE_ISENTROPIC:         zaxistype = ZAXIS_ISENTROPIC;             break;
    case GRIB2_LTYPE_SNOW:               zaxistype = ZAXIS_SNOW;                   break;
    case GRIB2_LTYPE_SEADEPTH:           zaxistype = ZAXIS_DEPTH_BELOW_SEA;        break;
    case GRIB2_LTYPE_LAKE_BOTTOM:        zaxistype = ZAXIS_LAKE_BOTTOM;            break;
    case GRIB2_LTYPE_SEDIMENT_BOTTOM:    zaxistype = ZAXIS_SEDIMENT_BOTTOM;        break;
    case GRIB2_LTYPE_SEDIMENT_BOTTOM_TA: zaxistype = ZAXIS_SEDIMENT_BOTTOM_TA;     break;
    case GRIB2_LTYPE_SEDIMENT_BOTTOM_TW: zaxistype = ZAXIS_SEDIMENT_BOTTOM_TW;     break;
    case GRIB2_LTYPE_MIX_LAYER:          zaxistype = ZAXIS_MIX_LAYER;              break;
    case GRIB2_LTYPE_REFERENCE:          zaxistype = ZAXIS_REFERENCE;              break;
    }
  // clang-format on
  return zaxistype;
}


int zaxisTypeToGrib1ltype(int zaxistype)
{
  int grib_ltype = -1;
  // clang-format off
  switch (zaxistype)
    {
    case ZAXIS_SURFACE:               grib_ltype = GRIB1_LTYPE_SURFACE;            break;
    case ZAXIS_GENERIC:               grib_ltype = -1;                             break;
    case ZAXIS_HYBRID:                grib_ltype = -1;                             break;
    case ZAXIS_HYBRID_HALF:           grib_ltype = -1;                             break;
    case ZAXIS_PRESSURE:              grib_ltype = GRIB1_LTYPE_ISOBARIC;           break;
    case ZAXIS_HEIGHT:                grib_ltype = GRIB1_LTYPE_HEIGHT;             break;
    case ZAXIS_DEPTH_BELOW_SEA:       grib_ltype = GRIB1_LTYPE_SEADEPTH;           break;
    case ZAXIS_DEPTH_BELOW_LAND:      grib_ltype = GRIB1_LTYPE_LANDDEPTH;          break;
    case ZAXIS_ISENTROPIC:            grib_ltype = GRIB1_LTYPE_ISENTROPIC;         break;
    case ZAXIS_TRAJECTORY:            grib_ltype = -1;                             break;
    case ZAXIS_ALTITUDE:              grib_ltype = GRIB1_LTYPE_ALTITUDE;           break;
    case ZAXIS_SIGMA:                 grib_ltype = GRIB1_LTYPE_SIGMA;              break;
    case ZAXIS_MEANSEA:               grib_ltype = GRIB1_LTYPE_MEANSEA;            break;
    case ZAXIS_TROPOPAUSE:            grib_ltype = GRIB1_LTYPE_TROPOPAUSE;         break;
    case ZAXIS_TOA:                   grib_ltype = GRIB1_LTYPE_TOA;                break;
    case ZAXIS_SEA_BOTTOM:            grib_ltype = GRIB1_LTYPE_SEA_BOTTOM;         break;
    case ZAXIS_ATMOSPHERE:            grib_ltype = GRIB1_LTYPE_ATMOSPHERE;         break;
    case ZAXIS_CLOUD_BASE:            grib_ltype = GRIB1_LTYPE_CLOUD_BASE;         break;
    case ZAXIS_CLOUD_TOP:             grib_ltype = GRIB1_LTYPE_CLOUD_TOP;          break;
    case ZAXIS_ISOTHERM_ZERO:         grib_ltype = GRIB1_LTYPE_ISOTHERM0;          break;
    case ZAXIS_SNOW:                  grib_ltype = -1;                             break;
    case ZAXIS_LAKE_BOTTOM:           grib_ltype = GRIB1_LTYPE_LAKE_BOTTOM;        break;
    case ZAXIS_SEDIMENT_BOTTOM:       grib_ltype = GRIB1_LTYPE_SEDIMENT_BOTTOM;    break;
    case ZAXIS_SEDIMENT_BOTTOM_TA:    grib_ltype = GRIB1_LTYPE_SEDIMENT_BOTTOM_TA; break;
    case ZAXIS_SEDIMENT_BOTTOM_TW:    grib_ltype = GRIB1_LTYPE_SEDIMENT_BOTTOM_TW; break;
    case ZAXIS_MIX_LAYER:             grib_ltype = GRIB1_LTYPE_MIX_LAYER;          break;
    case ZAXIS_REFERENCE:             grib_ltype = -1;                             break;
    }
  // clang-format on
  return grib_ltype;
}


int zaxisTypeToGrib2ltype(int zaxistype)
{
  int grib_ltype = -1;
  // clang-format off
  switch (zaxistype)
    {
    case ZAXIS_SURFACE:               grib_ltype = GRIB2_LTYPE_SURFACE;            break;
    case ZAXIS_GENERIC:               grib_ltype = -1;                             break;
    case ZAXIS_HYBRID:                grib_ltype = GRIB2_LTYPE_HYBRID;             break;
    case ZAXIS_HYBRID_HALF:           grib_ltype = GRIB2_LTYPE_HYBRID;             break;
    case ZAXIS_PRESSURE:              grib_ltype = GRIB2_LTYPE_ISOBARIC;           break;
    case ZAXIS_HEIGHT:                grib_ltype = GRIB2_LTYPE_HEIGHT;             break;
    case ZAXIS_DEPTH_BELOW_SEA:       grib_ltype = GRIB2_LTYPE_SEADEPTH;           break;
    case ZAXIS_DEPTH_BELOW_LAND:      grib_ltype = GRIB2_LTYPE_LANDDEPTH;          break;
    case ZAXIS_ISENTROPIC:            grib_ltype = GRIB2_LTYPE_ISENTROPIC;         break;
    case ZAXIS_TRAJECTORY:            grib_ltype = -1;                             break;
    case ZAXIS_ALTITUDE:              grib_ltype = GRIB2_LTYPE_ALTITUDE;           break;
    case ZAXIS_SIGMA:                 grib_ltype = GRIB2_LTYPE_SIGMA;              break;
    case ZAXIS_MEANSEA:               grib_ltype = GRIB2_LTYPE_MEANSEA;            break;
    case ZAXIS_TROPOPAUSE:            grib_ltype = GRIB2_LTYPE_TROPOPAUSE;         break;
    case ZAXIS_TOA:                   grib_ltype = GRIB2_LTYPE_TOA;                break;
    case ZAXIS_SEA_BOTTOM:            grib_ltype = GRIB2_LTYPE_SEA_BOTTOM;         break;
    case ZAXIS_ATMOSPHERE:            grib_ltype = GRIB2_LTYPE_ATMOSPHERE;         break;
    case ZAXIS_CLOUD_BASE:            grib_ltype = GRIB2_LTYPE_CLOUD_BASE;         break;
    case ZAXIS_CLOUD_TOP:             grib_ltype = GRIB2_LTYPE_CLOUD_TOP;          break;
    case ZAXIS_ISOTHERM_ZERO:         grib_ltype = GRIB2_LTYPE_ISOTHERM0;          break;
    case ZAXIS_SNOW:                  grib_ltype = GRIB2_LTYPE_SNOW;               break;
    case ZAXIS_LAKE_BOTTOM:           grib_ltype = GRIB2_LTYPE_LAKE_BOTTOM;        break;
    case ZAXIS_SEDIMENT_BOTTOM:       grib_ltype = GRIB2_LTYPE_SEDIMENT_BOTTOM;    break;
    case ZAXIS_SEDIMENT_BOTTOM_TA:    grib_ltype = GRIB2_LTYPE_SEDIMENT_BOTTOM_TA; break;
    case ZAXIS_SEDIMENT_BOTTOM_TW:    grib_ltype = GRIB2_LTYPE_SEDIMENT_BOTTOM_TW; break;
    case ZAXIS_MIX_LAYER:             grib_ltype = GRIB2_LTYPE_MIX_LAYER;          break;
    case ZAXIS_REFERENCE:             grib_ltype = GRIB2_LTYPE_REFERENCE;          break;
    }
  // clang-format on
  return grib_ltype;
}


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;
}


/*
int grbInqRecord(stream_t * streamptr, int *varID, int *levelID)
{
  int status;

  status = cgribexInqRecord(streamptr, varID, levelID);

  return (status);
}
*/

void grbDefRecord(stream_t * streamptr)
{
  UNUSED(streamptr);
}

static
int grbScanTimestep1(stream_t * streamptr)
{
  int 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("Sufficient GRIB support unavailable!");
#endif

  return status;
}

static
int grbScanTimestep2(stream_t * streamptr)
{
  int 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("Sufficient GRIB support unavailable!");
#endif

  return status;
}

static
int grbScanTimestep(stream_t * streamptr)
{
  int 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("Sufficient GRIB support unavailable!");
#endif

  return status;
}


#ifdef HAVE_LIBGRIB
int grbInqContents(stream_t * streamptr)
{
  streamptr->curTsID = 0;

  int status = grbScanTimestep1(streamptr);
  if ( status == 0 && streamptr->ntsteps == -1 ) status = grbScanTimestep2(streamptr);

  int fileID = streamptr->fileID;
  fileSetPos(fileID, 0, SEEK_SET);

  return status;
}
#endif

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);

  int ntsteps = CDI_UNDEFID;
  while ( (tsID + 1) > streamptr->rtsteps && ntsteps == CDI_UNDEFID )
    {
      ntsteps = grbScanTimestep(streamptr);
      if ( ntsteps == CDI_EUFSTRUCT )
	{
	  streamptr->ntsteps = streamptr->rtsteps;
	  break;
	}
    }

  int nrecs;

  if ( tsID >= streamptr->ntsteps && streamptr->ntsteps != CDI_UNDEFID )
    {
      nrecs = 0;
    }
  else
    {
      streamptr->curTsID = tsID;
      nrecs = streamptr->tsteps[tsID].nrecs;
    }

  return nrecs;
}

// used in CDO!!!
void streamInqGRIBinfo(int streamID, int *intnum, float *fltnum, off_t *bignum)
{
  stream_t *streamptr = stream_to_pointer(streamID);

  int filetype = streamptr->filetype;

  if ( filetype == CDI_FILETYPE_GRB )
    {
      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);
    }
}


int grbGetGridtype(int *gridID, size_t 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;
        }
    }

  return gridtype;
}

/*
 * 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 {
  int param;
  int level1;
  int level2;
  int ltype;
  int tsteptype;
  size_t gridsize;
  char name[32];
  VarScanKeys scanKeys;
  var_tile_t tiles;
} compvar2_t;


static
int gribapiGetZaxisType(long editionNumber, int grib_ltype)
{
  return (editionNumber <= 1) ? grib1ltypeToZaxisType(grib_ltype) : grib2ltypeToZaxisType(grib_ltype);
}

static
int getTimeunits(const 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 timeunit_factor(const int tu1, const int tu2)
{
  if (tu2 == TUNIT_HOUR)
    {
      switch (tu1)
        {
        case TUNIT_SECOND:  return 3600;
        case TUNIT_MINUTE:  return 60;
        case TUNIT_HOUR:    return 1;
        case TUNIT_3HOURS:  return 1./3;
        case TUNIT_6HOURS:  return 1./6;
        case TUNIT_12HOURS: return 1./12;
        case TUNIT_DAY:     return 1./24;
        }
    }

  return 1;
}

static
int gribapiGetTimeUnits(grib_handle *gh)
{
  long unitsOfTime = -1;
  grib_get_long(gh, "indicatorOfUnitOfTimeRange", &unitsOfTime);

  GRIB_CHECK(my_grib_set_long(gh, "stepUnits", unitsOfTime), 0);

  return getTimeunits(unitsOfTime);
}

static
void gribapiGetSteps(grib_handle *gh, int timeunits, int *startStep, int *endStep)
{
  long unitsOfTime;
  int status = grib_get_long(gh, "stepUnits", &unitsOfTime);
  const int timeunits2 = (status == 0) ? getTimeunits(unitsOfTime) : timeunits;
  //timeunits2 = gribapiGetTimeUnits(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 * timeunit_factor(timeunits, timeunits2)) + 0.5);
    }

  *endStep = *startStep;
  status = grib_get_long(gh, "endStep", &lpar);
  if ( status == 0 )
    *endStep = (int) (((double)lpar * timeunit_factor(timeunits, timeunits2)) + 0.5);
  // printf("%d %d %d %d %d %g\n", *startStep, *endStep, lpar, timeunits, timeunits2, timeunit_factor(timeunits, timeunits2));
}

static
void gribapiGetDataDateTime(grib_handle *gh, int64_t *datadate, int *datatime)
{
  long date;
  GRIB_CHECK(grib_get_long(gh, "dataDate", &date), 0);
  *datadate = (int64_t) date;
  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);
  *datatime = cdiEncodeTime((int)hour, (int)minute, (int)second);
}

static
void gribapiSetDataDateTime(grib_handle *gh, int64_t datadate, int datatime)
{
  GRIB_CHECK(my_grib_set_long(gh, "dataDate", (long)datadate), 0);
  int hour, minute, second;
  cdiDecodeTime(datatime, &hour, &minute, &second);
  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 gribapiGetTimeUnitFactor(const 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
void gribapiGetValidityDateTime(grib_handle *gh, int64_t *vdate, int *vtime, int64_t *sDate, int *sTime)
{
  *sDate = 0;
  *sTime = 0;

  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())`.
    {
      gribapiGetDataDateTime(gh, vdate, vtime);
    }
  else
    {
      int64_t rdate;
      int rtime;
      gribapiGetDataDateTime(gh, &rdate, &rtime);

      const int timeUnits = gribapiGetTimeUnits(gh);
      int startStep = 0, endStep = 0;
      gribapiGetSteps(gh, timeUnits, &startStep, &endStep);

      {
	int ryear, rmonth, rday, rhour, rminute, rsecond;
	cdiDecodeDate(rdate, &ryear, &rmonth, &rday);
	cdiDecodeTime(rtime, &rhour, &rminute, &rsecond);

        if ( rday > 0 )
          {
            extern int CGRIBEX_grib_calendar;
            int64_t julday;
            int secofday;
            encode_caldaysec(CGRIBEX_grib_calendar, ryear, rmonth, rday, rhour, rminute, rsecond, &julday, &secofday);

            int64_t time_unit_factor = gribapiGetTimeUnitFactor(timeUnits);

            //if (startStep > 0)
              {
                int64_t julday_x = julday;
                int secofday_x = secofday;
                julday_add_seconds(time_unit_factor * startStep, &julday_x, &secofday_x);
                decode_caldaysec(CGRIBEX_grib_calendar, julday_x, secofday_x, &ryear, &rmonth, &rday, &rhour, &rminute, &rsecond);
                *sDate = cdiEncodeDate(ryear, rmonth, rday);
                *sTime = cdiEncodeTime(rhour, rminute, 0);
              }

            julday_add_seconds(time_unit_factor * endStep, &julday, &secofday);
            decode_caldaysec(CGRIBEX_grib_calendar, julday, secofday, &ryear, &rmonth, &rday, &rhour, &rminute, &rsecond);
          }

	*vdate = cdiEncodeDate(ryear, rmonth, rday);
	*vtime = cdiEncodeTime(rhour, rminute, rsecond);
      }
    }
}

static
void grib1GetLevel(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(const 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 calcLevel(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 grib2GetLevel(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 = calcLevel(*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 = calcLevel(*level_sf, factor, llevel);
        }
    }
}

static
void gribGetLevel(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 )
    {
      grib1GetLevel(gh, leveltype1, lbounds, level1, level2);
      *leveltype2 = -1;
      *level_sf = 0;
      *level_unit = 0;
    }
  else
    {
      grib2GetLevel(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 gribapiGetString(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);
    }
  if      ( length == 8 && memcmp(string, "unknown", length) == 0 ) string[0] = 0;
  else if ( length == 2 && memcmp(string, "~", length)       == 0 ) string[0] = 0;
}

static
int gribapiGetEnsembleInfo(grib_handle *gh, long *typeOfEnsembleForecast, long *numberOfForecastsInEnsemble, long *perturbationNumber)
{
  int status = 0;
  if (grib_get_long(gh, "typeOfEnsembleForecast", typeOfEnsembleForecast) == 0)
    {
      GRIB_CHECK(grib_get_long(gh, "numberOfForecastsInEnsemble", numberOfForecastsInEnsemble), 0);
      GRIB_CHECK(grib_get_long(gh, "perturbationNumber", perturbationNumber), 0);
      if (*perturbationNumber > 0) status = 1;
    }

  if (status == 0)
    {
      *typeOfEnsembleForecast = 0;
      *perturbationNumber = 0;
      *numberOfForecastsInEnsemble = 0;
    }

  return status;
}

static
VarScanKeys gribapiGetScanKeys(grib_handle *gh)
{
  VarScanKeys scanKeys;
  varScanKeysInit(&scanKeys);

  long typeOfEnsembleForecast = 0, numberOfForecastsInEnsemble = 0, perturbationNumber = 0;
  gribapiGetEnsembleInfo(gh, &typeOfEnsembleForecast, &numberOfForecastsInEnsemble, &perturbationNumber);
  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;
  gribapiGetString(gh, "name", string, vlen); // longname
  if (string[0]) varDefKeyString(varID, CDI_KEY_LONGNAME, string);

  gribapiGetString(gh, "units", string, vlen);
  if (string[0]) varDefKeyString(varID, CDI_KEY_UNITS, string);

  string[0] = 0;
  const 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 Info 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 typeOfEnsembleForecast = 0, numberOfForecastsInEnsemble = 0, perturbationNumber = 0;
  gribapiGetEnsembleInfo(gh, &typeOfEnsembleForecast, &numberOfForecastsInEnsemble, &perturbationNumber);
  if ( perturbationNumber > 0 )
    {
      varDefKeyInt(varID, CDI_KEY_TYPEOFENSEMBLEFORECAST, (int) typeOfEnsembleForecast);
      varDefKeyInt(varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, (int) numberOfForecastsInEnsemble);
      varDefKeyInt(varID, CDI_KEY_PERTURBATIONNUMBER, (int) perturbationNumber);
    }

  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, mpimType);
              varDefKeyInt(varID, CDI_KEY_MPIMCLASS, mpimClass);
              varDefKeyInt(varID, CDI_KEY_MPIMUSER, 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, revStatus);
            }
        }
    }
}

static
void gribapiDefProjRLL(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
double shapeOfTheEarthToRadius(long shapeOfTheEarth)
{
  switch (shapeOfTheEarth)
    {
    case 0:  return 6367470.;
    case 2:  return 6378160.;
    case 6:  return 6371229.;
    case 8:  return 6371200.;
    }
  return 6367470.;
}

static
void gribapiDefProjLCC(grib_handle *gh, int gridID)
{
  long shapeOfTheEarth;
  grib_get_long(gh, "shapeOfTheEarth", &shapeOfTheEarth);
  const double a = shapeOfTheEarthToRadius(shapeOfTheEarth);
  const double rf = (shapeOfTheEarth == 2) ? 297.0 : 0;
  double lon_0, lat_1, lat_2, xval_0, yval_0;
  long projflag = 0;
  grib_get_double(gh, "longitudeOfFirstGridPointInDegrees", &xval_0);
  grib_get_double(gh, "latitudeOfFirstGridPointInDegrees", &yval_0);
  grib_get_double(gh, "LoVInDegrees", &lon_0);
  grib_get_double(gh, "Latin1InDegrees", &lat_1);
  grib_get_double(gh, "Latin2InDegrees", &lat_2);
  grib_get_long(gh, "projectionCentreFlag", &projflag);
  bool lsouth = gribbyte_get_bit((int)projflag, 1);
  if ( lsouth ) { lat_1 = -lat_1; lat_2 = -lat_2; }

  double lat_0 = lat_2;
  double x_0 = CDI_Grid_Missval;
  double y_0 = CDI_Grid_Missval;

  if ( proj_lonlat_to_lcc_func )
    {
      x_0 = xval_0; y_0 = yval_0;
      proj_lonlat_to_lcc_func(CDI_Grid_Missval, lon_0, lat_0, lat_1, lat_2, a, rf, (size_t)1, &x_0, &y_0);
      if ( IS_NOT_EQUAL(x_0, CDI_Grid_Missval) && IS_NOT_EQUAL(y_0, CDI_Grid_Missval) )
        { x_0 = -x_0; y_0 = -y_0; }
    }

  gridDefParamLCC(gridID, CDI_Grid_Missval, lon_0, lat_0, lat_1, lat_2, a, rf, xval_0, yval_0, x_0, y_0);
}


static
void gribapiDefProjSTERE(grib_handle *gh, int gridID)
{
  long shapeOfTheEarth;
  grib_get_long(gh, "shapeOfTheEarth", &shapeOfTheEarth);
  const double a = shapeOfTheEarthToRadius(shapeOfTheEarth);

  double lon_0, lat_ts, xval_0, yval_0;
  grib_get_double(gh, "longitudeOfFirstGridPointInDegrees", &xval_0);
  grib_get_double(gh, "latitudeOfFirstGridPointInDegrees", &yval_0);
  grib_get_double(gh, "LaDInDegrees", &lat_ts);
  grib_get_double(gh, "orientationOfTheGridInDegrees", &lon_0);

  long southPoleOnProjectionPlane;
  grib_get_long(gh, "southPoleOnProjectionPlane", &southPoleOnProjectionPlane);
  double lat_0 = southPoleOnProjectionPlane ? -90 : 90;

  double x_0 = CDI_Grid_Missval;
  double y_0 = CDI_Grid_Missval;

  if ( proj_lonlat_to_stere_func )
    {
      x_0 = xval_0; y_0 = yval_0;
      proj_lonlat_to_stere_func(CDI_Grid_Missval, lon_0, lat_ts, lat_0, a, (size_t)1, &x_0, &y_0);
      if ( IS_NOT_EQUAL(x_0, CDI_Grid_Missval) && IS_NOT_EQUAL(y_0, CDI_Grid_Missval) )
        { x_0 = -x_0; y_0 = -y_0; }
    }

  gridDefParamSTERE(gridID, CDI_Grid_Missval, lon_0, lat_ts, lat_0, a, xval_0, yval_0, x_0, y_0);
}

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, int lread_additional_keys)
{
  const int vlistID = streamptr->vlistID;
  const int tsID    = streamptr->curTsID;
  const int recID   = recordNewEntry(streamptr, tsID);
  record_t *record = &streamptr->tsteps[tsID].records[recID];

  const int tsteptype = gribapiGetTsteptype(gh);
  int numavg = 0;

  // 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     = leveltype1;
  record->tsteptype = (short)tsteptype;
  record->gridsize  = gribapiGetGridsize(gh);
  record->scanKeys  = *scanKeys;
  record->tiles     = tiles ? *tiles : dummy_tiles;

  strncpy(record->varname, varname, sizeof(record->varname)-1);
  record->varname[sizeof(record->varname) - 1] = 0;

  grid_t *grid = (grid_t *)Malloc(sizeof(*grid));
  const bool uvRelativeToGrid = gribapiGetGrid(gh, grid);

  struct addIfNewRes gridAdded = cdiVlistAddGridIfNew(vlistID, grid, 0);
  const int gridID = gridAdded.Id;
  if      ( !gridAdded.isNew ) Free(grid);
  else if ( grid->projtype == CDI_PROJ_RLL ) gribapiDefProjRLL(gh, gridID);
  else if ( grid->projtype == CDI_PROJ_LCC ) gribapiDefProjLCC(gh, gridID);
  else if ( grid->projtype == CDI_PROJ_STERE ) gribapiDefProjSTERE(gh, gridID);

  const int zaxistype = gribapiGetZaxisType(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) */
        const 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);

  record->varID = (short)varID;
  record->levelID = (short)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;
      for ( int i = 0; i < cdiNAdditionalGRIBKeys; i++ )
        {
          // note: if the key is not defined, we do not throw an error!
          if ( grib_get_long(gh, cdiAdditionalGRIBKeys[i], &lval) == 0 )
            varDefOptGribInt(varID, tile_index, lval, cdiAdditionalGRIBKeys[i]);
          if ( grib_get_double(gh, cdiAdditionalGRIBKeys[i], &dval) == 0 )
            varDefOptGribDbl(varID, tile_index, dval, cdiAdditionalGRIBKeys[i]);
        }
    }

  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);
	}
    }

  streamptr->tsteps[tsID].nallrecs++;
  streamptr->nrecs++;

  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));
  const 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(compvar2_t compVar, 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     = record.ltype;
  compVar0.tsteptype = 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;

  return memcmp(&compVar0, &compVar, sizeof(compvar2_t));
}

static
grib_handle *gribapiGetDiskRepresentation(size_t recsize, size_t *buffersize, void **gribbuffer, int *outDatatype, int *outCompressionType)
{
  const 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;
      const 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, compvar2_t compVar, const DateTime* verificationTime, const DateTime* 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;
    }
  const bool recordExists = recID < streamptr->nrecs;

  // Then we need to know whether the verification time is consistent.
  const bool consistentTime = !memcmp(verificationTime, expectedVTime, sizeof(*verificationTime));

  // 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)

int gribapiScanTimestep1(stream_t * streamptr)
{
  off_t recpos = 0;
  void *gribbuffer = NULL;
  size_t buffersize = 0;
  DateTime datetime0 = { .date = 10101, .time = 0 };
  int nrecs_scanned = 0;        //Only used for debug output.
  bool warn_time = true;
  int fcast = 0;
  grib_handle *gh = NULL;

  streamptr->curTsID = 0;

  const int tsID  = tstepsNewEntry(streamptr);
  if ( tsID != 0 ) Error("Internal problem! tstepsNewEntry returns %d", tsID);

  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  const int fileID = streamptr->fileID;

  unsigned nrecs = 0;
  while ( true )
    {
      int level1 = 0, level2 = 0;
      const size_t recsize = gribGetSize(fileID);
      recpos = fileGetPos(fileID);

      if ( recsize == 0 )
        {
          streamptr->ntsteps = 1;
          break;
        }

      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 datatype, comptype = 0;
      gh = gribapiGetDiskRepresentation(recsize, &buffersize, &gribbuffer, &datatype, &comptype);

      nrecs_scanned++;
      GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

      const int param = gribapiGetParam(gh);
      int leveltype1 = -1, leveltype2 = -1, lbounds, level_sf, level_unit;
      var_tile_t tiles = dummy_tiles;
      gribGetLevel(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

      char varname[256];
      gribapiGetString(gh, "shortName", varname, sizeof(varname));

      int64_t vdate, sdate;
      int vtime, stime;
      gribapiGetValidityDateTime(gh, &vdate, &vtime, &sdate, &stime);
      DateTime datetime = { .date = vdate, .time = vtime };

      VarScanKeys scanKeys = gribapiGetScanKeys(gh);

      if ( nrecs == 0 )
        {
          datetime0 = datetime;
          gribapiGetDataDateTime(gh, &(taxis->rdate), &(taxis->rtime));
          fcast = gribapiTimeIsFC(gh);
          if ( fcast ) taxis->unit = gribapiGetTimeUnits(gh);
          taxis->fdate = taxis->rdate;
          taxis->ftime = taxis->rtime;
          taxis->sdate = sdate;
          taxis->stime = stime;
          taxis->vdate = vdate;
          taxis->vtime = vtime;
        }
      else
        {
          const int tsteptype = gribapiGetTsteptype(gh);
          const size_t gridsize = gribapiGetGridsize(gh);
          checkTimeResult result = checkTime(streamptr,
                                             gribapiVarSet(param, level1, level2, leveltype1, tsteptype, gridsize, varname, scanKeys, tiles),
                                             &datetime, &datetime0);
          if ( result == CHECKTIME_STOP )
            {
              break;
            }
          else if ( result == CHECKTIME_SKIP )
            {
              gribWarning("Parameter already exist, skipped!", nrecs_scanned, tsID+1, varname, param, level1, level2);
              continue;
            }
          else if ( result == CHECKTIME_INCONSISTENT && warn_time )
            {
              gribWarning("Inconsistent verification time!", nrecs_scanned, 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 vdate=%lld vtime=%d",
                  nrecs, (int)recpos, varname, paramstr, leveltype1, level1, level2, vdate, vtime);
        }

      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, 1);

      grib_handle_delete(gh);
      gh = NULL;
    }

  if ( gh ) grib_handle_delete(gh);

  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);
  // printf("1: %d %6d  %d %6d  %d %6d\n", taxis->rdate, taxis->rtime, taxis->sdate, taxis->stime, taxis->vdate, taxis->vtime);

  const 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 gribapiScanTimestep2(stream_t * streamptr)
{
  int rstatus = 0;
  off_t recpos = 0;
  DateTime datetime0 = { LONG_MIN, LONG_MIN };
  // int gridID;
  int recID;
  grib_handle *gh = NULL;

  streamptr->curTsID = 1;

  const int fileID  = streamptr->fileID;
  const 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);
  record_t *records = streamptr->tsteps[tsID].records;

  const int nrecords = streamScanInitRecords2(streamptr);

  int nrecs_scanned = nrecords; //Only used for debug output
  int rindex = 0;
  while ( true )
    {
      if ( rindex > nrecords ) break;

      const size_t recsize = gribGetSize(fileID);
      recpos = fileGetPos(fileID);
      if ( recsize == 0 )
	{
	  streamptr->ntsteps = 2;
	  break;
	}

      ensureBufferSize(recsize, &buffersize, &gribbuffer);

      size_t readsize = recsize;
      if (gribRead(fileID, gribbuffer, &readsize)) break;

      grbDecompress(recsize, &buffersize, &gribbuffer);

      nrecs_scanned++;
      gh = grib_handle_new_from_message(NULL, gribbuffer, recsize);
      GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

      const int param = gribapiGetParam(gh);
      int level1 = 0, level2 = 0, leveltype1, leveltype2, lbounds, level_sf, level_unit;
      var_tile_t tiles = dummy_tiles;
      gribGetLevel(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

      char varname[256];
      gribapiGetString(gh, "shortName", varname, sizeof(varname));

      int64_t vdate, sdate;
      int vtime, stime;
      gribapiGetValidityDateTime(gh, &vdate, &vtime, &sdate, &stime);
      DateTime datetime = { .date = vdate, .time = vtime };

      if ( rindex == 0 )
	{
          datetime0 = datetime;
          const int taxisID = vlistInqTaxis(vlistID);
	  if ( taxisInqType(taxisID) == TAXIS_RELATIVE )
	    {
	      taxis->type = TAXIS_RELATIVE;
	      taxis->unit = gribapiGetTimeUnits(gh);
              gribapiGetDataDateTime(gh, &(taxis->rdate), &(taxis->rtime));
	    }
	  else
	    {
	      taxis->type = TAXIS_ABSOLUTE;
	    }
          taxis->fdate = taxis->rdate;
          taxis->ftime = taxis->rtime;
	  taxis->vdate = vdate;
	  taxis->vtime = vtime;
	  taxis->sdate = sdate;
	  taxis->stime = stime;
          // printf("2: %d %6d  %d %6d  %d %6d\n", taxis->rdate, taxis->rtime, taxis->sdate, taxis->stime, taxis->vdate, taxis->vtime);
	}

      VarScanKeys scanKeys = gribapiGetScanKeys(gh);

      const int tsteptype = gribapiGetTsteptype(gh);
      const 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!", nrecs_scanned, tsID+1, varname, param, level1, level2);
              return CDI_EUFSTRUCT;
            }
          else
            {
              gribWarning("Parameter not defined at timestep 1, skipped!", nrecs_scanned, tsID+1, varname, param, level1, level2);
              continue;
            }
        }

      if ( records[recID].used )
        {
          if ( CDI_Inventory_Mode == 1 ) break;
          else
	    {
	      if ( datetimeDiffer(datetime, datetime0) ) break;

              gribWarning("Parameter already exist, skipped!", nrecs_scanned, tsID+1, varname, param, level1, level2);
	      continue;
	    }
	}

      records[recID].used = true;
      streamptr->tsteps[tsID].recIDs[rindex] = recID;

      if ( CDI_Debug )
        {
          char paramstr[32];
          cdiParamToString(param, paramstr, sizeof(paramstr));
          Message("%4d %8d name=%s id=%s ltype=%d lev1=%d lev2=%d vdate=%lld vtime=%d",
                  nrecs_scanned, (int)recpos, varname, paramstr, leveltype1, level1, level2, vdate, vtime);
        }

      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;

      const int varID = records[recID].varID;

      if ( tsteptype != vlistInqVarTsteptype(vlistID, varID) )
	vlistDefVarTsteptype(vlistID, varID, tsteptype);

      grib_handle_delete(gh);
      gh = NULL;

      rindex++;
    }

  if ( gh ) grib_handle_delete(gh);

  int nrecs = 0;
  for ( recID = 0; recID < nrecords; recID++ )
    {
      if ( ! records[recID].used )
	{
	  vlistDefVarTimetype(vlistID, records[recID].varID, TIME_CONSTANT);
	}
      else
	{
	  nrecs++;
	}
    }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  streamptr->record->buffer     = gribbuffer;
  streamptr->record->buffersize = buffersize;

  return rstatus;
}


int 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);
      record_t *records = streamptr->tsteps[tsID].records;

      nrecs = streamScanInitRecords(streamptr, tsID);

      const int fileID = streamptr->fileID;

      fileSetPos(fileID, streamptr->tsteps[tsID].position, SEEK_SET);

      int nrecs_scanned = streamptr->tsteps[0].nallrecs + streamptr->tsteps[1].nrecs*(tsID-1);    //Only used for debug output.
      int rindex = 0;
      off_t recpos = 0;
      DateTime datetime0 = { LONG_MIN, LONG_MIN };
      grib_handle *gh = NULL;
      char varname[256];
      while ( true )
	{
	  if ( rindex > nrecs ) break;

	  const size_t recsize = gribGetSize(fileID);
	  recpos = fileGetPos(fileID);
	  if ( recsize == 0 )
	    {
	      streamptr->ntsteps = streamptr->rtsteps + 1;
	      break;
	    }

	  if ( rindex >= nrecs ) break;

          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);

          nrecs_scanned++;
	  gh = grib_handle_new_from_message(NULL, gribbuffer, recsize);
	  GRIB_CHECK(my_grib_set_double(gh, "missingValue", CDI_Default_Missval), 0);

          const int param = gribapiGetParam(gh);
          int level1 = 0, level2 = 0, leveltype1, leveltype2 = -1, lbounds, level_sf, level_unit;
          var_tile_t tiles = dummy_tiles;
          gribGetLevel(gh, &leveltype1, &leveltype2, &lbounds, &level1, &level2, &level_sf, &level_unit, &tiles);

	  gribapiGetString(gh, "shortName", varname, sizeof(varname));

          int64_t vdate, sdate;
          int vtime, stime;
	  gribapiGetValidityDateTime(gh, &vdate, &vtime, &sdate, &stime);
          DateTime datetime = { .date  = vdate, .time  = vtime };

	  if ( rindex == nrecs ) break;

	  if ( rindex == 0 )
	    {
              datetime0 = datetime;
              const int taxisID = vlistInqTaxis(vlistID);
	      if ( taxisInqType(taxisID) == TAXIS_RELATIVE )
		{
		  taxis->type = TAXIS_RELATIVE;
		  taxis->unit = gribapiGetTimeUnits(gh);
                  gribapiGetDataDateTime(gh, &(taxis->rdate), &(taxis->rtime));
		}
	      else
		{
		  taxis->type = TAXIS_ABSOLUTE;
		}
              taxis->fdate = taxis->rdate;
              taxis->ftime = taxis->rtime;
	      taxis->vdate = vdate;
	      taxis->vtime = vtime;
	      taxis->sdate = sdate;
	      taxis->stime = stime;
              // printf("n: %d %6d  %d %6d  %d %6d\n", taxis->rdate, taxis->rtime, taxis->sdate, taxis->stime, taxis->vdate, taxis->vtime);
	    }

          VarScanKeys scanKeys = gribapiGetScanKeys(gh);

          const int tsteptype = gribapiGetTsteptype(gh);
          const 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!", nrecs_scanned, tsID+1, varname, param, level1, level2);
                  return CDI_EUFSTRUCT;
                }
              else
                {
                  gribWarning("Parameter not defined at timestep 1, skipped!", nrecs_scanned, tsID+1, varname, param, level1, level2);
                  continue;
                }
	    }

	  if ( CDI_Inventory_Mode != 1 )
	    {
	      if ( records[recID].used )
		{
		  if ( datetimeDiffer(datetime, datetime0) ) break;

		  if ( CDI_Debug )
                    gribWarning("Parameter already exist, skipped!", nrecs_scanned, tsID+1, varname, param, level1, level2);

		  continue;
		}
	    }

          records[recID].used = true;
          streamptr->tsteps[tsID].recIDs[rindex] = recID;

	  if ( CDI_Debug )
	    Message("%4d %8d %4d %8d %8lld %6d", rindex+1, (int)recpos, param, level1, vdate, vtime);

	  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 %8d %4d %8d %8lld %6d", rindex, (int)recpos, param, level1, vdate, vtime);

          grib_handle_delete(gh);
	  gh = NULL;

	  rindex++;
	}

      if ( gh ) grib_handle_delete(gh);

      for ( vrecID = 0; vrecID < nrecs; vrecID++ )
	{
	  recID   = streamptr->tsteps[tsID].recIDs[vrecID];
	  if ( ! records[recID].used ) break;
	}

      if ( vrecID < nrecs )
	{
	  gribWarning("Paramameter not found!", nrecs_scanned, 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

int gribapiDecode(void *gribbuffer, size_t gribsize, double *data, size_t gridsize,
		  int unreduced, size_t *nmiss, 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 ( gridsize != datasize )
    Error("Internal problem: gridsize(%zu) != datasize(%zu)!", gridsize, datasize);
  size_t dummy = datasize;
  GRIB_CHECK(grib_get_double_array(gh, "values", data, &dummy), 0);

  long lpar;
  GRIB_CHECK(grib_get_long(gh, "gridDefinitionTemplateNumber", &lpar), 0);
  int gridtype = (int) lpar;

  *nmiss = 0;
  if ( gridtype < 50 || gridtype > 53 )
    {
      GRIB_CHECK(grib_get_long(gh, "numberOfMissing", &lpar), 0);
      *nmiss = (int) lpar;
      // printf("gridtype %d, nmiss %d\n", gridtype, nmiss);
    }

  grib_handle_delete(gh);

  return status;
}


static
void gribapiDefInstitut(grib_handle *gh, int vlistID, int varID)
{
  int instID = (vlistInqInstitut(vlistID) != CDI_UNDEFID) ?
    vlistInqInstitut(vlistID) : 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) != CDI_UNDEFID) ?
    vlistInqModel(vlistID) : 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(const 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(const 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 91:
      case 1001:
      case 1101:
      case 40034:
        return 1;
    }

  return 0;
}

static
int getUnitsOfTime(const 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 ( typeOfGeneratingProcess == 4 )
    {
      proDefTempNum = (proDefTempNum == 8) ? 11 : 1;
    }

  if ( productDefinitionTemplate != -1 ) proDefTempNum = productDefinitionTemplate;

  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, int64_t date, int time, 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 ( date == 0 ) date = 10101;
  gribapiSetDataDateTime(gh, date, time);
}

static
int gribapiDefDateTimeRel(int editionNumber, grib_handle *gh, int64_t fdate, int ftime, int64_t vdate, int vtime, int64_t sdate, int stime,
                          int productDefinitionTemplate, int typeOfGeneratingProcess, int tsteptype, int timeunit, int calendar, int gcinit)
{
  int status = -1;
  int year, month, day, hour, minute, second;
  int64_t julday1, julday2, days;
  int secofday1, secofday2, secs;

  cdiDecodeDate(fdate, &year, &month, &day);
  cdiDecodeTime(ftime, &hour, &minute, &second);
  encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday1, &secofday1);

  if ( vdate == 0 && vtime == 0 ) { vdate = fdate; vtime = ftime; }

  cdiDecodeDate(vdate, &year, &month, &day);
  cdiDecodeTime(vtime, &hour, &minute, &second);
  encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday2, &secofday2);

  (void) julday_sub(julday1, secofday1, julday2, secofday2, &days, &secs);

  const int factor = getTimeunitFactor(timeunit);

  if ( !(int)(fmod(days*86400.0 + secs, factor)))
    {
      const int proDefTempNum = gribapiDefSteptype(editionNumber, gh, productDefinitionTemplate, typeOfGeneratingProcess, tsteptype, gcinit);
      gribapiDefStepUnits(editionNumber, gh, timeunit, proDefTempNum, gcinit);

      long startStep = 0;
      const double endStepF = (days*86400.0 + secs)/factor;
      const long maxStep = (editionNumber > 1) ? INT_MAX : 65000;
      if (endStepF > maxStep) return status;
      long endStep = (long) endStepF;

      if (sdate != 0 && (tsteptype == TSTEP_RANGE || tsteptype == TSTEP_AVG || tsteptype == TSTEP_ACCUM || tsteptype == TSTEP_DIFF))
        {
          cdiDecodeDate(sdate, &year, &month, &day);
          cdiDecodeTime(stime, &hour, &minute, &second);
          encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday2, &secofday2);

          (void) julday_sub(julday1, secofday1, julday2, secofday2, &days, &secs);

          startStep = (long) ((days*86400.0 + secs)/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 ( fdate == 0 ) fdate = 10101;
      gribapiSetDataDateTime(gh, fdate, ftime);

      // 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 || 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,
                    int64_t vdate, int vtime, int tsteptype, int numavg, int taxisID, int gcinit)
{
  UNUSED(numavg);

  int taxistype = -1;
  if ( taxisID != -1 ) taxistype = taxisInqType(taxisID);

  if ( typeOfGeneratingProcess == 196 )
    {
      vdate = 10101;
      vtime = 0;
      taxistype = TAXIS_ABSOLUTE;
    }
  /*
  else if ( typeOfGeneratingProcess == 9 )
    {
    }
  */

  if ( taxistype == TAXIS_RELATIVE )
    {
      const int timeunit = taxisInqTunit(taxisID);
      const int calendar = taxisInqCalendar(taxisID);

      int64_t fdate = taxisInqFdate(taxisID);
      int ftime = taxisInqFtime(taxisID);
      if (fdate == CDI_UNDEFID)
        {
          fdate = taxisInqRdate(taxisID);
          ftime = taxisInqRtime(taxisID);
        }
      if (vdate < fdate || (vdate == fdate && vtime < ftime))
        {
          fdate = vdate;
          ftime = vtime;
        }

      int64_t sdate = taxisInqSdate(taxisID);
      int stime = taxisInqStime(taxisID);

      int status = gribapiDefDateTimeRel(editionNumber, gh, fdate, ftime, vdate, vtime, sdate, stime, productDefinitionTemplate,
                                         typeOfGeneratingProcess, tsteptype, timeunit, calendar, gcinit);
      if ( status != 0 ) taxistype = TAXIS_ABSOLUTE;
    }

  if ( taxistype == TAXIS_ABSOLUTE )
    {
      gribapiDefDateTimeAbs(editionNumber, gh, vdate, vtime, productDefinitionTemplate, typeOfGeneratingProcess, tsteptype, gcinit);
    }
}

static
void gribapiDefGridRegular(grib_handle *gh, int gridID, int gridtype, bool gridIsRotated, bool gridIsCurvilinear, int uvRelativeToGrid)
{
  const char *mesg;
  size_t len;
  if ( gridtype == GRID_GAUSSIAN )
    {
      static const char mesg_gaussian[] = "regular_gg";
      len = sizeof(mesg_gaussian) - 1;
      mesg = mesg_gaussian;
    }
  else if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      static const char mesg_gaussian_reduced[] = "reduced_gg";
      len = sizeof(mesg_gaussian_reduced) - 1;
      mesg = mesg_gaussian_reduced;
    }
  else if ( gridIsRotated )
    {
      static const char mesg_rot_lonlat[] = "rotated_ll";
      len = sizeof(mesg_rot_lonlat) - 1;
      mesg = mesg_rot_lonlat;
    }
  else
    {
      static const char mesg_regular_ll[] = "regular_ll";
      len = sizeof(mesg_regular_ll) - 1;
      mesg = mesg_regular_ll;
    }
  GRIB_CHECK(my_grib_set_string(gh, "gridType", mesg, &len), 0);

  double xfirst = 0, xlast = 0, xinc = 0;
  double yfirst = 0, ylast = 0, yinc = 0;

  size_t nlon = gridInqXsize(gridID);
  size_t nlat = gridInqYsize(gridID);

  if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      nlon = 0;

      int *reducedPoints = (int *) Malloc(nlat*sizeof(int));
      long *pl    = (long *) Malloc(nlat*sizeof(long));
      gridInqReducedPoints(gridID, reducedPoints);
      for ( size_t i = 0; i < nlat; ++i ) pl[i] = reducedPoints[i];

      GRIB_CHECK(grib_set_long_array(gh, "pl", pl, nlat), 0);

      Free(pl);
      Free(reducedPoints);

      xfirst = 0;
      xinc   =        360. * 0.5 / (double)nlat;
      xlast  = 360. - 360. * 0.5 / (double)nlat;
    }
  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 ) xfirsto -= 360;
  while ( xlasto  > 360 ) xlasto  -= 360;

  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", (long)nlat), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees",  yfirst), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfLastGridPointInDegrees",   ylast), 0);

  long xscan = xfirst > xlast;
  GRIB_CHECK(my_grib_set_long(gh, "iScansNegatively", xscan), 0);
  xscan = yfirst < ylast;
  GRIB_CHECK(my_grib_set_long(gh, "jScansPositively", xscan), 0);

  if ( gridtype == GRID_GAUSSIAN || gridtype == GRID_GAUSSIAN_REDUCED )
    {
      int 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, ypole = 0, angle = 0;
      gridInqParamRLL(gridID, &xpole, &ypole, &angle);

      xpole += 180;
      if ( fabs(ypole) > 0 ) ypole = -ypole; // change from north to south pole
      if ( fabs(angle) > 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);
    }

  if ( uvRelativeToGrid >= 0 ) GRIB_CHECK(my_grib_set_long(gh, "uvRelativeToGrid", uvRelativeToGrid), 0);
}

static
long radiusToShapeOfTheEarth(double radius)
{
  long shapeOfTheEarth = 0;

  if      (IS_EQUAL(radius, 6367470.)) shapeOfTheEarth = 0;
  else if (IS_EQUAL(radius, 6378160.)) shapeOfTheEarth = 2;
  else if (IS_EQUAL(radius, 6371229.)) shapeOfTheEarth = 6;
  else if (IS_EQUAL(radius, 6371200.)) shapeOfTheEarth = 8;

  return shapeOfTheEarth;
}

static
void gribapiDefGridLCC(grib_handle *gh, int editionNumber, int gridID, int uvRelativeToGrid)
{
  const long xsize = (long) gridInqXsize(gridID);
  const long ysize = (long) gridInqYsize(gridID);

  double lon_0, lat_0, lat_1, lat_2, a, rf, xval_0, yval_0, x_0, y_0;
  gridInqParamLCC(gridID, CDI_Grid_Missval, &lon_0, &lat_0, &lat_1, &lat_2, &a, &rf, &xval_0, &yval_0, &x_0, &y_0);
  gridVerifyGribParamLCC(CDI_Grid_Missval, &lon_0, &lat_0, &lat_1, &lat_2, &a, &rf, &xval_0, &yval_0, &x_0, &y_0);
  if ( xval_0 < 0 ) xval_0 += 360;
  if ( lon_0 < 0 ) lon_0 += 360;
  const bool lsouth = (lat_1 < 0);
  if ( lsouth ) { lat_1 = -lat_2; lat_2 = -lat_2; }
  int projflag = 0;
  if ( lsouth ) gribbyte_set_bit(&projflag, 1);

  double xinc = gridInqXinc(gridID);
  double yinc = gridInqYinc(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_long(gh, "DxInMetres", lround(xinc)), 0);
  GRIB_CHECK(my_grib_set_long(gh, "DyInMetres", lround(yinc)), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfFirstGridPointInDegrees", xval_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees", yval_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "LoVInDegrees", lon_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "Latin1InDegrees", lat_1), 0);
  GRIB_CHECK(my_grib_set_double(gh, "Latin2InDegrees", lat_2), 0);
  GRIB_CHECK(my_grib_set_long(gh, "projectionCentreFlag", projflag), 0);


  const long shapeOfTheEarth = radiusToShapeOfTheEarth(a);
  if ( shapeOfTheEarth ) GRIB_CHECK(my_grib_set_long(gh, "shapeOfTheEarth", shapeOfTheEarth), 0);

  if ( uvRelativeToGrid >= 0 ) GRIB_CHECK(my_grib_set_long(gh, "uvRelativeToGrid", uvRelativeToGrid), 0);

  const long earthIsOblate = (IS_EQUAL(a, 6378160.) && IS_EQUAL(rf, 297.));
  if ( earthIsOblate ) GRIB_CHECK(my_grib_set_long(gh, "earthIsOblate", earthIsOblate), 0);

  int scanflag = 0;
  gribbyte_set_bit(&scanflag, 2);
  if ( editionNumber <= 1 )
    GRIB_CHECK(my_grib_set_long(gh, "scanningMode", (long)scanflag), 0);
}

static
void gribapiDefGridSTERE(grib_handle *gh, int gridID)
{
  const long xsize = (long) gridInqXsize(gridID);
  const long ysize = (long) gridInqYsize(gridID);

  double lon_0, lat_ts, lat_0, a, xval_0, yval_0, x_0, y_0;
  gridInqParamSTERE(gridID, CDI_Grid_Missval, &lon_0, &lat_ts, &lat_0, &a, &xval_0, &yval_0, &x_0, &y_0);
  gridVerifyGribParamSTERE(CDI_Grid_Missval, &lon_0, &lat_ts, &lat_0, &a, &xval_0, &yval_0, &x_0, &y_0);
  if ( xval_0 < 0 ) xval_0 += 360;
  int projflag = 0;

  const double xinc = gridInqXinc(gridID);
  const 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_long(gh, "DxInMetres", lround(xinc)), 0);
  GRIB_CHECK(my_grib_set_long(gh, "DyInMetres", lround(yinc)), 0);
  GRIB_CHECK(my_grib_set_double(gh, "longitudeOfFirstGridPointInDegrees", xval_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "latitudeOfFirstGridPointInDegrees", yval_0), 0);
  GRIB_CHECK(my_grib_set_double(gh, "LaDInDegrees", lat_ts), 0);
  GRIB_CHECK(my_grib_set_double(gh, "orientationOfTheGridInDegrees", lon_0), 0);
  const long southPoleOnProjectionPlane = IS_EQUAL(lat_0, -90.);
  GRIB_CHECK(my_grib_set_double(gh, "southPoleOnProjectionPlane", southPoleOnProjectionPlane), 0);
  GRIB_CHECK(my_grib_set_long(gh, "projectionCentreFlag", projflag), 0);

  const long shapeOfTheEarth = radiusToShapeOfTheEarth(a);
  if ( shapeOfTheEarth ) GRIB_CHECK(my_grib_set_long(gh, "shapeOfTheEarth", shapeOfTheEarth), 0);

  GRIB_CHECK(my_grib_set_long(gh, "resolutionAndComponentFlags", 8), 0);
  GRIB_CHECK(my_grib_set_long(gh, "iScansNegatively", 0), 0);
  GRIB_CHECK(my_grib_set_long(gh, "jScansPositively", 1), 0);
}

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;

  const 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 number = 0;
      cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, &number);
      if ( number < 0 ) number = 0;
      GRIB_CHECK(my_grib_set_long(gh, "numberOfGridUsed", number), 0);

      int position = 0;
      cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDINREFERENCE, &position);
      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;
      cdiInqKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
      if (grib_set_bytes(gh, "uuidOfHGrid", uuid, &len) != 0)
        Warning("Can't write UUID!");
    }
}

static
void gribapiDefGridSpectral(grib_handle *gh, int gridID)
{
  const 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, size_t 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)
{
  const size_t gridsize = gridInqSize(gridID);
  bool gridIsRotated = false;
  bool gridIsCurvilinear = false;
  const 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:
      {
        gribapiDefGridLCC(gh, editionNumber, gridID, uvRelativeToGrid);
	break;
      }
    case CDI_PROJ_STERE:
      {
        gribapiDefGridSTERE(gh, gridID);
	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)
{
  double scaled_value  = level;
  long   iscaled_value = lround(scaled_value);
  long   i;

  const double eps = 1.e-8;
  for ( i=0; (fabs(scaled_value - (double) iscaled_value) >= eps) && i < 7; i++ )
    {
      scaled_value *= 10.;
      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 leveltype, bool lbounds, double level, double dlevel1, double dlevel2)
{
  gribapiDefLevelType(gh, gcinit, "indicatorOfTypeOfLevel", leveltype);

  if ( lbounds )
    {
      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 lbounds, double level, double dlevel1, double dlevel2)
{
  gribapiDefLevelType(gh, gcinit, "typeOfFirstFixedSurface", leveltype1);
  if ( lbounds ) gribapiDefLevelType(gh, gcinit, "typeOfSecondFixedSurface", leveltype2);

  if ( !lbounds ) 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 ( lbounds )
    {
      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)
{
  char units[CDI_MAX_NAME];
  bool lbounds = false;

  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);
  double level = zaxisInqLevels(zaxisID, NULL) ? zaxisInqLevel(zaxisID, levelID) : levelID+1;

  double dlevel1 = level, dlevel2 = 0;
  if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
    {
      lbounds = true;
      dlevel1 = zaxisInqLbound(zaxisID, levelID);
      dlevel2 = zaxisInqUbound(zaxisID, levelID);
    }

  if ( zaxistype == ZAXIS_GENERIC && ltype == 0 )
    {
      Message("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 = (editionNumber <= 1) ? zaxisTypeToGrib1ltype(zaxistype) : zaxisTypeToGrib2ltype(zaxistype);
  long grib_ltype2 = (ltype != ltype2 && ltype2 != -1) ? ltype2 : grib_ltype;

  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 = 1;
            int length = CDI_MAX_NAME;
            cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
            if ( units[1] == 'm' && !units[2] )
              {
                if      ( units[0] == 'c' ) sf = 0.01;
                else if ( units[0] == 'd' ) sf = 0.1;
                else if ( units[0] == 'k' ) sf = 1000;
              }
            if ( IS_NOT_EQUAL(sf, 1) )
              {
                level   *= sf;
                dlevel1 *= sf;
                dlevel2 *= sf;
              }
          }

        if ( editionNumber <= 1 )
          {
            grib1DefLevel(gh, gcinit, grib_ltype, lbounds, level, dlevel1, dlevel2);
          }
        else
          {
            /* 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"

               The key/value pairs that are set in "grib2DefLevel" do not exist for this template.
            */
            if ( proddef_template_num != 32 )
              grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype2, lbounds, 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:
      {
        if ( editionNumber <= 1 )
          grib1DefLevel(gh, gcinit, grib_ltype, lbounds, level, dlevel1, dlevel2);
        else
          grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype2, lbounds, level, dlevel1, dlevel2);

        break;
      }
    case ZAXIS_HYBRID:
    case ZAXIS_HYBRID_HALF:
      {
        if ( editionNumber <= 1 )
          {
            grib_ltype = lbounds ? GRIB1_LTYPE_HYBRID_LAYER : GRIB1_LTYPE_HYBRID;
            grib1DefLevel(gh, gcinit, grib_ltype, lbounds, level, dlevel1, dlevel2);
          }
        else
          {
            grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype, lbounds, 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);

        int length = CDI_MAX_NAME;
        cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
        if ( units[0] && (units[0] != 'P') | (units[1] != 'a') )
          {
            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;

            grib1DefLevel(gh, gcinit, grib_ltype, lbounds, level, dlevel1, dlevel2);
	  }
	else
	  {
            if ( ltype2 == -1 ) ltype2 = GRIB2_LTYPE_ISOBARIC;
            grib2DefLevel(gh, gcinit, GRIB2_LTYPE_ISOBARIC, ltype2, lbounds, level, dlevel1, dlevel2);
	  }

	break;
      }
    case ZAXIS_SNOW:
      {
        if ( editionNumber <= 1 )
          ; // not available
	else
          {
            grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype, lbounds, level, dlevel1, dlevel2);
          }

	break;
      }
    case ZAXIS_DEPTH_BELOW_LAND:
      {
        int length = CDI_MAX_NAME;
        cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
        double sf; //scalefactor

	if ( editionNumber <= 1 )
	  {
	    if      ( memcmp(units, "mm", 2) == 0 ) sf =   0.1;
	    else if ( memcmp(units, "cm", 2) == 0 ) sf =   1; // cm
	    else if ( memcmp(units, "dm", 2) == 0 ) sf =  10;
	    else                                    sf = 100;

            grib1DefLevel(gh, gcinit, grib_ltype, lbounds, level*sf, dlevel1*sf, dlevel2*sf);
	  }
	else
	  {
	    if      ( memcmp(units, "mm", 2) == 0 ) sf = 0.001;
	    else if ( memcmp(units, "cm", 2) == 0 ) sf = 0.01;
	    else if ( memcmp(units, "dm", 2) == 0 ) sf = 0.1;
	    else                                    sf = 1; // meter

            grib2DefLevel(gh, gcinit, grib_ltype, grib_ltype, lbounds, 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 ( lbounds )
              {
                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, lbounds, 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, 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:
      {
	if ( editionNumber <= 1 )
          {
            grib1DefLevel(gh, gcinit, ltype, lbounds, level, dlevel1, dlevel2);
          }
        else
          {
            grib2DefLevel(gh, gcinit, ltype, ltype, lbounds, 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;

  const long iScansNegatively      = (scanningMode & 128)/128;
  const long jScansPositively      = (scanningMode & 64)/64;
  const long jPointsAreConsecutive = (scanningMode & 32)/32;

  if (cdiDebugExt>=30)
  {
    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 i,j;
  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 (j=0; j<jDim; j++)
      {
        const size_t jXiDim = j*iDim;
        for (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 (j=0; j<jDim; j++)
      {
        size_t jXiDim = j*iDim;
        for (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 (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 (i=0; i<iDim; i++)
            {
              for (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 (j=0; j<jDim; j++)
        {
          size_t jXiDim = j*iDim;
          for (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 (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 (j=0; j<jDim; j++)
        {
          if (cdiDebugExt>=30) printf("convertDataScanningMode():  processing row nr. %04zu;\n", j);
          jInv = (jDim-1) -j;
          for (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 (j=0; j<jDim; j++)
        {
          if (cdiDebugExt>=30) printf("convertDataScanningMode():  processing row nr. %04zu;\n", j);
          size_t jXiDim = j*iDim;
          for (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 (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 (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 (j=0; j<jDim; j++)
      {
        size_t jXiDim = j*iDim;
        for (i=0; i<iDim; i++)
        {
          idxIN = i + jXiDim;
          printf("%03.0f, ",data[idxIN]);
        }
        printf("\n");
      }
  }

  free(dataCopy); return;
}
#endif //HIRLAM_EXTENSIONS

static
void gribapiSetExtMode(grib_handle *gh, int gridID, size_t datasize, const double *data)
{
  /*
  Nj = 550;
  latitudeOfFirstGridPointInDegrees = -30.8;
  latitudeOfLastGridPointInDegrees = 24.1;
  iScansNegatively = 0;
  jScansPositively = 0;
  jPointsAreConsecutive = 0;
  jDirectionIncrementInDegrees = 0.1; */
#ifndef HIRLAM_EXTENSIONS
  (void)data;
  (void)datasize;
#endif
  int gridtype = gridInqType(gridID);
  if ( gridtype == GRID_PROJECTION )
    {
      int projtype = gridInqProjType(gridID);
      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;
    }

  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)
        {
          size_t gridsize = gridInqSize(gridID);
          Message("(scanModeIN=%d; gridsize=%zu", scanModeIN, gridsize);
        }

      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 varID, int levelID, int vlistID, int gridID, int zaxisID,
		     int64_t vdate, int vtime, int tsteptype, int numavg,
		     size_t datasize, const double *data, size_t nmiss, void **gribbuffer, size_t *gribbuffersize,
		     int comptype, void *gribContainer)
{
  void *dummy = NULL;
  /*  int ensID, ensCount, forecast_type; *//* Ensemble Data */
  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 = 0;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_PRODUCTDEFINITIONTEMPLATE, &productDefinitionTemplate);

  int uvRelativeToGrid = -1;
  cdiInqKeyInt(vlistID, varID, CDI_KEY_UVRELATIVETOGRID, &uvRelativeToGrid);

  char name[256], stdname[256];
  vlistInqVarName(vlistID, varID, name);
  vlistInqVarStdname(vlistID, varID, stdname);

#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 ( 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, vdate, vtime, 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_TYPEOFENSEMBLEFORECAST, &typeOfEnsembleForecast);
    if ( status == 0 ) grib_set_long(gh, "typeOfEnsembleForecast", 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);
  }

  if ( ! gc->init ) gribapiDefInstitut(gh, vlistID, varID);
  if ( ! gc->init ) gribapiDefModel(gh, vlistID, varID);

  if ( ! gc->init ) 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 = 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(section2PaddingLength);
          cdiInqKeyBytes(vlistID, varID, CDI_KEY_SECTION2PADDING, section2Padding, &section2PaddingLength);
          size_t len = section2PaddingLength;
          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);
  //if (!gc->init)
  {
    int ret = 0;

    /* 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 ( zaxisInqSize(zaxisID)==(levelID+1) )
              vlistptr->vars[varID].opt_grib_kvpair[i].update = false;

            if (vlistptr->vars[varID].opt_grib_kvpair[i].data_type == t_double)
              {
                if ( CDI_Debug )
                  Message("key \"%s\"  :   double value = %g\n",
                          vlistptr->vars[varID].opt_grib_kvpair[i].keyword,
                          vlistptr->vars[varID].opt_grib_kvpair[i].dbl_val);
                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\n",
                          vlistptr->vars[varID].opt_grib_kvpair[i].keyword,
                          vlistptr->vars[varID].opt_grib_kvpair[i].int_val);
                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);
              }
          }
      }
  }

  if ( nmiss > 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, datasize, data);

  GRIB_CHECK(grib_set_double_array(gh, "values", data, datasize), 0);

  /* get the size of coded message  */
  size_t recsize = 0;
  GRIB_CHECK(grib_get_message(gh, (const void **)&dummy, &recsize), 0);
  recsize += 512; /* add some space for possible filling */
  *gribbuffersize = recsize;
  *gribbuffer = Malloc(*gribbuffersize);

  if ( ! 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];
          gribapiGetString(gh, "shortName", grb_name, sizeof(grb_name));
          strToLower(cdi_name);
          strToLower(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)) )
            Message("*** 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 (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_DOUBLE_PRECISION) ? 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_DOUBLE_PRECISION : EXSE_SINGLE_PRECISION;
}

void iegReadRecord(stream_t *streamptr, double *data, size_t *nmiss)
{
  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].records[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *iegp = streamptr->record->exsep;
  int status = iegRead(fileID, iegp);
  if ( status != 0 ) Error("Could not read IEG record!");

  iegInqDataDP(iegp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  int gridID  = vlistInqVarGrid(vlistID, varID);
  size_t size = gridInqSize(gridID);

  streamptr->numvals += size;

  *nmiss = 0;
  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;
	(*nmiss)++;
      }
}

static
int iegGetZaxisType(int iegleveltype)
{
  int leveltype = 0;

  switch ( iegleveltype )
    {
    case IEG_LTYPE_SURFACE:
      {
	leveltype = ZAXIS_SURFACE;
	break;
      }
    case IEG_LTYPE_99:
    case IEG_LTYPE_ISOBARIC:
      {
	leveltype = ZAXIS_PRESSURE;
	break;
      }
    case IEG_LTYPE_HEIGHT:
      {
	leveltype = ZAXIS_HEIGHT;
	break;
      }
    case IEG_LTYPE_ALTITUDE:
      {
	leveltype = ZAXIS_ALTITUDE;
	break;
      }
    case IEG_LTYPE_HYBRID:
    case IEG_LTYPE_HYBRID_LAYER:
      {
	leveltype = ZAXIS_HYBRID;
	break;
      }
    case IEG_LTYPE_LANDDEPTH:
    case IEG_LTYPE_LANDDEPTH_LAYER:
      {
	leveltype = ZAXIS_DEPTH_BELOW_LAND;
	break;
      }
    case IEG_LTYPE_SEADEPTH:
      {
	leveltype = ZAXIS_DEPTH_BELOW_SEA;
	break;
      }
    default:
      {
	leveltype = ZAXIS_GENERIC;
	break;
      }
    }

  return leveltype;
}


static void iegDefTime(int *pdb, int64_t date, int time, int taxisID)
{
  int timetype = (taxisID != -1) ? taxisInqType(taxisID) : -1;
  if ( timetype == TAXIS_ABSOLUTE || timetype == TAXIS_RELATIVE )
    {
      int year, month, day, hour, minute, second;
      cdiDecodeDate(date, &year, &month, &day);
      cdiDecodeTime(time, &hour, &minute, &second);

      IEG_P_Year(pdb)     = year;
      IEG_P_Month(pdb)    = month;
      IEG_P_Day(pdb)      = day;
      IEG_P_Hour(pdb)     = hour;
      IEG_P_Minute(pdb)   = 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 };
  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;

  size_t xsize = gridInqXsize(gridID);
  size_t 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, xlast = 0, xinc = 0;
      double yfirst = 0, ylast = 0, yinc = 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, ypole = 0, angle = 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 )
    {
      Message("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 iegCopyRecord(stream_t *streamptr2, stream_t *streamptr1)
{
  streamFCopyRecord(streamptr2, streamptr1, "IEG");
}


void iegDefRecord(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;

  int gridID  = vlistInqVarGrid(vlistID, varID);
  int zaxisID = vlistInqVarZaxis(vlistID, varID);

  iegrec_t *iegp = (iegrec_t*) record->exsep;
  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;

  int64_t date = streamptr->tsteps[tsID].taxis.vdate;
  int time = streamptr->tsteps[tsID].taxis.vtime;
  iegDefTime(iegp->ipdb, date, time, vlistInqTaxis(vlistID));
  iegDefGrid(iegp->igdb, gridID);
  iegDefLevel(iegp->ipdb, iegp->igdb, iegp->vct, zaxisID, levelID);

  iegp->dprec = iegDefDatatype(record->prec);
}


void iegWriteRecord(stream_t *streamptr, const double *data)
{
  Record *record = streamptr->record;
  iegrec_t *iegp = (iegrec_t*) record->exsep;

  int fileID = streamptr->fileID;
  size_t gridsize = gridInqSize(record->gridID);

  double refval = data[0];
  for ( size_t i = 1; i < gridsize; i++ )
    if ( data[i] < refval ) refval = data[i];

  iegp->refval = refval;

  iegDefDataDP(iegp, data);
  iegWrite(fileID, iegp);
}

static
void iegAddRecord(stream_t *streamptr, int param, int *pdb, int *gdb, double *vct,
		  size_t recsize, off_t position, int prec)
{
  const int vlistID = streamptr->vlistID;
  const int tsID    = streamptr->curTsID;
  const int recID   = recordNewEntry(streamptr, tsID);
  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    = 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);
  cdiGridTypeInit(grid, gridtype, IEG_G_NumLon(gdb)*IEG_G_NumLat(gdb));
  const size_t xsize = (size_t)IEG_G_NumLon(gdb);
  const size_t ysize = (size_t)IEG_G_NumLat(gdb);
  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;
  const 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 / (xsize - 1);

	/* correct xinc if necessary */
	if ( IEG_G_FirstLon(gdb) == 0 && IEG_G_LastLon(gdb) > 354000 )
	  {
	    const double xinc = 360. / 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 / (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);
  const int gridID = gridAdded.Id;
  if ( !gridAdded.isNew ) 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);
    }

  const int lbounds = IEG_P_LevelType(pdb) == IEG_LTYPE_HYBRID_LAYER ? 1 : 0;

  const 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);

  record->varID   = (short)varID;
  record->levelID = (short)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
void iegDateTime(int *pdb, int64_t *date, int *time)
{
  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;

  *date = cdiEncodeDate(ryear, rmonth, rday);
  *time = cdiEncodeTime(rhour, rminute, 0);
}

static
void iegScanTimestep1(stream_t *streamptr)
{
  DateTime datetime0 = { LONG_MIN, LONG_MIN };
  off_t recpos;
  iegrec_t *iegp = (iegrec_t*) streamptr->record->exsep;

  streamptr->curTsID = 0;

  int tsID = tstepsNewEntry(streamptr);
  if ( tsID != 0 ) Error("Internal problem! tstepsNewEntry returns %d", tsID);
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  const int fileID = streamptr->fileID;

  int nrecs = 0;
  while ( true )
    {
      recpos = fileGetPos(fileID);
      if ( iegRead(fileID, iegp) != 0 )
	{
	  streamptr->ntsteps = 1;
	  break;
	}

      const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

      const int prec   = iegp->dprec;
      const int rcode  = IEG_P_Parameter(iegp->ipdb);
      const int tabnum = IEG_P_CodeTable(iegp->ipdb);
      const 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;

      int64_t vdate = 0;
      int vtime = 0;
      iegDateTime(iegp->ipdb, &vdate, &vtime);
      DateTime datetime = { .date = vdate, .time = vtime};

      if ( nrecs == 0 )
	{
	  datetime0 = datetime;
          taxis->vdate = vdate;
          taxis->vtime = vtime;
	}
      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 ( datetimeDiffer(datetime, datetime0) )
	    Warning("Inconsistent verification time for param %d level %d", param, rlevel);
	}

      nrecs++;

      if ( CDI_Debug )
	Message("%4d%8d%4d%8d%8ld%6d", nrecs, (int)recpos, param, rlevel, (long)vdate, vtime);

      iegAddRecord(streamptr, param, iegp->ipdb, iegp->igdb, iegp->vct, recsize, recpos, prec);
    }

  tstepScanLoopFinished:
  streamptr->rtsteps = 1;

  cdi_generate_vars(streamptr);

  const int taxisID = taxisCreate(TAXIS_ABSOLUTE);
  taxis->type  = TAXIS_ABSOLUTE;
  taxis->rdate = taxis->vdate;
  taxis->rtime = taxis->vtime;

  const 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->exsep;

  streamptr->curTsID = 1;

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const 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);
  record_t *records = streamptr->tsteps[tsID].records;

  const int nrecords = streamScanInitRecords2(streamptr);

  for ( int rindex = 0; rindex <= nrecords; rindex++ )
    {
      recpos = fileGetPos(fileID);
      if ( iegRead(fileID, iegp) != 0 )
	{
	  streamptr->ntsteps = 2;
	  break;
	}

      const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

      const int rcode  = IEG_P_Parameter(iegp->ipdb);
      const int tabnum = IEG_P_CodeTable(iegp->ipdb);
      const 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;

      int64_t vdate = 0;
      int vtime = 0;
      iegDateTime(iegp->ipdb, &vdate, &vtime);

      if ( rindex == 0 )
	{
	  taxis->type  = TAXIS_ABSOLUTE;
	  taxis->vdate = vdate;
	  taxis->vtime = vtime;
	}

      bool nextstep = false;
      int recID = 0;
      for ( recID = 0; recID < nrecords; recID++ )
	{
	  if ( param == records[recID].param && rlevel == records[recID].ilevel )
	    {
	      if ( records[recID].used )
		{
		  nextstep = true;
		}
	      else
		{
		  records[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%8ld%6d", rindex+1, (int)recpos, param, rlevel, (long)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 ( ! records[recID].used )
	{
          vlistDefVarTimetype(vlistID, records[recID].varID, TIME_CONSTANT);
	}
      else
	{
	  nrecs++;
	}
    }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  return 0;
}


int iegInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  iegScanTimestep1(streamptr);

  const 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->exsep;

  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);
      record_t *records = streamptr->tsteps[tsID].records;

      nrecs = streamScanInitRecords(streamptr, tsID);

      const 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;
	    }

	  const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

	  const int rcode  = IEG_P_Parameter(iegp->ipdb);
	  const int tabnum = IEG_P_CodeTable(iegp->ipdb);
	  const 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;

          int64_t vdate = 0;
          int vtime = 0;
	  iegDateTime(iegp->ipdb, &vdate, &vtime);

	  // if ( rindex == nrecs ) break; gcc-4.5 internal compiler error
	  if ( rindex == nrecs ) continue;
	  const int recID = streamptr->tsteps[tsID].recIDs[rindex];

	  if ( rindex == 0 )
	    {
	      taxis->type  = TAXIS_ABSOLUTE;
	      taxis->vdate = vdate;
	      taxis->vtime = 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%8ld%6d", rindex, (int)recpos, param, rlevel, (long)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 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 *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  void *iegp = streamptr->record->exsep;

  int vlistID  = streamptr->vlistID;
  int fileID   = streamptr->fileID;
  /* NOTE: tiles are not supported here! */
  double missval = vlistInqVarMissval(vlistID, varID);
  size_t gridsize = gridInqSize(vlistInqVarGrid(vlistID, varID));
  int tsid     = streamptr->curTsID;

  off_t currentfilepos = fileGetPos(fileID);

  /* NOTE: tiles are not supported here! */
  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);
  iegInqDataDP(iegp, data);

  fileSetPos(fileID, currentfilepos, SEEK_SET);

  *nmiss = 0;
  for ( size_t i = 0; i < gridsize; i++ )
    if ( DBL_IS_EQUAL(data[i], missval) || DBL_IS_EQUAL(data[i], (float)missval) )
      {
	data[i] = missval;
	(*nmiss)++;
      }
}


void iegReadVarDP(stream_t *streamptr, int varID, double *data, size_t *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = 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], nmiss);
}


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->exsep;
  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;
  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;

  int64_t date = streamptr->tsteps[tsID].taxis.vdate;
  int time     = streamptr->tsteps[tsID].taxis.vtime;
  iegDefTime(iegp->ipdb, date, time, vlistInqTaxis(vlistID));
  iegDefGrid(iegp->igdb, gridID);
  iegDefLevel(iegp->ipdb, iegp->igdb, iegp->vct, zaxisID, levID);

  iegp->dprec = iegDefDatatype(vlistInqVarDatatype(vlistID, varID));

  size_t gridsize = gridInqSize(gridID);

  double refval = data[0];
  for ( size_t i = 1; i < gridsize; i++ )
    if ( data[i] < refval ) refval = data[i];

  iegp->refval = refval;

  iegDefDataDP(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 = gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevs    = (size_t) zaxisInqSize(vlistInqVarZaxis(vlistID, varID));

  for ( size_t levID = 0;  levID < nlevs; 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 recordInitEntry(record_t *record)
{
  record->position  = CDI_UNDEFID;
  record->size      = 0;
  record->gridsize  = 0;
  record->param     = 0;
  record->ilevel    = CDI_UNDEFID;
  record->used      = false;
  record->tsteptype = CDI_UNDEFID;
  record->varID     = CDI_UNDEFID;
  record->levelID   = CDI_UNDEFID;
  memset(record->varname, 0, sizeof(record->varname));
  varScanKeysInit(&record->scanKeys);
  memset(&record->tiles, 0, sizeof(record->tiles));
}


int recordNewEntry(stream_t *streamptr, int tsID)
{
  size_t recordID = 0;
  size_t recordSize = (size_t)streamptr->tsteps[tsID].recordSize;
  record_t *records = streamptr->tsteps[tsID].records;
  /*
    Look for a free slot in record.
    (Create the table the first time through).
  */
  if ( ! recordSize )
    {
      recordSize = 1;   /*  <<<<----  */
      records = (record_t *) Malloc(recordSize * sizeof (record_t));

      for ( size_t i = 0; i < recordSize; i++ )
	records[i].used = CDI_UNDEFID;
    }
  else
    {
      while ( recordID < recordSize && records[recordID].used != CDI_UNDEFID )
        ++recordID;
    }
  /*
    If the table overflows, double its size.
  */
  if ( recordID == recordSize )
    {
      if (recordSize <= INT_MAX / 2)
        recordSize *= 2;
      else if (recordSize < INT_MAX)
        recordSize = INT_MAX;
      else
        Error("Cannot handle this many records!\n");
      records = (record_t *) Realloc(records, recordSize * sizeof (record_t));

      for ( size_t i = recordID; i < recordSize; i++ )
	records[i].used = CDI_UNDEFID;
    }

  recordInitEntry(&records[recordID]);

  records[recordID].used = 1;

  streamptr->tsteps[tsID].recordSize = (int)recordSize;
  streamptr->tsteps[tsID].records    = records;

  return (int)recordID;
}

static
void cdiInitRecord(stream_t *streamptr)
{
  Record *record = (Record *) Malloc(sizeof(Record));
  streamptr->record = record;

  record->param      = 0;
  record->level      = 0;
  record->date       = 0;
  record->time       = 0;
  record->gridID     = 0;
  record->buffer     = NULL;
  record->buffersize = 0;
  record->position   = 0;
  record->varID      = 0;
  record->levelID    = CDI_UNDEFID;
}


void streamInqRecord(int streamID, int *varID, int *levelID)
{
  check_parg(varID);
  check_parg(levelID);

  stream_t *streamptr = stream_to_pointer(streamID);

  cdiDefAccesstype(streamID, TYPE_REC);

  if ( ! streamptr->record ) cdiInitRecord(streamptr);

  int tsID   = streamptr->curTsID;
  int rindex = streamptr->tsteps[tsID].curRecID + 1;

  if ( rindex >= streamptr->tsteps[tsID].nrecs )
    Error("record %d not available at timestep %d", rindex+1, tsID+1);

  int recID  = streamptr->tsteps[tsID].recIDs[rindex];

  if ( recID == -1 || recID >= streamptr->tsteps[tsID].nallrecs )
    Error("Internal problem! tsID = %d recID = %d", tsID, recID);

  *varID   = streamptr->tsteps[tsID].records[recID].varID;
  int lindex = streamptr->tsteps[tsID].records[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", streamID, tsID, recID, *varID, *levelID);

  streamptr->curTsID = tsID;
  streamptr->tsteps[tsID].curRecID = rindex;
}

/*
@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 streamDefRecord(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 level   = (int)(zaxisInqLevel(zaxisID, levelID));

  Record *record = streamptr->record;
  record->varID    = varID;
  record->levelID  = levelID;
  record->param    = param;
  record->level    = level;
  record->date     = streamptr->tsteps[tsID].taxis.vdate;
  record->time     = streamptr->tsteps[tsID].taxis.vtime;
  record->gridID   = gridID;
  record->prec     = vlistInqVarDatatype(vlistID, varID);

  switch (cdiBaseFiletype(streamptr->filetype))
    {
#ifdef HAVE_LIBGRIB
    case CDI_FILETYPE_GRB:
    case CDI_FILETYPE_GRB2:
      grbDefRecord(streamptr);
      break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      srvDefRecord(streamptr);
      break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      extDefRecord(streamptr);
      break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      iegDefRecord(streamptr);
      break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      if ( streamptr->accessmode == 0 ) cdfEndDef(streamptr);
      cdfDefRecord(streamptr);
      break;
#endif
    default:
      Error("%s support not compiled in!", strfiletype(streamptr->filetype));
      break;
    }
}


void streamCopyRecord(int streamID2, int streamID1)
{
  stream_t *streamptr1 = stream_to_pointer(streamID1),
    *streamptr2 = stream_to_pointer(streamID2);
  int filetype1 = streamptr1->filetype,
    filetype2 = streamptr2->filetype,
    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_GRB:
    case CDI_FILETYPE_GRB2:
      grbCopyRecord(streamptr2, streamptr1);
      break;
#endif
#ifdef HAVE_LIBSERVICE
    case CDI_FILETYPE_SRV:
      srvCopyRecord(streamptr2, streamptr1);
      break;
#endif
#ifdef HAVE_LIBEXTRA
    case CDI_FILETYPE_EXT:
      extCopyRecord(streamptr2, streamptr1);
      break;
#endif
#ifdef HAVE_LIBIEG
    case CDI_FILETYPE_IEG:
      iegCopyRecord(streamptr2, streamptr1);
      break;
#endif
#ifdef HAVE_LIBNETCDF
    case CDI_FILETYPE_NETCDF:
      cdfCopyRecord(streamptr2, streamptr1);
      break;
#endif
    default:
      {
	Error("%s support not compiled in!", strfiletype(filetype));
	break;
      }
    }
}


void cdi_create_records(stream_t *streamptr, int tsID)
{
  unsigned nrecords, maxrecords;

  tsteps_t *sourceTstep = streamptr->tsteps;
  tsteps_t *destTstep = sourceTstep + tsID;

  if ( destTstep->records ) 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;
      for ( unsigned recID = 0; recID < maxrecords; recID++ )
	{
	  int varID = sourceTstep->records[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 = (unsigned)streamptr->tsteps[1].nallrecs;
    }
  //  printf("tsID, nrecords %d %d\n", tsID, nrecords);

  record_t *records = NULL;
  if ( maxrecords > 0 ) records = (record_t *) Malloc(maxrecords*sizeof(record_t));

  destTstep->records    = records;
  destTstep->recordSize = (int)maxrecords;
  destTstep->nallrecs   = (int)nrecords;

  if ( tsID == 0 )
    {
      for ( unsigned recID = 0; recID < maxrecords; recID++ )
        recordInitEntry(&destTstep->records[recID]);
    }
  else
    {
      memcpy(destTstep->records, sourceTstep->records, (size_t)maxrecords*sizeof(record_t));

      for ( unsigned recID = 0; recID < maxrecords; recID++ )
	{
          record_t *curRecord = &sourceTstep->records[recID];
          destTstep->records[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 )
                {
                  destTstep->records[recID].position = CDI_UNDEFID;
                  destTstep->records[recID].size     = 0;
                  destTstep->records[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_DOUBLE_PRECISION) ? 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_DOUBLE_PRECISION : EXSE_SINGLE_PRECISION;
}

/* not used
int srvInqRecord(stream_t *streamptr, int *varID, int *levelID)
{
  int status;
  int fileID;
  int icode, ilevel;
  int zaxisID = -1;
  int header[8];
  int vlistID;
  void *srvp = streamptr->record->exsep;

  vlistID = streamptr->vlistID;
  fileID  = streamptr->fileID;

  *varID   = -1;
  *levelID = -1;

  status = srvRead(fileID, srvp);
  if ( status != 0 ) return (0);

  srvInqHeader(srvp, header);

  icode  = header[0];
  ilevel = header[1];

  *varID = vlistInqVarID(vlistID, icode);

  if ( *varID == CDI_UNDEFID ) Error("Code %d undefined", icode);

  zaxisID = vlistInqVarZaxis(vlistID, *varID);

  *levelID = zaxisInqLevelID(zaxisID, (double) ilevel);

  return 1;
}
*/

void srvReadRecord(stream_t *streamptr, double *data, size_t *nmiss)
{
  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].records[recID].varID;
  off_t recpos = streamptr->tsteps[tsID].records[recID].position;

  fileSetPos(fileID, recpos, SEEK_SET);

  void *srvp = streamptr->record->exsep;
  int status = srvRead(fileID, srvp);
  if ( status != 0 ) Error("Failed to read record from SRV file");

  int header[8];
  srvInqHeader(srvp, header);
  srvInqDataDP(srvp, data);

  double missval = vlistInqVarMissval(vlistID, varID);
  int gridID  = vlistInqVarGrid(vlistID, varID);
  size_t size = gridInqSize(gridID);

  streamptr->numvals += size;

  *nmiss = 0;
  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;
	(*nmiss)++;
      }
}


void srvCopyRecord(stream_t *streamptr2, stream_t *streamptr1)
{
  streamFCopyRecord(streamptr2, streamptr1, "SRV");
}


void srvDefRecord(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->level;
  header[2] = record->date;
  header[3] = record->time;

  int gridID = record->gridID;
  size_t 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->exsep;
  srvp->dprec = srvDefDatatype(record->prec);

  srvDefHeader(srvp, header);
}


void srvWriteRecord(stream_t *streamptr, const double *data)
{
  void *srvp = streamptr->record->exsep;
  srvDefDataDP(srvp, data);
  srvWrite(streamptr->fileID, srvp);
}

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)
{
  const int vlistID = streamptr->vlistID;
  const int tsID    = streamptr->curTsID;
  const int recID   = recordNewEntry(streamptr, tsID);
  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);
  const int gridID = gridAdded.Id;
  if (!gridAdded.isNew) Free(grid);

  const int leveltype = ZAXIS_GENERIC;
  const 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);
  record->varID   = (short)varID;
  record->levelID = (short)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)
{
  DateTime datetime0 = { LONG_MIN, LONG_MIN };
  off_t recpos;
  srvrec_t *srvp = (srvrec_t*) streamptr->record->exsep;

  streamptr->curTsID = 0;

  int tsID  = tstepsNewEntry(streamptr);
  if ( tsID != 0 ) Error("Internal problem! tstepsNewEntry returns %d", tsID);
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  const int fileID = streamptr->fileID;

  int nrecs = 0;
  while ( true )
    {
      recpos = fileGetPos(fileID);
      if ( srvRead(fileID, srvp) != 0 )
	{
	  streamptr->ntsteps = 1;
	  break;
	}

      const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

      int header[8];
      srvInqHeader(srvp, header);

      const int prec   = srvp->dprec;
      const int rcode  = header[0];
      const int rlevel = header[1];
      const int vdate  = header[2];
      const int vtime  = header[3];
      const int rxsize = header[4];
      const int rysize = header[5];
      const int param  = cdiEncodeParam(rcode, 255, 255);
      DateTime datetime = { .date = vdate, .time = vtime };

      if ( nrecs == 0 )
	{
	  datetime0 = datetime;
          taxis->vdate = vdate;
          taxis->vtime = vtime;
	}
      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 ( datetimeDiffer(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, rxsize, rysize, recsize, recpos, prec);
    }

  tstepScanLoopFinished:
  streamptr->rtsteps = 1;

  cdi_generate_vars(streamptr);

  const int taxisID = taxisCreate(TAXIS_ABSOLUTE);
  taxis->type = TAXIS_ABSOLUTE;
  taxis->rdate = taxis->vdate;
  taxis->rtime = taxis->vtime;

  const 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->exsep;

  streamptr->curTsID = 1;

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const 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);
  record_t *records = streamptr->tsteps[tsID].records;

  const int nrecords = streamScanInitRecords2(streamptr);

  for ( int rindex = 0; rindex <= nrecords; rindex++ )
    {
      recpos = fileGetPos(fileID);
      if ( srvRead(fileID, srvp) != 0 )
	{
	  streamptr->ntsteps = 2;
	  break;
	}

      const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

      srvInqHeader(srvp, header);

      const int rcode  = header[0];
      const int rlevel = header[1];
      const int vdate  = header[2];
      const int vtime  = header[3];
      const int param  = cdiEncodeParam(rcode, 255, 255);

      if ( rindex == 0 )
	{
	  taxis->type  = TAXIS_ABSOLUTE;
	  taxis->vdate = vdate;
	  taxis->vtime = vtime;
	}

      bool nextstep = false;
      int recID;
      for ( recID = 0; recID < nrecords; recID++ )
	{
	  if ( param == records[recID].param && rlevel == records[recID].ilevel )
	    {
	      if ( records[recID].used )
		{
		  nextstep = true;
		}
	      else
		{
		  records[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 ( ! records[recID].used )
	{
          vlistDefVarTimetype(vlistID, records[recID].varID, TIME_CONSTANT);
	}
      else
	{
	  nrecs++;
	}
    }
  streamptr->tsteps[tsID].nrecs = nrecs;

  streamptr->rtsteps = 2;

  streamScanTsFixNtsteps(streamptr, recpos);

  return 0;
}


int srvInqContents(stream_t *streamptr)
{
  streamptr->curTsID = 0;

  srvScanTimestep1(streamptr);

  const 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->exsep;

  int tsID = streamptr->rtsteps;
  taxis_t *taxis = &streamptr->tsteps[tsID].taxis;

  if ( streamptr->tsteps[tsID].recordSize == 0 )
    {
      cdi_create_records(streamptr, tsID);
      record_t *records = streamptr->tsteps[tsID].records;

      nrecs = streamScanInitRecords(streamptr, tsID);

      const 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;
	    }

	  const size_t recsize = (size_t)(fileGetPos(fileID) - recpos);

	  srvInqHeader(srvp, header);

	  const int rcode  = header[0];
	  const int rlevel = header[1];
	  const int vdate  = header[2];
	  const int vtime  = header[3];
	  const int param  = cdiEncodeParam(rcode, 255, 255);

	  // if ( rindex == nrecs ) break; gcc-4.5 internal compiler error
	  if ( rindex == nrecs ) continue;
	  const int recID = streamptr->tsteps[tsID].recIDs[rindex];

	  if ( rindex == 0 )
	    {
	      taxis->type  = TAXIS_ABSOLUTE;
	      taxis->vdate = vdate;
	      taxis->vtime = 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 *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d  levID = %d", streamptr->self, varID, levID);

  void *srvp = streamptr->record->exsep;

  int vlistID  = streamptr->vlistID;
  int fileID   = streamptr->fileID;
  /* NOTE: tiles are not supported here! */
  double missval = vlistInqVarMissval(vlistID, varID);
  size_t gridsize = gridInqSize(vlistInqVarGrid(vlistID, varID));
  int tsid     = streamptr->curTsID;

  off_t currentfilepos = fileGetPos(fileID);

  /* NOTE: tiles are not supported here! */
  int recID = streamptr->vars[varID].recordTable[0].recordID[levID];
  off_t recpos = streamptr->tsteps[tsid].records[recID].position;
  fileSetPos(fileID, recpos, SEEK_SET);
  if ( srvRead(fileID, srvp) < 0 ) abort();
  int header[8];
  srvInqHeader(srvp, header);
  srvInqDataDP(srvp, data);

  fileSetPos(fileID, currentfilepos, SEEK_SET);

  *nmiss = 0;
  for ( size_t i = 0; i < gridsize; i++ )
    if ( DBL_IS_EQUAL(data[i], missval) || DBL_IS_EQUAL(data[i], (float)missval) )
      {
	data[i] = missval;
	(*nmiss)++;
      }
}


void srvReadVarDP(stream_t *streamptr, int varID, double *data, size_t *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  int vlistID = streamptr->vlistID;
  size_t gridsize = 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], nmiss);
}


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;
  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)(zaxisInqLevel(vlistInqVarZaxis(vlistID, varID), levID));
  header[2] = streamptr->tsteps[tsID].taxis.vdate;
  header[3] = streamptr->tsteps[tsID].taxis.vtime;

  size_t xsize = gridInqXsize(gridID);
  size_t 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] = xsize;
  header[5] = ysize;
  header[6] = 0;
  header[7] = 0;

  int datatype = vlistInqVarDatatype(vlistID, varID);

  srvrec_t *srvp = (srvrec_t*) streamptr->record->exsep;
  srvp->dprec = srvDefDatatype(datatype);

  srvDefHeader(srvp, header);
  srvDefDataDP(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;
  size_t gridsize = gridInqSize(vlistInqVarGrid(vlistID, varID));
  size_t nlevs    = (size_t) zaxisInqSize(vlistInqVarZaxis(vlistID, varID));

  for ( size_t levID = 0; levID < nlevs; levID++ )
    srvWriteVarSliceDP(streamptr, varID, (int)levID, &data[levID*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:
 */



static void streamvar_init_recordtable(stream_t *streamptr, int varID, int isub)
{
  streamptr->vars[varID].recordTable[isub].nlevs    = 0;
  streamptr->vars[varID].recordTable[isub].recordID = NULL;
  streamptr->vars[varID].recordTable[isub].lindex   = NULL;
}


static
void streamvar_init_entry(stream_t *streamptr, int varID)
{
  streamptr->vars[varID].ncvarid      = CDI_UNDEFID;
  streamptr->vars[varID].defmiss      = false;

  streamptr->vars[varID].subtypeSize  = 0;
  streamptr->vars[varID].recordTable  = NULL;

  streamptr->vars[varID].gridID       = CDI_UNDEFID;
  streamptr->vars[varID].zaxisID      = CDI_UNDEFID;
  streamptr->vars[varID].tsteptype    = CDI_UNDEFID;
  streamptr->vars[varID].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;

  streamvar_init_entry(streamptr, 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;
    }

  streamptr->vars[varID].recordTable[subID].nlevs    = nlevs;
  streamptr->vars[varID].recordTable[subID].recordID = level;
  streamptr->vars[varID].recordTable[subID].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++;

  streamptr->vars[varID].gridID  = gridID;
  streamptr->vars[varID].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);
  streamptr->vars[varID].recordTable = (sleveltable_t *) Malloc((size_t)nsub * sizeof (sleveltable_t));
  if( streamptr->vars[varID].recordTable == NULL )
    SysError("Allocation of leveltable failed!");
  streamptr->vars[varID].subtypeSize = nsub;

  for (int isub=0; isub<nsub; isub++) {
    streamvar_init_recordtable(streamptr, varID, isub);
    allocate_record_table_entry(streamptr, varID, isub, nlevs);
    if ( CDI_Debug )
      Message("streamptr->vars[varID].recordTable[isub].recordID[0]=%d",
              streamptr->vars[varID].recordTable[isub].recordID[0]);
  }

  streamptr->vars[varID].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



static
size_t grbEncode(int filetype, int memtype, int varID, int levelID, int vlistID, int gridID, int zaxisID,
		 int date, int time, int tsteptype, int numavg,
		 size_t datasize, const void *data, size_t nmiss, void **gribbuffer,
		 int comptype, void *gribContainer)
{
  size_t nbytes = 0;

#ifdef HAVE_LIBCGRIBEX
  if ( filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1 )
    {
      size_t gribbuffersize = datasize*4+3000;
      *gribbuffer = Malloc(gribbuffersize);

      nbytes = cgribexEncode(memtype, varID, levelID, vlistID, gridID, zaxisID,
			     date, time, tsteptype, numavg,
			     datasize, data, nmiss, *gribbuffer, gribbuffersize);
    }
  else
#endif
#ifdef HAVE_LIBGRIB_API
    {
      const void *datap = data;
      if ( memtype == MEMTYPE_FLOAT )
        {
          const float *dataf = (const float*) data;
          double *datad = (double*) Malloc(datasize*sizeof(double));
          for ( size_t i = 0; i < datasize; ++i ) datad[i] = (double) dataf[i];
          datap = (const void*) datad;
        }

      size_t gribbuffersize;
      nbytes = gribapiEncode(varID, levelID, vlistID, gridID, zaxisID,
			     date, time, tsteptype, numavg,
			     (long) datasize, datap, nmiss, gribbuffer, &gribbuffersize,
			     comptype, gribContainer);
      
      if ( memtype == MEMTYPE_FLOAT ) Free((void*)datap);
    }
#else
    {
      Error("ecCodes support not compiled in!");
      (void)gribContainer;
      (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;
}


void grbCopyRecord(stream_t * streamptr2, stream_t * streamptr1)
{
  const int filetype = streamptr1->filetype;
  const int fileID1 = streamptr1->fileID;
  const int fileID2 = streamptr2->fileID;
  const int tsID    = streamptr1->curTsID;
  const int vrecID  = streamptr1->tsteps[tsID].curRecID;
  const int recID   = streamptr1->tsteps[tsID].recIDs[vrecID];
  const off_t recpos  = streamptr1->tsteps[tsID].records[recID].position;
  const size_t recsize = streamptr1->tsteps[tsID].records[recID].size;

  fileSetPos(fileID1, recpos, SEEK_SET);

  // round up recsize to next multiple of 8
  const size_t gribbuffersize = ((recsize + 7U) & ~7U);

  unsigned char *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 !
          void *gh = (void*)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 = NULL;
      gh = grib_handle_new_from_message(NULL, (void *) gribbuffer, recsize);

      const 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 nmiss = 0;

          const int vlistID = streamptr1->vlistID;
          const int varID   = streamptr1->tsteps[tsID].records[recID].varID;
          const int levelID = streamptr1->tsteps[tsID].records[recID].levelID;
          const 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, nmiss);

          free(data);
        }
    }
#endif // HIRLAM_EXTENSIONS
#endif

  if ( filetype == CDI_FILETYPE_GRB )
    {
      size_t unzipsize;
      const int izip = gribGetZip(recsize, gribbuffer, &unzipsize);

      if ( izip == 0 && (streamptr2->comptype == CDI_COMPRESS_SZIP || streamptr2->comptype == CDI_COMPRESS_AEC) )
          nbytes = grbSzip(filetype, gribbuffer, nbytes);
    }

  while ( nbytes & 7 ) gribbuffer[nbytes++] = 0;

  const size_t nwrite = fileWrite(fileID2, gribbuffer, nbytes);
  if ( nwrite != nbytes )
    {
      perror(__func__);
      Error("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 nmiss)
{
  void *gribbuffer = NULL;
  void *gc = NULL;

  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 comptype  = streamptr->comptype;
  int tsID      = streamptr->curTsID;
  int date      = streamptr->tsteps[tsID].taxis.vdate;
  int time      = streamptr->tsteps[tsID].taxis.vtime;
  int numavg    = (tsteptype == TSTEP_AVG) ? streamptr->tsteps[tsID].taxis.numavg : 0;

  if ( CDI_Debug )
    Message("gridID = %d zaxisID = %d", gridID, zaxisID);

  size_t datasize = gridInqSize(gridID);

#ifdef HAVE_LIBCGRIBEX
  if ( filetype == CDI_FILETYPE_GRB && !CDI_gribapi_grib1 )
    {
    }
  else
#endif
    {
#ifdef GRIBCONTAINER2D
      gribContainer_t **gribContainers =  (gribContainer_t **) streamptr->gribContainers;
      gc = (void *) &gribContainers[varID][levelID];
#else
      gribContainer_t *gribContainers =  (gribContainer_t *) streamptr->gribContainers;
      gc = (void *) &gribContainers[varID];
#endif
    }

  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 int ljpeg_warn = 1;
      if ( ljpeg_warn ) Warning("JPEG compression of GRIB1 records not available!");
      ljpeg_warn = 0;
    }

  size_t nbytes = grbEncode(filetype, memtype, varID, levelID, vlistID, gridID, zaxisID, date, time, tsteptype, numavg,
                            datasize, data, nmiss, &gribbuffer, comptype, gc);

  if ( filetype == CDI_FILETYPE_GRB && (streamptr->comptype == CDI_COMPRESS_SZIP || streamptr->comptype == CDI_COMPRESS_AEC) )
    nbytes = grbSzip(filetype, gribbuffer, nbytes);

  size_t (*myFileWrite)(int fileID, const void *restrict buffer, size_t len, int tsID)
    = (size_t (*)(int, const void *restrict, size_t, int))
    namespaceSwitchGet(NSSWITCH_FILE_WRITE).func;

  const size_t nwrite = myFileWrite(fileID, gribbuffer, nbytes, tsID);
  if ( nwrite != nbytes )
    {
      perror(__func__);
      Error("Failed to write GRIB slice!");
    }

  if ( gribbuffer ) Free(gribbuffer);
}


void grb_write_var(stream_t *streamptr, int varID, int memtype, const void *data, size_t nmiss)
{
  int vlistID  = streamptr->vlistID,
    gridID   = vlistInqVarGrid(vlistID, varID),
    zaxisID  = vlistInqVarZaxis(vlistID, varID),
    nlevs    = zaxisInqSize(zaxisID);
  size_t gridsize = 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)+levelID*gridsize;
        
        size_t nmiss_slice = 0;
        if ( nmiss )
          for ( size_t i = 0; i < chunkLen; ++i )
            nmiss_slice += DBL_IS_EQUAL(fdata[i], missval);

        grb_write_var_slice(streamptr, varID, levelID, memtype, fdata, nmiss_slice);
      }
  else
    for ( int levelID = 0; levelID < nlevs; levelID++ )
      {
        const double *restrict ddata = ((const double *)data)+levelID*gridsize;
        
        size_t nmiss_slice = 0;
        if ( nmiss )
          for ( size_t i = 0; i < chunkLen; ++i )
            nmiss_slice += DBL_IS_EQUAL(ddata[i], missval);

        grb_write_var_slice(streamptr, varID, levelID, memtype, ddata, nmiss_slice);
      }
}


void grb_write_record(stream_t *streamptr, int memtype, const void *data, size_t nmiss)
{
  int varID   = streamptr->record->varID;
  int levelID = streamptr->record->levelID;

  grb_write_var_slice(streamptr, varID, levelID, memtype, data, nmiss);
}


#endif
#ifdef HAVE_CONFIG_H
#endif

#ifdef HAVE_LIBGRIB



static
int grbDecode(int filetype, int memtype, void *cgribexp, void *gribbuffer, size_t gribsize, void *data, size_t datasize,
	      int unreduced, size_t *nmiss, 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, nmiss, missval);
    }
  else
#endif
#ifdef HAVE_LIBGRIB_API
    {
      void *datap = (memtype == MEMTYPE_FLOAT) ? Malloc(datasize*sizeof(double)) : data;

      status = gribapiDecode(gribbuffer, gribsize, datap, datasize, unreduced, nmiss, missval);

      if ( memtype == MEMTYPE_FLOAT )
        {
          float *dataf = (float*) data;
          double *datad = (double*) datap;
          for ( size_t i = 0; i < datasize; ++i ) dataf[i] = (float) datad[i];
          Free(datap);
        }
    }
#else
    {
      Error("ecCodes support not compiled in!");
    }
#endif

  return status;
}

// Decompresses the grib data in gribbuffer.
static
int grbUnzipRecord(void *gribbuffer, size_t *gribsize)
{
  int zip = 0;

  const 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(gribbuffer, (long)unzipsize, 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 DecodeArgs {
  int recID, *outZip, filetype, memtype, unreduced;
  void *cgribexp, *gribbuffer, *data;
  size_t recsize, gridsize, nmiss;
  double missval;
} DecodeArgs;


static
int grb_decode_record(void *untypedArgs)
{
  DecodeArgs *args = untypedArgs;
  *args->outZip = grbUnzipRecord(args->gribbuffer, &args->recsize);
  grbDecode(args->filetype, args->memtype, args->cgribexp, args->gribbuffer, args->recsize, args->data, args->gridsize,
            args->unreduced, &args->nmiss, args->missval);
  return 0;
}

static
DecodeArgs grb_read_raw_data(stream_t *streamptr, int recID, int memtype, void *gribbuffer, void *data, bool resetFilePos)
{
  const int vlistID = streamptr->vlistID;
  const int fileID = streamptr->fileID;
  const int tsID = streamptr->curTsID;	// FIXME: This should be looked up from the given recID
  const off_t recpos = streamptr->tsteps[tsID].records[recID].position;
  const size_t recsize = streamptr->tsteps[tsID].records[recID].size;
  const int varID = streamptr->tsteps[tsID].records[recID].varID;

  const int gridID = vlistInqVarGrid(vlistID, varID);
  const size_t gridsize = gridInqSize(gridID);
  if ( CDI_Debug ) Message("gridID = %d gridsize = %zu", gridID, gridsize);

  if ( recsize == 0 ) Error("Internal problem! Recordsize is zero for record %d at timestep %d", recID+1, tsID+1);

  void *cgribexp = (gribbuffer && streamptr->record->cgribexp) ? streamptr->record->cgribexp : NULL;
  if (!gribbuffer) gribbuffer = Malloc(streamptr->record->buffersize);
  if (!data) data = Malloc(gridsize*(memtype == MEMTYPE_FLOAT ? sizeof(float) : sizeof(double)));

  if (resetFilePos)
    {
      const off_t currentfilepos = fileGetPos(fileID);
      fileSetPos(fileID, recpos, SEEK_SET);
      if (fileRead(fileID, gribbuffer, recsize) != recsize) Error("Failed to read GRIB record");
      fileSetPos(fileID, currentfilepos, SEEK_SET);
    }
  else
    {
      fileSetPos(fileID, recpos, SEEK_SET);
      if (fileRead(fileID, gribbuffer, recsize) != recsize) Error("Failed to read GRIB record");
      streamptr->numvals += gridsize;
    }

  return (DecodeArgs){
    .recID = recID,
    .outZip = &streamptr->tsteps[tsID].records[recID].zip,
    .filetype = streamptr->filetype,
    .memtype = memtype,
    .cgribexp = cgribexp,
    .gribbuffer = gribbuffer,
    .recsize = recsize,
    .data = data,
    .gridsize = gridsize,
    .unreduced = streamptr->unreduced,
    .nmiss = 0,
    .missval = vlistInqVarMissval(vlistID, varID),
  };
}

typedef struct JobDescriptor {
  DecodeArgs args;
  AsyncJob *job;
} JobDescriptor;

static
void JobDescriptor_startJob(AsyncManager *jobManager, JobDescriptor *me, stream_t *streamptr, int recID, int memtype, bool resetFilePos)
{
  me->args = grb_read_raw_data(streamptr, recID, memtype, NULL, NULL, resetFilePos);
  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 JobDescriptor_finishJob(AsyncManager *jobManager, JobDescriptor *me, void *data, size_t *nmiss)
{
  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)));
  *nmiss = me->args.nmiss;

  Free(me->args.gribbuffer);
  Free(me->args.data);
  me->args.recID = -1;	// mark as inactive
}

static
void grb_read_next_record(stream_t *streamptr, int recID, int memtype, void *data, size_t *nmiss, bool resetFilePos)
{
  bool jobFound = false;

  int workerCount = streamptr->numWorker;
  if (workerCount > 0)
    {
      if (workerCount > streamptr->tsteps[0].nrecs) workerCount = streamptr->tsteps[0].nrecs;

      AsyncManager *jobManager = (AsyncManager *) streamptr->jobManager;
      JobDescriptor *jobs = (JobDescriptor *) streamptr->jobs;

      // if this is the first call, init and start worker threads
      tsteps_t *timestep = &streamptr->tsteps[streamptr->curTsID];

      if (!jobs)
        {
          jobs = malloc(workerCount*sizeof*jobs);
          streamptr->jobs = jobs;
          for (int i = 0; i < workerCount; i++) jobs[i].args.recID = -1;
          if (AsyncWorker_init(&jobManager, workerCount)) xabort("error while trying to start worker threads");
          streamptr->jobManager = jobManager;
        }

      if (recID == 0) streamptr->nextRecID = 0;
      if (recID == 0) streamptr->cachedTsID = streamptr->curTsID; // no active workers -> we may start processing records of a new timestep

      if (streamptr->cachedTsID == streamptr->curTsID)
        {
          // Start as many new jobs as possible.
          for (int i = 0; streamptr->nextRecID < timestep->nrecs && i < workerCount; i++)
            {
              JobDescriptor *jd = &jobs[i];
              if (jd->args.recID < 0)
                {
                  JobDescriptor_startJob(jobManager, jd, streamptr, timestep->recIDs[streamptr->nextRecID++], memtype, resetFilePos);
                }
            }

          // search for a job descriptor with the given recID, and use its results if it exists
          for (int i = 0; !jobFound && i < workerCount; i++)
            {
              JobDescriptor *jd = &jobs[i];
              if (jd->args.recID == recID)
                {
                  jobFound = true;
                  JobDescriptor_finishJob(jobManager, jd, data, nmiss);
                  if (streamptr->nextRecID < timestep->nrecs)
                    {
                      JobDescriptor_startJob(jobManager, jd, streamptr, timestep->recIDs[streamptr->nextRecID++], memtype, resetFilePos);
                    }
                }
            }
        }
    }

  // perform the work synchronously if we didn't start a job for it yet
  if (!jobFound)
    {
      DecodeArgs args = grb_read_raw_data(streamptr, recID, memtype, streamptr->record->buffer, data, resetFilePos);
      grb_decode_record(&args);
      *nmiss = args.nmiss;
    }
}


void grb_read_record(stream_t *streamptr, int memtype, void *data, size_t *nmiss)
{
  const int tsID = streamptr->curTsID;
  const int vrecID = streamptr->tsteps[tsID].curRecID;
  const int recID = streamptr->tsteps[tsID].recIDs[vrecID];

  grb_read_next_record(streamptr, recID, memtype, data, nmiss, false);
}


void grb_read_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, void *data, size_t *nmiss)
{
  const int isub = subtypeInqActiveIndex(streamptr->vars[varID].subtypeID);
  const int recID = streamptr->vars[varID].recordTable[isub].recordID[levelID];

  grb_read_next_record(streamptr, recID, memtype, data, nmiss, true);
}


void grb_read_var(stream_t *streamptr, int varID, int memtype, void *data, size_t *nmiss)
{
  const int vlistID = streamptr->vlistID;
  const int fileID = streamptr->fileID;

  const int gridID = vlistInqVarGrid(vlistID, varID);
  const size_t gridsize = gridInqSize(gridID);

  const off_t currentfilepos = fileGetPos(fileID);

  const int isub = subtypeInqActiveIndex(streamptr->vars[varID].subtypeID);
  const int nlevs = streamptr->vars[varID].recordTable[0].nlevs;

  if ( CDI_Debug ) Message("nlevs = %d gridID = %d gridsize = %zu", nlevs, gridID, gridsize);

  *nmiss = 0;
  for (int levelID = 0; levelID < nlevs; levelID++)
    {
      const int recID = streamptr->vars[varID].recordTable[isub].recordID[levelID];

      void *datap = NULL;
      if ( memtype == MEMTYPE_FLOAT )
        datap = (float*)data + levelID*gridsize;
      else
        datap = (double*)data + levelID*gridsize;

      size_t imiss;
      grb_read_next_record(streamptr, recID, memtype, datap, &imiss, false);
      *nmiss += imiss;
    }

  fileSetPos(fileID, currentfilepos, SEEK_SET);
}

#endif
#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

#ifdef HAVE_LIBNETCDF



void cdfDefVarDeflate(int ncid, int ncvarID, int deflate_level)
{
#ifdef HAVE_NETCDF4
  // Set chunking, shuffle, and deflate.
  const int shuffle = 1, deflate = 1;

  if (deflate_level < 1 || deflate_level > 9) deflate_level = 1;

  int status;
  if ((status = nc_def_var_deflate(ncid, ncvarID, shuffle, deflate, deflate_level)))
    {
      Error("nc_def_var_deflate failed; %s", nc_strerror(status));
    }
#else
  static bool lwarn = true;
  if (lwarn)
    {
      lwarn = false;
      Warning("Deflate compression failed, NetCDF4 not available!");
    }
#endif
}

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
  static bool lwarn = true;
  if (lwarn)
    {
      lwarn = false;
      Warning("Szip compression failed, NetCDF4/szlib not available!");
    }
#endif
}

#ifdef HAVE_NETCDF4
static
nc_type cdfTypeComplexFloat(stream_t *streamptr)
{
  if (streamptr->nc_complex_float_id == CDI_UNDEFID)
    {
      typedef struct complex_float { float r, i; } complex_float;
      const 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));
      streamptr->nc_complex_float_id = nc_complex_id;
    }

  return (nc_type) streamptr->nc_complex_float_id;
}

static
nc_type cdfTypeComplexDouble(stream_t *streamptr)
{
  if (streamptr->nc_complex_double_id == CDI_UNDEFID)
    {
      typedef struct complex_double { double r, i; } complex_double;
      const 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));
      streamptr->nc_complex_double_id = nc_complex_id;
    }

  return (nc_type) streamptr->nc_complex_double_id;
}
#endif

nc_type cdfDefDatatype(int datatype, stream_t *streamptr)
{
  nc_type xtype = NC_FLOAT;

  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;
#ifdef  HAVE_NETCDF4
      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
      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_CPX32 || datatype == CDI_DATATYPE_CPX64 )
        Error("CDI library does not support complex numbers with NetCDF4 classic!");
#endif
      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!");
    }

  return xtype;
}

static
void cdfDefVarMissval(stream_t *streamptr, int varID, int dtype, int lcheck)
{
  if ( streamptr->vars[varID].defmiss == false )
    {
      const int vlistID = streamptr->vlistID;
      const int fileID  = streamptr->fileID;
      const int ncvarID = streamptr->vars[varID].ncvarid;
      const 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->vars[varID].defmiss = true;
    }
}

static
void cdfDefInstituteGlobal(const stream_t *streamptr)
{
  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;
  const int instID  = vlistInqInstitut(vlistID);

  if ( instID != CDI_UNDEFID )
    {
      const char *longname = institutInqLongnamePtr(instID);
      if ( longname )
	{
	  const 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);
	    }
	}
    }
}

static
void cdfDefSourceGlobal(const stream_t *streamptr)
{
  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;
  const 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);
	    }
	}
    }
}

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->has_bounds) return;

  int time_varid = streamptr->basetime.ncvarid;
  char timeVarName[CDI_MAX_NAME];
  cdf_inq_varname(fileID, time_varid, timeVarName);

  int stepType = vlistInqVarTsteptype(cdiID, varID);

  const char *cellMethod = NULL;
  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";

  if (cellMethod)
    {
      const char *attname = "cell_methods";
      char atttxt[CDI_MAX_NAME+10];
      sprintf(atttxt, "%s: %s", timeVarName, cellMethod);
      cdf_put_att_text(fileID, ncvarID, attname, strlen(atttxt), atttxt);
    }
}

void cdfDefineAttributes(int cdiID, int varID, int fileID, int ncvarID)
{
  int atttype, attlen;
  size_t len;
  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);
          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 )
        {
          size_t attSize = (size_t)attlen*sizeof(int);
          int *attint = (int *)resizeBuf(&attBuf, &attBufSize, attSize);
          cdiInqAttInt(cdiID, varID, attname, attlen, &attint[0]);
          len = (size_t)attlen;
          nc_type xtype = (atttype == CDI_DATATYPE_INT8)   ? NC_BYTE :
                          (atttype == CDI_DATATYPE_INT16)  ? NC_SHORT :
#ifdef  HAVE_NETCDF4
                          (atttype == CDI_DATATYPE_UINT8)  ? NC_UBYTE :
                          (atttype == CDI_DATATYPE_UINT16) ? NC_USHORT :
                          (atttype == CDI_DATATYPE_UINT32) ? NC_UINT :
#endif
                          NC_INT;
          cdf_put_att_int(fileID, ncvarID, attname, xtype, len, attint);
        }
      else if ( atttype == CDI_DATATYPE_FLT32 || atttype == CDI_DATATYPE_FLT64 )
        {
          size_t attSize = (size_t)attlen * sizeof(double);
          double *attflt = (double *)resizeBuf(&attBuf, &attBufSize, attSize);
          cdiInqAttFlt(cdiID, varID, attname, attlen, attflt);
          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);
        }
    }

  Free(attBuf);
}

static
void cdfDefineInstituteName(int vlistID, int varID, int fileID, int ncvarID)
{
  const 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;

  const int vlistID = streamptr->vlistID;
  const 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(vlistID, CDI_GLOBAL, fileID, NC_GLOBAL);

  if ( natts > 0 && streamptr->ncmode == 2 ) cdf_enddef(fileID);

  streamptr->globalatts = 1;
}

static
void cdf_get_gmapvarname(int gridID, char *gmapvarname)
{
  int pgridID = gridID;
  char gmapname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(pgridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);

  if ( !gmapname[0] )
    {
      int projID = gridInqProj(gridID);
      if ( projID != CDI_UNDEFID )
        {
          pgridID = projID;
          length = CDI_MAX_NAME;
          cdiInqKeyString(pgridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_NAME, gmapname, &length);
        }
    }

  if ( gmapname[0] )
    {
      length = CDI_MAX_NAME;
      cdiInqKeyString(pgridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname, &length);
    }
}

static
int nc_grid_index(stream_t *streamptr, int gridID)
{
  int index = 0;
  const int vlistID = streamptr->vlistID;
  const int ngrids = vlistNgrids(vlistID);
  for ( index = 0; index < ngrids; ++index )
    if ( streamptr->ncgrid[index].gridID == gridID ) break;

  assert(index < ngrids);

  return index;
}

static
int xtype2ppb(nc_type xtype)
{
  int ppb = 32;

  if      (xtype == NC_BYTE)   ppb = 8;
  else if (xtype == NC_SHORT)  ppb = 16;
#ifdef  HAVE_NETCDF4
  else if (xtype == NC_UBYTE)  ppb = 8;
  else if (xtype == NC_USHORT) ppb = 16;
#endif

  return ppb;
}

static
void cdfDefVarCompression(const stream_t *streamptr, int ncvarID, bool lchunk, nc_type xtype)
{
  if ( streamptr->comptype == CDI_COMPRESS_ZIP )
    {
      if ( lchunk && (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C) )
        {
          cdfDefVarDeflate(streamptr->fileID, ncvarID, streamptr->complevel);
        }
      else
        {
          if ( lchunk )
            {
              static bool lwarn = true;
              if ( lwarn )
                {
                  lwarn = false;
                  Warning("Deflate compression is only available for NetCDF4!");
                }
            }
        }
    }
  else if ( streamptr->comptype == CDI_COMPRESS_SZIP )
    {
      if ( lchunk && (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C) )
        {
          cdfDefVarSzip(streamptr->fileID, ncvarID, xtype2ppb(xtype));
        }
      else
        {
          if ( lchunk )
            {
              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 )
    {
      const double addoffset   = vlistInqVarAddoffset(vlistID, varID);
      const double scalefactor = vlistInqVarScalefactor(vlistID, varID);
      const bool laddoffset   = IS_NOT_EQUAL(addoffset, 0);
      const bool lscalefactor = IS_NOT_EQUAL(scalefactor, 1);

      if ( laddoffset || lscalefactor )
        {
          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)
{
  const int fileID  = streamptr->fileID;

  if ( gridtype != GRID_GENERIC && gridtype != GRID_LONLAT &&
       gridtype != GRID_PROJECTION && gridtype != GRID_CURVILINEAR && gridtype != GRID_CHARXY )
    {
      const 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]; gmapvarname[0] = 0;
  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 )
    {
      const int numLPE = gridInqNP(gridID);
      if (numLPE > 0)
        cdf_put_att_int(fileID, ncvarID, "CDI_grid_num_LPE", NC_INT, 1, &numLPE);
    }

  if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      const int ncyvarID = streamptr->ncgrid[gridindex].ncIDs[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);
        }

      const int ncrpvarID = streamptr->ncgrid[gridindex].ncIDs[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 )
    {
      const int ncxvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_X];
      const int ncyvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_Y];
      cdfAppendCoordinates(fileID, ncyvarID, coordinates);
      cdfAppendCoordinates(fileID, ncxvarID, coordinates);
    }
  else if ( gridtype == GRID_GAUSSIAN_REDUCED )
    {
      /*
      const int ncxvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_X];
      const int ncyvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_Y];
      cdfAppendCoordinates(fileID, ncyvarID, coordinates);
      cdfAppendCoordinates(fileID, ncxvarID, coordinates);
      */
    }
  else if ( gridtype == GRID_UNSTRUCTURED || gridtype == GRID_CURVILINEAR )
    {
      const int ncxvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_X];
      const int ncyvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_Y];
      const int ncavarID = streamptr->ncgrid[gridindex].ncIDs[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) )
        {
          const int ncxvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_X];
          cdfAppendCoordinates(fileID, ncxvarID, coordinates);
        }
      else if ( gridInqYIsc(gridID) )
        {
          const int ncyvarID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_Y];
          cdfAppendCoordinates(fileID, ncyvarID, coordinates);
        }
    }

  const size_t len = strlen(coordinates);
  if ( len ) cdf_put_att_text(fileID, ncvarID, "coordinates", len, coordinates);
}

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 lchunk, size_t chunks[4], char axis[5], size_t *piax)
{
  const int fileID  = streamptr->fileID;
  const 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;
  if ( xid != CDI_UNDEFID ) cdf_inq_dimlen(fileID, xid, &xsize);
  if ( yid != CDI_UNDEFID ) cdf_inq_dimlen(fileID, yid, &ysize);

  const int timetype = vlistInqVarTimetype(vlistID, varID);
  if ( vlistHasTime(vlistID) && timetype != TIME_CONSTANT )
    {
      const int tid = streamptr->basetime.ncdimid;
      if (tid == CDI_UNDEFID) Error("Internal problem, time undefined!");
      axis[iax++] = 'T';
      chunks[ndims] = 1;
      dims[ndims] = tid;
      ndims++;
    }

  int chunktype = vlistInqVarChunkType(vlistID, varID);
  const size_t chunk_size_max = 65536;
  const size_t chunk_size_lim = 1073741823;
  if ( chunktype != CDI_CHUNK_LINES && gridsize > chunk_size_lim )
    {
      if ( CDI_Debug ) fprintf(stderr, "gridsize > %zu, changed chunktype to CDI_CHUNK_LINES!\n", chunk_size_lim);
      chunktype = CDI_CHUNK_LINES;
    }

  for ( int id = 0; id < 3; ++id )
    {
      if ( dimorder[id] == 3 && zid != CDI_UNDEFID )
        {
          axis[iax++] = 'Z';
          chunks[ndims] = 1;
          dims[ndims] = zid;
          ndims++;
        }
      else if ( dimorder[id] == 2 && yid != CDI_UNDEFID )
        {
          if ( chunktype == CDI_CHUNK_AUTO )
            chunks[ndims] = (chunk_size_max > gridsize) ? ysize : chunk_size_max/xsize;
          else
            chunks[ndims] = (chunktype == CDI_CHUNK_LINES) ? 1 : ysize;
          dims[ndims] = yid;
          ndims++;
        }
      else if ( dimorder[id] == 1 && xid != CDI_UNDEFID )
        {
          if ( chunktype == CDI_CHUNK_AUTO && yid == CDI_UNDEFID )
            chunks[ndims] = (chunk_size_max > xsize) ? xsize : chunk_size_max;
          else
            chunks[ndims] = (xsize > chunk_size_lim) ? chunk_size_lim : xsize;
          dims[ndims] = xid;
          ndims++;
        }
    }

  if ( CDI_Debug )
    fprintf(stderr, "lchunk %d chunktype %d  chunks %zu %zu %zu %zu\n", lchunk, chunktype, chunks[0], chunks[1], chunks[2], chunks[3]);

  *piax = iax;
  return ndims;
}

static
void cdfDefineAttrLeveltype(int fileID, int ncvarID, int zaxisID, int zaxistype)
{
  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);
    }
}

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];
      sprintf(varname, "%s", name);
      char *varname2 = varname+strlen(varname);
      unsigned iz = 0;

      do
        {
          if ( iz ) sprintf(varname2, "_%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 ( strcmp(name, varname) != 0 )
        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 )
    sprintf(varname, "var%d", code);
  else
    sprintf(varname, "param%d.%d.%d", pnum, pcat, *pdis);

  char *varname2 = varname+strlen(varname);
  int ncvarID;
  unsigned iz = 0;

  do
    {
      if ( iz ) sprintf(varname2, "_%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
int cdfDefVar(stream_t *streamptr, int varID)
{
  if (streamptr->vars[varID].ncvarid != CDI_UNDEFID)
    return streamptr->vars[varID].ncvarid;

  const int fileID  = streamptr->fileID;
  if (CDI_Debug) Message("streamID = %d, fileID = %d, varID = %d", streamptr->self, fileID, varID);

  const int vlistID = streamptr->vlistID;
  const int param = vlistInqVarParam(vlistID, varID);
  int code = vlistInqVarCode(vlistID, varID);
  int pnum, pcat, pdis;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);

  const int gridID = vlistInqVarGrid(vlistID, varID);
  const size_t gridsize = gridInqSize(gridID);
  const int gridtype = gridInqType(gridID);
  const int gridindex = nc_grid_index(streamptr, gridID);
  const int xid = (gridtype != GRID_TRAJECTORY) ? streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X] : CDI_UNDEFID;
  const int yid = (gridtype != GRID_TRAJECTORY && gridtype != GRID_GAUSSIAN_REDUCED) ?
    streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_Y] : CDI_UNDEFID;

  const int zaxisID = vlistInqVarZaxis(vlistID, varID);
  const int zaxistype = zaxisInqType(zaxisID);
  const int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  const int zid = streamptr->zaxisID[zaxisindex];

  int dimorder[3]; // ZYX and ZXY
  vlistInqVarDimorder(vlistID, varID, &dimorder);
  const bool lchunk = (dimorder[0] == 3) ? (gridsize >= 32) : false;

  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];
  int ndims = cdfDefineDimsAndChunks(streamptr, varID, xid, yid, zid, gridsize, dimorder, dims, lchunk, chunks, axis, &iax);

  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);

  const 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);

  const int dtype = vlistInqVarDatatype(vlistID, varID);
  const nc_type xtype = cdfDefDatatype(dtype, streamptr);

  int ncvarID = -1;
  cdf_def_var(fileID, name, xtype, ndims, dims, &ncvarID);

#ifdef  HAVE_NETCDF4
  if (lchunk && (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C))
    cdf_def_var_chunking(fileID, ncvarID, NC_CHUNKED, chunks);
#endif

  cdfDefVarCompression(streamptr, ncvarID, lchunk, xtype);

  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);
    }

  const bool zaxisIsScalar = (zid == CDI_UNDEFID) ? (zaxisInqScalar(zaxisID) > 0) : false;
  const int nczvarID = (zaxisIsScalar || zaxistype == ZAXIS_CHAR) ? streamptr->nczvarID[zaxisindex] : CDI_UNDEFID;

  cdfDefineCoordinates(streamptr, ncvarID, nczvarID, gridtype, gridID, gridindex, xid, yid, gridsize, axis, iax);

  cdfDefVarPacking(streamptr, ncvarID, xtype, vlistID, varID);

  if ( dtype == CDI_DATATYPE_UINT8 && xtype == NC_BYTE )
    {
      const 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(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 )
    {
      const int fileID = streamptr->fileID;
      if ( streamptr->ncmode == 2 ) cdf_redef(fileID);

      const 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);
          else
            cdf__enddef(fileID, CDI_Netcdf_Hdr_Pad);
        }

      streamptr->accessmode = 1;
    }
}

static
void cdfWriteGridTraj(stream_t *streamptr, int gridID)
{
  const int gridindex = nc_grid_index(streamptr, gridID);
  const int lonID = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
  const int latID = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_Y];
  const size_t index = (size_t)streamptr->curTsID;

  const double xlon = gridInqXval(gridID, 0);
  const 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 nmiss)
{
  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 ) */
    {
      const double missval      = vlistInqVarMissval(vlistID, varID);
      const double addoffset    = vlistInqVarAddoffset(vlistID, varID);
      const double scalefactor  = vlistInqVarScalefactor(vlistID, varID);
      const bool laddoffset     = IS_NOT_EQUAL(addoffset, 0);
      const bool lscalefactor   = IS_NOT_EQUAL(scalefactor, 1);

      if ( laddoffset || lscalefactor )
        {
          if ( memtype == MEMTYPE_FLOAT )
            {
              mdata_sp = (float *) Malloc(nvals*sizeof(float));
              memcpy(mdata_sp, pdata_sp, nvals*sizeof(float));
              pdata_sp = mdata_sp;

              if ( nmiss > 0 )
                {
                  for ( size_t i = 0; i < nvals; i++ )
                    {
                      double temp = mdata_sp[i];
                      if ( !DBL_IS_EQUAL(temp, missval) )
                        {
                          if ( laddoffset )   temp -= addoffset;
                          if ( lscalefactor ) temp /= scalefactor;
                          mdata_sp[i] = (float)temp;
                        }
                    }
                }
              else
                {
                  for ( size_t i = 0; i < nvals; i++ )
                    {
                      double temp = mdata_sp[i];
                      if ( laddoffset )   temp -= addoffset;
                      if ( lscalefactor ) 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 ( nmiss > 0 )
                {
                  for ( size_t i = 0; i < nvals; i++ )
                    {
                      if ( !DBL_IS_EQUAL(mdata_dp[i], missval) )
                        {
                          if ( laddoffset )   mdata_dp[i] -= addoffset;
                          if ( lscalefactor ) mdata_dp[i] /= scalefactor;
                        }
                    }
                }
              else
                {
                  for ( size_t i = 0; i < nvals; i++ )
                    {
                      if ( laddoffset )   mdata_dp[i] -= addoffset;
                      if ( lscalefactor ) mdata_dp[i] /= scalefactor;
                    }
                }
            }
        }

      if ( dtype == CDI_DATATYPE_UINT8 || dtype == CDI_DATATYPE_INT8 ||
           dtype == CDI_DATATYPE_INT16 || 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 && memtype != MEMTYPE_FLOAT )
        {
          double fmin =  1.0e200;
          double fmax = -1.0e200;
          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, nmiss = %d, missval = %g, minval = %g, maxval = %g",
                  nvals, nmiss, 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;

  const int gridtype = gridInqType(gridID);
  if (gridtype == GRID_TRAJECTORY)
    {
      cdfWriteGridTraj(streamptr, gridID);
    }
  else
    {
      const int gridindex = nc_grid_index(streamptr, gridID);
      *xid = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
      if (gridtype != GRID_GAUSSIAN_REDUCED)
        *yid = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_Y];
    }

  const int vlistID = streamptr->vlistID;
  const int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  *zid = streamptr->zaxisID[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;

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const long ntsteps = streamptr->ntsteps;
  if ( CDI_Debug ) Message("ntsteps = %ld", ntsteps);

  const 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 )
    {
      const 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 nmiss)
{
  if ( streamptr->accessmode == 0 ) cdfEndDef(streamptr);

  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const int ncvarID = cdfDefVar(streamptr, varID);

  const int gridID   = vlistInqVarGrid(vlistID, varID);
  const 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->ncmode = 2;
    }

  const int dtype = vlistInqVarDatatype(vlistID, varID);

  if ( nmiss > 0 ) cdfDefVarMissval(streamptr, varID, dtype, 1);

  const size_t nvals = 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, nmiss);
}

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;

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const long ntsteps = streamptr->ntsteps;
  if ( CDI_Debug ) Message("ntsteps = %ld", ntsteps);

  const 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 )
    {
      const int zaxisID  = vlistInqVarZaxis(vlistID, varID);
      int size = zaxisInqSize(zaxisID);
      xassert(rect[2][0] >= 0 && rect[2][0] <= rect[2][1] && rect[2][1] <= size);
      start[ndims] = (size_t)rect[2][0];
      count[ndims] = (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] && (size_t)rect[1][1] <= size);
      start[ndims] = (size_t)rect[1][0];
      count[ndims] = (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] && (size_t)rect[0][1] <= size);
      start[ndims] = (size_t)rect[0][0];
      count[ndims] = (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 nmiss)
{
  if ( streamptr->accessmode == 0 ) cdfEndDef(streamptr);

  const int streamID = streamptr->self;

  if ( CDI_Debug )
    Message("streamID = %d  varID = %d", streamID, varID);

  const int vlistID = streamInqVlist(streamID);
  const int fileID  = streamInqFileID(streamID);

  const int ncvarID = cdfDefVar(streamptr, varID);

  const int gridID   = vlistInqVarGrid(vlistID, varID);
  const 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->ncmode = 2;
    }

  const int dtype = vlistInqVarDatatype(vlistID, varID);

  if ( nmiss > 0 ) cdfDefVarMissval(streamptr, varID, dtype, 1);

  const size_t nvals = 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, nmiss);
}

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;

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const long ntsteps = streamptr->ntsteps;
  if ( CDI_Debug ) Message("ntsteps = %ld", ntsteps);

  const 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 nmiss)
{
  if ( streamptr->accessmode == 0 ) cdfEndDef(streamptr);

  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const int ncvarID = cdfDefVar(streamptr, varID);

  const int gridID   = vlistInqVarGrid(vlistID, varID);
  const int zaxisID  = vlistInqVarZaxis(vlistID, varID);

  int xid, yid, zid;
  cdfGetXYZid(streamptr, gridID, zaxisID, &xid, &yid, &zid);

  int dimorder[3];
  vlistInqVarDimorder(vlistID, varID, &dimorder);
  const 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);

  const int dtype = vlistInqVarDatatype(vlistID, varID);

  if ( nmiss > 0 ) cdfDefVarMissval(streamptr, varID, dtype, 1);

  const size_t nvals = gridInqSize(gridID);

  cdf_write_var_data(fileID, vlistID, varID, ncvarID, dtype, nvals, xsize, ysize, swapxy, start, count, memtype, data, nmiss);
}


void cdf_write_record(stream_t *streamptr, int memtype, const void *data, size_t nmiss)
{
  const int varID   = streamptr->record->varID;
  const int levelID = streamptr->record->levelID;
  cdf_write_var_slice(streamptr, varID, levelID, memtype, data, nmiss);
}

#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 <limits.h>
#include <float.h>



static
void cdfReadGridTraj(stream_t *streamptr, int gridID)
{
  const int vlistID = streamptr->vlistID;
  const int fileID  = streamptr->fileID;

  const int gridindex = vlistGridIndex(vlistID, gridID);
  const int lonID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_X];
  const int latID = streamptr->ncgrid[gridindex].ncIDs[CDF_VARID_Y];

  const int tsID = streamptr->curTsID;
  const size_t index = (size_t)tsID;

  double xlon, xlat;
  cdf_get_var1_double(fileID, lonID, &index, &xlon);
  cdf_get_var1_double(fileID, latID, &index, &xlat);

  gridDefXvals(gridID, &xlon);
  gridDefYvals(gridID, &xlat);
}

static
void cdfGetSlapDescription(stream_t *streamptr, int varID, size_t (*start)[4], size_t (*count)[4])
{
  const int vlistID = streamptr->vlistID;
  const int tsID = streamptr->curTsID;
  const int gridID = vlistInqVarGrid(vlistID, varID);
  const int zaxisID = vlistInqVarZaxis(vlistID, varID);
  const int timetype = vlistInqVarTimetype(vlistID, varID);
  const int gridindex = vlistGridIndex(vlistID, gridID);

  if ( CDI_Debug ) Message("tsID = %d", tsID);

  int xid = CDI_UNDEFID, yid = CDI_UNDEFID;
  if ( gridInqType(gridID) == GRID_TRAJECTORY )
    {
      cdfReadGridTraj(streamptr, gridID);
    }
  else
    {
      xid = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
      yid = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_Y];
    }
  const int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  const int zid = streamptr->zaxisID[zaxisindex];

  int ndims = 0;
#define addDimension(startCoord, length) do \
    { \
      (*start)[ndims] = startCoord; \
      (*count)[ndims] = length; \
      ndims++; \
    } while(0)
  if ( timetype != TIME_CONSTANT ) addDimension((size_t)tsID, 1);
  if ( zid != CDI_UNDEFID ) addDimension(0, (size_t)zaxisInqSize(zaxisID));
  if ( yid != CDI_UNDEFID ) addDimension(0, gridInqYsize(gridID));
  if ( xid != CDI_UNDEFID ) addDimension(0, 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);
  const bool haveMissVal = vlistInqVarMissvalUsed(vlistID, varID);
  double validRange[2];
  if (!(haveMissVal && vlistInqVarValidrange(vlistID, varID, validRange)))
    validRange[0] = DBL_MIN, validRange[1] = DBL_MAX;
  double validMin = validRange[0];
  double validMax = validRange[1];
  double offset = vlistInqVarAddoffset(vlistID, varID);
  double scaleFactor = vlistInqVarScalefactor(vlistID, varID);

  const bool haveOffset = IS_NOT_EQUAL(offset, 0);
  const bool haveScaleFactor = IS_NOT_EQUAL(scaleFactor, 1);
  size_t missValCount = 0;

  if ( IS_EQUAL(validMin, VALIDMISS) ) validMin = DBL_MIN;
  if ( IS_EQUAL(validMax, VALIDMISS) ) validMax = DBL_MAX;

  bool haveRangeCheck = (IS_NOT_EQUAL(validMax, DBL_MAX)) | (IS_NOT_EQUAL(validMin,DBL_MIN));
  assert(!haveRangeCheck || haveMissVal);

  switch ((int)haveMissVal | ((int)haveScaleFactor << 1)
          | ((int)haveOffset << 2) | ((int)haveRangeCheck << 3))
    {
    case 15: /* haveRangeCheck & haveMissVal & haveScaleFactor & haveOffset */
      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 + offset;
        }
      break;
    case 13: /* haveRangeCheck & haveMissVal & haveOffset */
      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] + offset;
        }
      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 & haveOffset */
      for ( size_t i = 0; i < valueCount; i++ )
        if ( DBL_IS_EQUAL(data[i], missVal) )
          missValCount++;
        else
          data[i] = data[i] * scaleFactor + offset;
      break;
    case 6: /* haveOffset & haveScaleFactor */
      for ( size_t i = 0; i < valueCount; i++ )
        data[i] = data[i] * scaleFactor + offset;
      break;
    case 5: /* haveMissVal & haveOffset */
      for ( size_t i = 0; i < valueCount; i++ )
        if ( DBL_IS_EQUAL(data[i], missVal) )
          missValCount++;
        else
          data[i] += offset;
      break;
    case 4: /* haveOffset */
      for ( size_t i = 0; i < valueCount; i++ )
        data[i] += offset;
      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 */
      for ( size_t i = 0; i < valueCount; i++ )
        missValCount += (unsigned)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);
  const bool haveMissVal = vlistInqVarMissvalUsed(vlistID, varID);
  double validRange[2];
  if (!(haveMissVal && vlistInqVarValidrange(vlistID, varID, validRange)))
    validRange[0] = DBL_MIN, validRange[1] = DBL_MAX;
  double validMin = validRange[0];
  double validMax = validRange[1];
  double offset = vlistInqVarAddoffset(vlistID, varID);
  double scaleFactor = vlistInqVarScalefactor(vlistID, varID);

  const bool haveOffset = IS_NOT_EQUAL(offset, 0);
  const bool haveScaleFactor = IS_NOT_EQUAL(scaleFactor, 1);
  size_t missValCount = 0;

  if ( IS_EQUAL(validMin, VALIDMISS) ) validMin = DBL_MIN;
  if ( IS_EQUAL(validMax, VALIDMISS) ) validMax = DBL_MAX;

  bool haveRangeCheck = (IS_NOT_EQUAL(validMax, DBL_MAX)) | (IS_NOT_EQUAL(validMin,DBL_MIN));
  assert(!haveRangeCheck || haveMissVal);

  switch ((int)haveMissVal | ((int)haveScaleFactor << 1)
          | ((int)haveOffset << 2) | ((int)haveRangeCheck << 3))
    {
    case 15: /* haveRangeCheck & haveMissVal & haveScaleFactor & haveOffset */
      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 + offset);
        }
      break;
    case 13: /* haveRangeCheck & haveMissVal & haveOffset */
      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] + offset);
        }
      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 & haveOffset */
      for ( size_t i = 0; i < valueCount; i++ )
        if ( DBL_IS_EQUAL(data[i], missVal) )
          missValCount++;
        else
          data[i] = (float)(data[i] * scaleFactor + offset);
      break;
    case 6: /* haveOffset & haveScaleFactor */
      for ( size_t i = 0; i < valueCount; i++ )
        data[i] = (float)(data[i] * scaleFactor + offset);
      break;
    case 5: /* haveMissVal & haveOffset */
      for ( size_t i = 0; i < valueCount; i++ )
        if ( DBL_IS_EQUAL(data[i], missVal) )
          missValCount++;
        else
          data[i] = (float)(data[i] + offset);
      break;
    case 4: /* haveOffset */
      for ( size_t i = 0; i < valueCount; i++ )
        data[i] = (float)(data[i] + offset);
      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 */
      for ( size_t i = 0; i < valueCount; i++ )
        missValCount += (unsigned)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 = gridInqYsize(gridId);
  size_t inHeight = gridInqXsize(gridId);
  
  const 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(size_t gridId, float *data)
{
  size_t inWidth = gridInqYsize(gridId);
  size_t inHeight = gridInqXsize(gridId);

  const 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 cdfInqDimIds(stream_t *streamptr, int varId, int (*outDimIds)[3])
{
  const int gridId = vlistInqVarGrid(streamptr->vlistID, varId);
  const int gridindex = vlistGridIndex(streamptr->vlistID, gridId);

  (*outDimIds)[0] = (*outDimIds)[1] = (*outDimIds)[2] = CDI_UNDEFID;
  switch ( gridInqType(gridId) )
    {
      case GRID_TRAJECTORY:
        cdfReadGridTraj(streamptr, gridId);
        break;

      case GRID_UNSTRUCTURED:
        (*outDimIds)[0] = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
        break;

      case GRID_GAUSSIAN_REDUCED:
        (*outDimIds)[0] = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
        break;

      default:
        (*outDimIds)[0] = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_X];
        (*outDimIds)[1] = streamptr->ncgrid[gridindex].ncIDs[CDF_DIMID_Y];
        break;
    }

  const int zaxisID = vlistInqVarZaxis(streamptr->vlistID, varId);
  const int zaxisindex = vlistZaxisIndex(streamptr->vlistID, zaxisID);
  (*outDimIds)[2] = streamptr->zaxisID[zaxisindex];
}

static
int cdfGetSkipDim(int fileId, int ncvarid, int (*dimIds)[3])
{
  if((*dimIds)[0] != CDI_UNDEFID) return 0;
  if((*dimIds)[1] != CDI_UNDEFID) return 0;

  int nvdims;
  cdf_inq_varndims(fileId, ncvarid, &nvdims);
  if(nvdims != 3) return 0;

  int varDimIds[3];
  cdf_inq_vardimid(fileId, ncvarid, varDimIds);

  size_t size = 0;
  if ( (*dimIds)[2] == varDimIds[2] )
    {
      cdf_inq_dimlen(fileId, varDimIds[1], &size);
      if ( size == 1 ) return 1;
    }
  else if ( (*dimIds)[2] == varDimIds[1] )
    {
      cdf_inq_dimlen(fileId, varDimIds[2], &size);
      if ( size == 1 ) return 2;
    }

  return 0;
}

static
void cdfGetSliceSlapDescription(stream_t *streamptr, int varID, int levelID, bool *outSwapXY, size_t (*start)[4], size_t (*count)[4])
{
  const int tsID = streamptr->curTsID;
  if ( CDI_Debug ) Message("tsID = %d", tsID);

  const int fileId = streamptr->fileID;
  const int vlistID = streamptr->vlistID;
  const int ncvarid = streamptr->vars[varID].ncvarid;

  const int gridId = vlistInqVarGrid(vlistID, varID);
  const int timetype = vlistInqVarTimetype(vlistID, varID);
  const size_t gridsize = gridInqSize(gridId);

  streamptr->numvals += gridsize;

  int dimIds[3]; // this array joins the old variables xid, yid, and zid
  cdfInqDimIds(streamptr, varID, &dimIds);

  const int skipdim = cdfGetSkipDim(fileId, ncvarid, &dimIds);

  int dimorder[3];
  vlistInqVarDimorder(vlistID, varID, &dimorder);

  *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((size_t)tsID, 1);
  if ( skipdim == 1 ) addDimension(0, 1);

  for ( int id = 0; id < 3; ++id )
    {
      size_t size;
      const int curDimId = dimIds[dimorder[id]-1];
      if ( curDimId == CDI_UNDEFID ) continue;
      switch ( dimorder[id] )
        {
          case 1:
          case 2:
            cdf_inq_dimlen(fileId, curDimId, &size);
            addDimension(0, size);
            break;
          case 3:
            addDimension((size_t)levelID, 1);
            break;
          default:
            Error("Internal errror: Malformed dimension order encountered. Please report this bug.\n");
        }
    }

  if ( skipdim == 2 ) addDimension(0, 1);

  assert(ndims <= (int)(sizeof(*start)/sizeof(**start)));
  assert(ndims <= (int)(sizeof(*count)/sizeof(**count)));

#undef addDimension

  if ( CDI_Debug )
    for (int idim = 0; idim < ndims; idim++)
      Message("dim = %d  start = %d  count = %d", 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)
{
  const int gridID  = vlistInqVarGrid(vlistID, varID);
  const int zaxisID = vlistInqVarZaxis(vlistID, varID);
  return gridInqSize(gridID) * (size_t)zaxisInqSize(zaxisID);
}


static
void cdfReadDataSliceSP2DP(int fileID, int ncvarid, size_t length, size_t start[4], size_t count[4], 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[4], size_t count[4], 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 cdfCheckDataDP_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 cdfCheckDataSP_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 cdfReadDataDP(stream_t *streamptr, int varID, size_t length, size_t start[4], size_t count[4], double *data)
{
  const int vlistID = streamptr->vlistID;
  const int fileID = streamptr->fileID;
  const int ncvarid = streamptr->vars[varID].ncvarid;
  const 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
        {
          cdf_get_vara_double(fileID, ncvarid, start, count, data);
      
          cdfCheckDataDP_UINT8(fileID, ncvarid, vlistID, varID, length, data);
        }
    }
}

static
void cdfReadDataSP(stream_t *streamptr, int varID, size_t length, size_t start[4], size_t count[4], float *data)
{
  const int vlistID = streamptr->vlistID;
  const int fileID = streamptr->fileID;
  const int ncvarid = streamptr->vars[varID].ncvarid;
  const int datatype = vlistInqVarDatatype(vlistID, varID);

  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
        {
          cdf_get_vara_float(fileID, ncvarid, start, count, data);
      
          cdfCheckDataSP_UINT8(fileID, ncvarid, vlistID, varID, length, data);
        }
    }
}

static
void cdfReadVarDP(stream_t *streamptr, int varID, double *data, size_t *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  const int vlistID = streamptr->vlistID;

  size_t start[4], count[4];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  const size_t length = getSizeVar3D(vlistID, varID);
  cdfReadDataDP(streamptr, varID, length, start, count, data);

  *nmiss = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

static
void cdfReadVarSP(stream_t *streamptr, int varID, float *data, size_t *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  const int vlistID = streamptr->vlistID;

  size_t start[4], count[4];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  const size_t length = getSizeVar3D(vlistID, varID);
  cdfReadDataSP(streamptr, varID, length, start, count, data);

  *nmiss = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}


void cdf_read_var(stream_t *streamptr, int varID, int memtype, void *data, size_t *nmiss)
{
  if ( memtype == MEMTYPE_DOUBLE )
    cdfReadVarDP(streamptr, varID, (double*) data, nmiss);
  else
    cdfReadVarSP(streamptr, varID, (float*) data, nmiss);
}

static
void cdfReadVarSliceDP(stream_t *streamptr, int varID, int levelID, double *data, size_t *nmiss)
{
  if ( CDI_Debug )
    Message("streamID = %d  varID = %d  levelID = %d", streamptr->self, varID, levelID);

  bool swapxy;
  size_t start[4], count[4];
  cdfGetSliceSlapDescription(streamptr, varID, levelID, &swapxy, &start, &count);

  const int vlistID = streamptr->vlistID;
  const int gridId = vlistInqVarGrid(vlistID, varID);
  const size_t length = gridInqSize(gridId);
  cdfReadDataDP(streamptr, varID, length, start, count, data);

  if ( swapxy ) transpose2dArrayDP(gridId, data);

  *nmiss = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

static
void cdfReadVarSliceSP(stream_t *streamptr, int varID, int levelID, float *data, size_t *nmiss)
{
  if ( CDI_Debug )
    Message("streamID = %d  varID = %d  levelID = %d", streamptr->self, varID, levelID);

  bool swapxy;
  size_t start[4], count[4];
  cdfGetSliceSlapDescription(streamptr, varID, levelID, &swapxy, &start, &count);

  const int vlistID = streamptr->vlistID;
  const int gridId = vlistInqVarGrid(vlistID, varID);
  const size_t length = gridInqSize(gridId);
  cdfReadDataSP(streamptr, varID, length, start, count, data);

  if ( swapxy ) transpose2dArraySP(gridId, data);

  *nmiss = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}


void cdf_read_var_slice(stream_t *streamptr, int varID, int levelID, int memtype, void *data, size_t *nmiss)
{
  if ( memtype == MEMTYPE_DOUBLE )
    cdfReadVarSliceDP(streamptr, varID, levelID, (double*) data, nmiss);
  else
    cdfReadVarSliceSP(streamptr, varID, levelID, (float*) data, nmiss);
}


void cdf_read_record(stream_t *streamptr, int memtype, void *data, size_t *nmiss)
{
  if ( CDI_Debug ) Message("streamID = %d", streamptr->self);

  const int tsID    = streamptr->curTsID;
  const int vrecID  = streamptr->tsteps[tsID].curRecID;
  const int recID   = streamptr->tsteps[tsID].recIDs[vrecID];
  const int varID   = streamptr->tsteps[tsID].records[recID].varID;
  const int levelID = streamptr->tsteps[tsID].records[recID].levelID;

  if ( memtype == MEMTYPE_DOUBLE )
    cdfReadVarSliceDP(streamptr, varID, levelID, (double*) data, nmiss);
  else
    cdfReadVarSliceSP(streamptr, varID, levelID, (float*) data, nmiss);
}

//----------------------------------------------------------------------------
// Parallel Version
//----------------------------------------------------------------------------

void cdfReadVarSliceDPPart(stream_t *streamptr, int varID, int levelID, int varType, int startpoint, size_t length, double *data, size_t *nmiss)
{
  (void)(varType);

  if ( CDI_Debug )
    Message("streamID = %d  varID = %d  levelID = %d", streamptr->self, varID, levelID);

  const int vlistID = streamptr->vlistID;

  bool swapxy;
  size_t start[4], count[4];
  cdfGetSliceSlapDescription(streamptr, varID, levelID, &swapxy, &start, &count);

  const int gridId = vlistInqVarGrid(vlistID, varID);
  const size_t gridsize = gridInqSize(gridId);

  unsigned int position = 0; 
  for (int i=0 ; i<4 ; i++)
    if (count[i] == gridsize)
      position = i;

  start[position] = start[position]+startpoint;
  count[position] = length;

  cdfReadDataDP(streamptr, varID, length, start, count, data);

  if ( swapxy ) transpose2dArrayDP(gridId, data);

  *nmiss = 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 *nmiss)
{
  (void)(varType);

  if ( CDI_Debug )
    Message("streamID = %d  varID = %d  levelID = %d", streamptr->self, varID, levelID);

  const int vlistID = streamptr->vlistID;

  bool swapxy;
  size_t start[4], count[4];
  cdfGetSliceSlapDescription(streamptr, varID, levelID, &swapxy, &start, &count);

  const int gridId = vlistInqVarGrid(vlistID, varID);
  size_t gridsize = gridInqSize(gridId);

  unsigned int position = 0; 
  for (int i=0 ; i<4 ; i++)
    if (count[i] == gridsize)
      position = i;

  start[position] = start[position]+startpoint;
  count[position] = length;

  cdfReadDataSP(streamptr, varID, length, start, count, data);

  if ( swapxy ) transpose2dArraySP(gridId, data);

  *nmiss = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}

static
int cdiStreamReadVarSlicePart(int streamID, int varID, int levelID, int varType, int start, size_t size, int memtype, void *data, size_t *nmiss)
{
  int status = 0;

  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamID, varID);

  check_parg(data);
  check_parg(nmiss);

  stream_t *streamptr = stream_to_pointer(streamID);
  const int filetype = streamptr->filetype;

  *nmiss = 0;

  // currently we only care for netcdf data
  switch (filetype)
    {
#if defined (HAVE_LIBNETCDF)
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    case CDI_FILETYPE_NC5:
      {
        if ( memtype == MEMTYPE_FLOAT )
          cdfReadVarSliceSPPart(streamptr, varID, levelID, varType, start, size, (float *)data, nmiss);
        else
          cdfReadVarSliceDPPart(streamptr, varID, levelID, varType, start, size, (double *)data, nmiss);
        break;
      }
#endif
    default:
      {
        Error("%s support not compiled in!", strfiletype(filetype));
        status = 2;
        break;
      }
    }

  return status;
}


void cdfReadVarDPPart(stream_t *streamptr, int varID, int varType, int startpoint, size_t length, double *data, size_t *nmiss)
{
  (void)(varType);
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  const int vlistID = streamptr->vlistID;
  const int ncvarid = streamptr->vars[varID].ncvarid;

  size_t start[4], count[4];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  const int ltime = TIME_CONSTANT != vlistInqVarTimetype(vlistID, varID);
  start[1+ltime] = start[1+ltime]+startpoint;
  count[1+ltime] = length;

  cdf_get_vara_double(streamptr->fileID, ncvarid, start, count, data);

  *nmiss = cdfDoInputDataTransformationDP(vlistID, varID, length, data);
}

void cdfReadVarSPPart(stream_t *streamptr, int varID, int varType, int startpoint, size_t length, float *data, size_t *nmiss)
{
  (void)(varType);
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamptr->self, varID);

  const int vlistID = streamptr->vlistID;
  const int ncvarid = streamptr->vars[varID].ncvarid;

  size_t start[4], count[4];
  cdfGetSlapDescription(streamptr, varID, &start, &count);

  const int ltime = TIME_CONSTANT != vlistInqVarTimetype(vlistID, varID);
  start[1+ltime] = start[1+ltime]+startpoint;
  count[1+ltime] = length;

  cdf_get_vara_float(streamptr->fileID, ncvarid, start, count, data);

  *nmiss = cdfDoInputDataTransformationSP(vlistID, varID, length, data);
}

static
void cdiStreamReadVarPart(int streamID, int varID, int varType, int start, size_t size, int memtype, void *data, size_t *nmiss)
{
  (void)(varType);
  if ( CDI_Debug ) Message("streamID = %d  varID = %d", streamID, varID);

  check_parg(data);
  check_parg(nmiss);

  stream_t *streamptr = stream_to_pointer(streamID);
  const int filetype = streamptr->filetype;

  *nmiss = 0;

  // currently we only care for netcdf data
  switch (filetype)
    {
#if defined (HAVE_LIBNETCDF)
    case CDI_FILETYPE_NC:
    case CDI_FILETYPE_NC2:
    case CDI_FILETYPE_NC4:
    case CDI_FILETYPE_NC4C:
    case CDI_FILETYPE_NC5:
      {
        if ( memtype == MEMTYPE_FLOAT )
          cdfReadVarSPPart(streamptr, varID, varType, start, size, (float *)data, nmiss);
        else
          cdfReadVarDPPart(streamptr, varID, varType, start, size, (double *)data, nmiss);

        break;
      }
#endif
    default:
      {
        Error("%s support not compiled in!", strfiletype(filetype));
        break;
      }
    }
}

void streamReadVarSlicePart(int streamID, int varID, int levelID, int varType, int start, size_t size, void *data, size_t *nmiss, int memtype)
{
  if ( cdiStreamReadVarSlicePart(streamID, varID, levelID, varType, start, size, memtype, data, nmiss) )
    {
      Error("Unexpected error returned from cdiStreamReadVarSlicePart()!");
    }
}

void streamReadVarPart(int streamID, int varID, int varType, int start, size_t size, void *data, size_t *nmiss, int memtype)
{
  cdiStreamReadVarPart(streamID, varID, varType, start, size, memtype, data, nmiss);
}

#endif

/* 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]          */
/*                 .                                                   */


/* 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 );

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 ( strcmp(key, cdiSubtypeAttributeName[i]) == 0 ) 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]);
    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:");
    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);
  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);
    }

#ifndef __cplusplus
  if(!outValue) outValue = &(int){0};
#else
  int dummy = 0;
  if(!outValue) outValue = &dummy;
#endif

  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)
        {
          *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 )
{  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:
 */
#include <inttypes.h>
#include <stdio.h>


void swap4byte(void *ptr, size_t size)
{
  int32_t *ptrtmp = (int32_t *)ptr;

  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));
}

void swap8byte(void *ptr, size_t size)
{
  int64_t *ptrtmp = (int64_t *)ptr;

  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));
}
/*
 * 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

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));
  }
}

#endif  /* TABLE_H */
#include <stddef.h>
#include <string.h>
#include <ctype.h>




#define MAX_TABLE  256
#define 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     = strdupx(name);
      parTable[tableID].pars[item].dupflags |= TABLE_DUP_NAME;
    }
  if ( longname && longname[0] )
    {
      parTable[tableID].pars[item].longname = strdupx(longname);
      parTable[tableID].pars[item].dupflags |= TABLE_DUP_LONGNAME;
    }
  if ( units && units[0] )
    {
      parTable[tableID].pars[item].units    = strdupx(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 = strdupx(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)
          {
            if (parTable[tableID].pars[item].dupflags & TABLE_DUP_NAME)
              Free((void *)parTable[tableID].pars[item].name);
            if (parTable[tableID].pars[item].dupflags & TABLE_DUP_LONGNAME)
              Free((void *)parTable[tableID].pars[item].longname);
            if (parTable[tableID].pars[item].dupflags & TABLE_DUP_UNITS)
              Free((void *)parTable[tableID].pars[item].units);
          }
        Free(parTable[tableID].pars);
        Free(parTable[tableID].name);
      }
}

static void parTableInit(void)
{
  ParTableInit = 1;

  atexit(parTableFinalize);
  if ( cdiPartabIntern ) tableDefault();

  tableGetPath();
}

static int tableNewEntry()
{
  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 entries!");

  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;
  int lnr = 0;
  char name[256], longname[256], units[256];
  int err;
  int tableID = CDI_UNDEFID;

  FILE *tablefp = fopen(tablefile, "r");
  if ( tablefp == NULL ) return tableID;

  char *tablename = strrchr(tablefile, '/');
  if ( tablename == 0 ) tablename = (char *) 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';
      lnr++;
      int id      = CDI_UNDEFID;
      int ltype   = CDI_UNDEFID;
      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++;
      id = atoi(pline);
      // if ( id > 255 ) id -= 256;
      if ( id == 0 ) continue;

      while ( isdigit((int) *pline) ) pline++;

      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++;

      if ( strchr(pline, '|') )
	err = decodeForm2(pline, name, longname, units);
      else
	err = decodeForm1(pline, name, longname, units);

      if ( err ) continue;

      if ( name[0] == 0 ) sprintf(name, "var%d", id);

      tableDefEntry(tableID, id, ltype, name, longname, units);
    }

  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)(sprintf(tablename+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 )
	if ( 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 = strdupx(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;
}


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++)
    {
      if ( parTable[tableID].pars[item].name )
	{
	  size_t lenname = strlen(parTable[tableID].pars[item].name);
	  if ( lenname  > maxname )  maxname  = lenname;
	}

      if ( parTable[tableID].pars[item].longname )
	{
	  size_t lenlname = strlen(parTable[tableID].pars[item].longname);
	  if ( lenlname > maxlname ) maxlname = lenlname;
	}

      if ( parTable[tableID].pars[item].units )
	{
	  size_t lenunits = strlen(parTable[tableID].pars[item].units);
	  if ( lenunits > maxunits ) maxunits = lenunits;
	}
    }

  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++)
    {
      if ( parTable[tableID].pars[item].name )
	{
	  size_t lenname = strlen(parTable[tableID].pars[item].name);
	  if ( lenname  > maxname )  maxname  = lenname;
	}

      if ( parTable[tableID].pars[item].longname )
	{
	  size_t lenlname = strlen(parTable[tableID].pars[item].longname);
	  if ( lenlname > maxlname ) maxlname = lenlname;
	}

      if ( parTable[tableID].pars[item].units )
	{
	  size_t lenunits = strlen(parTable[tableID].pars[item].units);
	  if ( lenunits > maxunits ) maxunits = lenunits;
	}
    }

  strncpy(tablename, parTable[tableID].name, sizeof (tablename));
  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
               && strcmp(parTable[tableID].pars[item].name, varname) == 0 )
            {
              *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:
 */
#include <stddef.h>



static int DefaultTimeType = TAXIS_ABSOLUTE;
static int DefaultTimeUnit = TUNIT_HOUR;


static const char *Timeunits[] = {
  "undefined",
  "seconds",
  "minutes",
  "quarters",
  "30minutes",
  "hours",
  "3hours",
  "6hours",
  "12hours",
  "days",
  "months",
  "years",
};


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 );

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

static int  TAXIS_Debug = 0;   /* If set to 1, debugging */


const char *tunitNamePtr(int unitID)
{
  int size = sizeof(Timeunits)/sizeof(*Timeunits);
  return (unitID > 0 && unitID < size) ? Timeunits[unitID] : Timeunits[0];
}

static
void taxisDefaultValue(taxis_t* taxisptr)
{
  taxisptr->self        = CDI_UNDEFID;
  taxisptr->used        = false;
  taxisptr->datatype    = CDI_DATATYPE_FLT64;
  taxisptr->type        = DefaultTimeType;
  taxisptr->sdate       = 0;
  taxisptr->stime       = 0;
  taxisptr->vdate       = 0;
  taxisptr->vtime       = 0;
  taxisptr->rdate       = CDI_UNDEFID;
  taxisptr->rtime       = 0;
  taxisptr->fdate       = CDI_UNDEFID;
  taxisptr->ftime       = 0;
  taxisptr->calendar    = CDI_Default_Calendar;
  taxisptr->unit        = DefaultTimeUnit;
  taxisptr->numavg      = 0;
  taxisptr->climatology = false;
  taxisptr->has_bounds  = false;
  taxisptr->vdate_lb    = 0;
  taxisptr->vtime_lb    = 0;
  taxisptr->vdate_ub    = 0;
  taxisptr->vtime_ub    = 0;
  taxisptr->fc_unit     = DefaultTimeUnit;
  taxisptr->fc_period   = 0;
  taxisptr->name        = NULL;
  taxisptr->longname    = NULL;
  taxisptr->units       = NULL;
}

static taxis_t *
taxisNewEntry(cdiResH resH)
{
  taxis_t *taxisptr = (taxis_t*) Malloc(sizeof(taxis_t));

  taxisDefaultValue(taxisptr);
  if (resH == CDI_UNDEFID)
    taxisptr->self = reshPut(taxisptr, &taxisOps);
  else
    {
      taxisptr->self = resH;
      reshReplace(resH, taxisptr, &taxisOps);
    }

  return taxisptr;
}

static
void taxisInit(void)
{
  static bool taxisInitialized = false;

  if ( taxisInitialized ) return;

  taxisInitialized = true;

  char *env = getenv("TAXIS_DEBUG");
  if ( env ) TAXIS_Debug = atoi(env);
}

/*
@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)
{
  if ( CDI_Debug ) Message("taxistype: %d", taxistype);

  taxisInit ();

  taxis_t *taxisptr = taxisNewEntry(CDI_UNDEFID);
  taxisptr->type = taxistype;

  int taxisID = taxisptr->self;

  if ( CDI_Debug ) Message("taxisID: %d", taxisID);

  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;

  if ( CDI_Debug ) Message("taxisID2: %d", taxisID2);

  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;
      taxisptr->datatype = CDI_DATATYPE_FLT64;
      if ( taxisptr->units )
        {
          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, int64_t 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, int64_t vdate)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  if (taxisptr->vdate != vdate)
    {
      taxisptr->vdate = 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 (taxisptr->vtime != vtime)
    {
      taxisptr->vtime = vtime;
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }
}

/*
@Function  taxisDefRdate
@Title     Define the reference date

@Prototype void taxisDefRdate(int taxisID, int64_t 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, int64_t rdate)
{
  taxis_t *taxisptr = ( taxis_t * ) reshGetVal ( taxisID, &taxisOps );

  if (taxisptr->rdate != rdate)
    {
      taxisptr->rdate = rdate;

      if ( taxisptr->units )
        {
          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 (taxisptr->rtime != rtime)
    {
      taxisptr->rtime = rtime;
      if ( taxisptr->units )
        {
          delete_refcount_string(taxisptr->units);
          taxisptr->units = NULL;
        }
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }
}

/*
@Function  taxisDefFdate
@Title     Define the forecast reference date

@Prototype void taxisDefFdate(int taxisID, int64_t fdate)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  fdate    Forecast reference date (YYYYMMDD)

@Description
The function @func{taxisDefFdate} defines the forecast reference date of a Time axis.

@EndFunction
*/
void taxisDefFdate(int taxisID, int64_t fdate)
{
  taxis_t *taxisptr = ( taxis_t * ) reshGetVal ( taxisID, &taxisOps );

  if (taxisptr->fdate != fdate)
    {
      taxisptr->fdate = fdate;
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }
}

/*
@Function  taxisDefFtime
@Title     Define the forecast reference time

@Prototype void taxisDefFtime(int taxisID, int ftime)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate}
    @Item  ftime    Forecast reference time (hhmmss)

@Description
The function @func{taxisDefFtime} defines the forecast reference time of a Time axis.

@EndFunction
*/
void taxisDefFtime(int taxisID, int ftime)
{
  taxis_t *taxisptr = ( taxis_t * ) reshGetVal ( taxisID, &taxisOps );

  if (taxisptr->ftime != ftime)
    {
      taxisptr->ftime = ftime;
      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;
      if ( taxisptr->units )
        {
          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 taxisptr->has_bounds;
}


void taxisWithBounds(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  if ( taxisptr->has_bounds == false )
    {
      taxisptr->has_bounds = true;
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }
}


void taxisDeleteBounds(int taxisID)
{
  taxis_t *taxisptr = ( taxis_t * ) reshGetVal ( taxisID, &taxisOps );

  if ( taxisptr->has_bounds )
    {
      taxisptr->has_bounds = 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().
  if (taxisptr2->units && taxisptr2->rdate != CDI_UNDEFID)
    {
      if (taxisptr2->rdate != taxisptr1->rdate || taxisptr2->rtime != taxisptr1->rtime)
        {
          delete_refcount_string(taxisptr2->units);
          taxisptr2->units = NULL;
        }
    }

  taxisptr2->rdate = taxisptr1->rdate;
  taxisptr2->rtime = taxisptr1->rtime;
  */

  taxisptr2->sdate = taxisptr1->sdate;
  taxisptr2->stime = taxisptr1->stime;

  taxisptr2->vdate = taxisptr1->vdate;
  taxisptr2->vtime = taxisptr1->vtime;

  if ( taxisptr2->has_bounds )
    {
      taxisptr2->vdate_lb = taxisptr1->vdate_lb;
      taxisptr2->vtime_lb = taxisptr1->vtime_lb;
      taxisptr2->vdate_ub = taxisptr1->vdate_ub;
      taxisptr2->vtime_ub = taxisptr1->vtime_ub;
    }

  taxisptr2->fdate = taxisptr1->fdate;
  taxisptr2->ftime = taxisptr1->ftime;

  taxisptr2->fc_unit   = taxisptr1->fc_unit;
  taxisptr2->fc_period = taxisptr1->fc_period;

  reshSetStatus(taxisID2, &taxisOps, RESH_DESYNC_IN_USE);
  reshUnlock();
}

/*
@Function  taxisInqVdate
@Title     Get the verification date

@Prototype int64_t 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
*/
int64_t taxisInqVdate(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);
  return taxisptr->vdate;
}


int64_t taxisInqSdate(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);
  return taxisptr->sdate;
}


void taxisInqVdateBounds(int taxisID, int64_t *vdate_lb, int64_t *vdate_ub)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  *vdate_lb = taxisptr->vdate_lb;
  *vdate_ub = taxisptr->vdate_ub;
}


void taxisDefVdateBounds(int taxisID, int64_t vdate_lb, int64_t vdate_ub)
{
  taxis_t *taxisptr = ( taxis_t * ) reshGetVal ( taxisID, &taxisOps );

  if ( taxisptr->vdate_lb != vdate_lb
       || taxisptr->vdate_ub != vdate_ub
       || taxisptr->has_bounds == false )
    {
      taxisptr->vdate_lb = vdate_lb;
      taxisptr->vdate_ub = vdate_ub;
      taxisptr->has_bounds = 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 taxisptr->vtime;
}


int taxisInqStime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);
  return taxisptr->stime;
}


void taxisInqVtimeBounds(int taxisID, int *vtime_lb, int *vtime_ub)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  *vtime_lb = taxisptr->vtime_lb;
  *vtime_ub = taxisptr->vtime_ub;
}


void taxisDefVtimeBounds(int taxisID, int vtime_lb, int vtime_ub)
{
  taxis_t *taxisptr = ( taxis_t * ) reshGetVal ( taxisID, &taxisOps );

  if ( taxisptr->vtime_lb != vtime_lb
       || taxisptr->vtime_ub != vtime_ub
       || taxisptr->has_bounds == false )
    {
      taxisptr->vtime_lb = vtime_lb;
      taxisptr->vtime_ub = vtime_ub;
      taxisptr->has_bounds = true;
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }
}

/*
@Function  taxisInqRdate
@Title     Get the reference date

@Prototype int64_t 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
*/
int64_t taxisInqRdate(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  if ( taxisptr->rdate == -1 )
    {
      taxisptr->rdate = taxisptr->vdate;
      taxisptr->rtime = taxisptr->vtime;
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }

  return taxisptr->rdate;
}

/*
@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 ( taxisptr->rdate == -1 )
    {
      taxisptr->rdate = taxisptr->vdate;
      taxisptr->rtime = taxisptr->vtime;
      reshSetStatus(taxisID, &taxisOps, RESH_DESYNC_IN_USE);
    }

  return taxisptr->rtime;
}

/*
@Function  taxisInqFdate
@Title     Get the forecast reference date

@Prototype int64_t taxisInqFdate(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqFdate} returns the forecast reference date of a Time axis.

@Result
@func{taxisInqFdate} returns the forecast reference date.

@EndFunction
*/
int64_t taxisInqFdate(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  if ( taxisptr->fdate == -1 )
    {
      taxisptr->fdate = taxisptr->vdate;
      taxisptr->ftime = taxisptr->vtime;
    }

  return taxisptr->fdate;
}

/*
@Function  taxisInqFtime
@Title     Get the forecast reference time

@Prototype int taxisInqFtime(int taxisID)
@Parameter
    @Item  taxisID  Time axis ID, from a previous call to @fref{taxisCreate} or @fref{vlistInqTaxis}

@Description
The function @func{taxisInqFtime} returns the forecast reference time of a Time axis.

@Result
@func{taxisInqFtime} returns the forecast reference time.

@EndFunction
*/
int taxisInqFtime(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);

  if ( taxisptr->fdate == -1 )
    {
      taxisptr->fdate = taxisptr->vdate;
      taxisptr->ftime = taxisptr->vtime;
    }

  return taxisptr->ftime;
}

/*
@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 *taxisPtr(int taxisID)
{
  taxis_t *taxisptr = (taxis_t *)reshGetVal(taxisID, &taxisOps);
  return taxisptr;
}


void ptaxisDefDatatype(taxis_t *taxisptr, int datatype)
{
  taxisptr->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);
    }
}


void ptaxisDefUnits(taxis_t *taxisptr, const char *units)
{
  if ( units )
    {
      size_t len = strlen(units);
      delete_refcount_string(taxisptr->units);
      char *taxisunits = taxisptr->units = new_refcount_string(len);
      strcpy(taxisunits, units);
    }
}


static void
cdiDecodeTimevalue(int timeunit, double timevalue, int64_t *days, int *secs)
{
  *days = 0;
  *secs = 0;

  if ( timeunit == TUNIT_MINUTE )
    {
      timevalue *= 60;
      timeunit = TUNIT_SECOND;
    }
  else if ( timeunit == TUNIT_HOUR )
    {
      timevalue /= 24;
      timeunit = TUNIT_DAY;
    }

  if ( timeunit == TUNIT_SECOND )
    {
      *days = (int) (timevalue/86400);
      double seconds = timevalue - *days*86400.;
      *secs = (int)lround(seconds);
      if ( *secs < 0 ) { *days -= 1; *secs += 86400; };
      /*
      {
	double cval = *days*86400. + *secs;
	if ( cval != timevalue )
	  printf("TUNIT_SECOND error: %g %g %d %d\n", timevalue, cval, *days, *secs);
      }
      */
    }
  else if ( timeunit == TUNIT_DAY )
    {
      *days = (int) timevalue;
      double seconds = (timevalue - *days)*86400;
      *secs = (int)lround(seconds);
      if ( *secs < 0 ) { *days -= 1; *secs += 86400; };
      /*
      {
	double cval = *days + *secs/86400.;
	if ( cval != timevalue )
	  printf("TUNIT_DAY error: %g %g %d %d\n", timevalue, cval, *days, *secs);
      }
      */
    }
  else
    {
      static bool lwarn = true;
      if ( lwarn )
	{
	  Warning("timeunit %s unsupported!", tunitNamePtr(timeunit));
	  lwarn = false;
	}
    }
}

static
void cdiEncodeTimevalue(int days, int secs, int timeunit, double *timevalue)
{
  if ( timeunit == TUNIT_SECOND )
    {
      *timevalue = days*86400. + secs;
    }
  else if ( timeunit == TUNIT_MINUTE  ||
	    timeunit == TUNIT_QUARTER ||
	    timeunit == TUNIT_30MINUTES )
    {
      *timevalue = days*1440. + secs/60.;
    }
  else if ( timeunit == TUNIT_HOUR   ||
	    timeunit == TUNIT_3HOURS ||
	    timeunit == TUNIT_6HOURS ||
	    timeunit == TUNIT_12HOURS )
    {
      *timevalue = days*24. + secs/3600.;
    }
  else if ( timeunit == TUNIT_DAY )
    {
      *timevalue = days + secs/86400.;
    }
  else
    {
      static bool lwarn = true;
      if ( lwarn )
	{
	  Warning("timeunit %s unsupported!", tunitNamePtr(timeunit));
	  lwarn = false;
	}
    }
}


void timeval2vtime(double timevalue, taxis_t *taxis, int64_t *vdate, int *vtime)
{
  int64_t rdate = taxis->rdate;
  int rtime = taxis->rtime;

  if ( DBL_IS_EQUAL(timevalue, 0.) )
    {
      *vdate = rdate;
      *vtime = rtime;
      return;
    }

  int year, month, day, hour, minute, second;
  cdiDecodeDate(rdate, &year, &month, &day);
  cdiDecodeTime(rtime, &hour, &minute, &second);

  int timeunit = taxis->unit;
  int calendar = taxis->calendar;

  if ( timeunit == TUNIT_MONTH && calendar == CALENDAR_360DAYS )
    {
      timeunit = TUNIT_DAY;
      timevalue *= 30;
    }

  if ( timeunit == TUNIT_MONTH || timeunit == TUNIT_YEAR )
    {
      if ( timeunit == TUNIT_YEAR ) timevalue *= 12;

      int nmon = (int) timevalue;
      double fmon = timevalue - nmon;

      month += nmon;

      while ( month > 12 ) { month -= 12; year++; }
      while ( month <  1 ) { month += 12; year--; }

      int dpm = days_per_month(calendar, year, month);
      timeunit = TUNIT_DAY;
      timevalue = fmon*dpm;
    }

  int64_t julday, days;
  int secofday, secs;
  encode_caldaysec(calendar, year, month, day, hour, minute, second, &julday, &secofday);

  cdiDecodeTimevalue(timeunit, timevalue, &days, &secs);

  julday_add(days, secs, &julday, &secofday);

  decode_caldaysec(calendar, julday, secofday, &year, &month, &day, &hour, &minute, &second);

  *vdate = cdiEncodeDate(year, month, day);
  *vtime = cdiEncodeTime(hour, minute, second);
}


double vtime2timeval(int64_t vdate, int vtime, taxis_t *taxis)
{
  double value = 0;
  int64_t julday1, julday2, days;
  int secofday1, secofday2, secs;

  int timeunit = (*taxis).unit;
  int calendar = (*taxis).calendar;

  int64_t rdate = (*taxis).rdate;
  int rtime = (*taxis).rtime;
  if ( rdate == -1 )
    {
      rdate = (*taxis).vdate;
      rtime = (*taxis).vtime;
    }

  if ( rdate == 0 && rtime == 0 && vdate == 0 && vtime == 0 ) return value;

  int ryear, rmonth;
  int year, month, day, hour, minute, second;
  cdiDecodeDate(rdate, &ryear, &rmonth, &day);
  cdiDecodeTime(rtime, &hour, &minute, &second);

  encode_caldaysec(calendar, ryear, rmonth, day, hour, minute, second, &julday1, &secofday1);

  cdiDecodeDate(vdate, &year, &month, &day);
  cdiDecodeTime(vtime, &hour, &minute, &second);

  int timeunit0 = timeunit;

  if ( timeunit == TUNIT_MONTH && calendar == CALENDAR_360DAYS )
    {
      timeunit = TUNIT_DAY;
    }

  if ( timeunit == TUNIT_MONTH || timeunit == TUNIT_YEAR )
    {
      value = (year-ryear)*12 - rmonth + month;

      int nmonth = (int) value;
      month -= nmonth;

      while ( month > 12 ) { month -= 12; year++; }
      while ( month <  1 ) { month += 12; year--; }

      int dpm = days_per_month(calendar, year, month);

      encode_caldaysec(calendar, year, month, day, hour, minute, second, &julday2, &secofday2);

      julday_sub(julday1, secofday1, julday2, secofday2, &days, &secs);

      value += (days+secs/86400.)/dpm;

      if ( timeunit == TUNIT_YEAR ) value = value/12;
    }
  else
    {
      encode_caldaysec(calendar, year, month, day, hour, minute, second, &julday2, &secofday2);

      julday_sub(julday1, secofday1, julday2, secofday2, &days, &secs);

      cdiEncodeTimevalue(days, secs, timeunit, &value);
    }

  if ( timeunit0 == TUNIT_MONTH && calendar == CALENDAR_360DAYS )
    {
      value /= 30;
    }

  return value;
}


static
void conv_timeval(double timevalue, int64_t *rvdate, int *rvtime)
{
  int daysec;

  int64_t vdate = (int) timevalue;
  if ( vdate < 0 )
    daysec = (int) (-(timevalue - vdate)*86400 + 0.01);
  else
    daysec = (int) ( (timevalue - vdate)*86400 + 0.01);

  int hour   =  daysec / 3600;
  int minute = (daysec - hour*3600)/60;
  int second =  daysec - hour*3600 - minute*60;
  int vtime  = cdiEncodeTime(hour, minute, second);

  *rvdate = vdate;
  *rvtime = vtime;
}


static
void splitTimevalue(double timevalue, int timeunit, int64_t *date, int *time)
{
  int64_t vdate = 0;
  int vtime = 0;

  if ( timeunit == TUNIT_SECOND )
    {
      timevalue /= 86400;
      conv_timeval(timevalue, &vdate, &vtime);
    }
  else if ( timeunit == TUNIT_HOUR )
    {
      timevalue /= 24;
      conv_timeval(timevalue, &vdate, &vtime);
    }
  else if ( timeunit == TUNIT_DAY )
    {
      conv_timeval(timevalue, &vdate, &vtime);
    }
  else if ( timeunit == TUNIT_MONTH )
    {
      vdate = (int64_t) timevalue*100 - ((timevalue < 0) * 2 - 1);
      vtime = 0;
    }
  else if ( timeunit == TUNIT_YEAR )
    {
      {
        static bool lwarn = true;
        if (lwarn && (fabs(timevalue - (int64_t) 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;
          }
      }

      vdate = (int64_t) timevalue*10000;
      vdate += timevalue < 0 ? -101 : 101;
      vtime = 0;
    }
  else
    {
      static bool lwarn = true;
      if ( lwarn )
	{
	  Warning("timeunit %s unsupported!", tunitNamePtr(timeunit));
	  lwarn = false;
	}
    }

  /* verify date and time */

  int year, month, day;
  cdiDecodeDate(vdate, &year, &month, &day);
  int hour, minute, second;
  cdiDecodeTime(vtime, &hour, &minute, &second);

  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;

      vdate = cdiEncodeDate(year, month, day);
      vtime = cdiEncodeTime(hour, minute, second);

      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);
        }
    }

  *date = vdate;
  *time = vtime;
}


void cdiSetForecastPeriod(double timevalue, taxis_t *taxis)
{
  int64_t julday, days;
  int secofday, secs;

  (*taxis).fc_period = timevalue;

  int timeunit = (*taxis).fc_unit;
  int calendar = (*taxis).calendar;

  int64_t vdate = (*taxis).vdate;
  int vtime = (*taxis).vtime;

  if ( vdate == 0 && vtime == 0 && DBL_IS_EQUAL(timevalue, 0.) ) return;

  int year, month, day, hour, minute, second;
  cdiDecodeDate(vdate, &year, &month, &day);
  cdiDecodeTime(vtime, &hour, &minute, &second);

  if ( timeunit == TUNIT_MONTH && calendar == CALENDAR_360DAYS )
    {
      timeunit = TUNIT_DAY;
      timevalue *= 30;
    }

  if ( timeunit == TUNIT_MONTH || timeunit == TUNIT_YEAR )
    {
      int nmon, dpm;
      double fmon;

      if ( timeunit == TUNIT_YEAR ) timevalue *= 12;

      nmon = (int) timevalue;
      fmon = timevalue - nmon;

      month -= nmon;

      while ( month > 12 ) { month -= 12; year++; }
      while ( month <  1 ) { month += 12; year--; }

      dpm = days_per_month(calendar, year, month);
      timeunit = TUNIT_DAY;
      timevalue = fmon*dpm;
    }

  encode_caldaysec(calendar, year, month, day, hour, minute, second, &julday, &secofday);

  cdiDecodeTimevalue(timeunit, timevalue, &days, &secs);

  julday_add(-days, -secs, &julday, &secofday);

  decode_caldaysec(calendar, julday, secofday, &year, &month, &day, &hour, &minute, &second);

  (*taxis).fdate = cdiEncodeDate(year, month, day);
  (*taxis).ftime = cdiEncodeTime(hour, minute, second);
}


void cdiDecodeTimeval(double timevalue, taxis_t *taxis, int64_t *date, int *time)
{
  if ( taxis->type == TAXIS_ABSOLUTE )
    splitTimevalue(timevalue, taxis->unit, date, time);
  else
    timeval2vtime(timevalue, taxis, date, time);
}


double cdiEncodeTimeval(int64_t date, int time, taxis_t *taxis)
{
  double timevalue;

  if ( taxis->type == TAXIS_ABSOLUTE )
    {
      if ( taxis->unit == TUNIT_YEAR )
	{
	  int year, month, day;
	  cdiDecodeDate(date, &year, &month, &day);

	  timevalue = year;
	}
      else if ( taxis->unit == TUNIT_MONTH )
	{
	  int year, month, day;
	  cdiDecodeDate(date, &year, &month, &day);
          timevalue = date/100
            + copysign((double)(day != 0) * 0.5, (double)date);
        }
      else
	{
	  int hour, minute, second;
	  cdiDecodeTime(time, &hour, &minute, &second);
          timevalue = copysign(1.0, (double)date)
            * (fabs((double)date) + (hour*3600 + minute*60 + second)/86400.);
	}
    }
  else
    timevalue = vtime2timeval(date, time, taxis);

  return timevalue;
}


void ptaxisInit(taxis_t *taxisptr)
{
  taxisDefaultValue ( taxisptr );
}


void ptaxisCopy(taxis_t *dest, taxis_t *source)
{
  reshLock ();

  /* memcpy(dest, source, sizeof(taxis_t)); */
  dest->used        = source->used;
  dest->datatype    = source->datatype;
  dest->type        = source->type;
  dest->sdate       = source->sdate;
  dest->stime       = source->stime;
  dest->vdate       = source->vdate;
  dest->vtime       = source->vtime;
  dest->rdate       = source->rdate;
  dest->rtime       = source->rtime;
  dest->fdate       = source->fdate;
  dest->ftime       = source->ftime;
  dest->calendar    = source->calendar;
  dest->unit        = source->unit;
  dest->numavg      = source->numavg;
  dest->climatology = source->climatology;
  dest->has_bounds  = source->has_bounds;
  dest->vdate_lb    = source->vdate_lb;
  dest->vtime_lb    = source->vtime_lb;
  dest->vdate_ub    = source->vdate_ub;
  dest->vtime_ub    = source->vtime_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);

  reshUnlock ();
}


static void
taxisPrintKernel(taxis_t * taxisptr, FILE * fp)
{
  int64_t 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"
          "used        = %d\n"
          "type        = %d\n"
          "vdate       = %lld\n"
          "vtime       = %d\n"
          "rdate       = %lld\n"
          "rtime       = %d\n"
          "fdate       = %lld\n"
          "ftime       = %d\n"
          "calendar    = %d\n"
          "unit        = %d\n"
          "numavg      = %d\n"
          "climatology = %d\n"
          "has_bounds  = %d\n"
          "vdate_lb    = %lld\n"
          "vtime_lb    = %d\n"
          "vdate_ub    = %lld\n"
          "vtime_ub    = %d\n"
          "fc_unit     = %d\n"
          "fc_period   = %g\n"
          "\n", taxisptr->self, taxisptr->self,
          (int)taxisptr->used, taxisptr->type,
          taxisptr->vdate, taxisptr->vtime,
          taxisptr->rdate, taxisptr->rtime,
          taxisptr->fdate, taxisptr->ftime,
          taxisptr->calendar, taxisptr->unit,
          taxisptr->numavg, (int)taxisptr->climatology,
          (int) taxisptr->has_bounds,
          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->used        == t2->used        &&
	     t1->type        == t2->type        &&
	     t1->vdate       == t2->vdate       &&
	     t1->vtime       == t2->vtime       &&
	     t1->rdate       == t2->rdate       &&
	     t1->rtime       == t2->rtime       &&
	     t1->fdate       == t2->fdate       &&
	     t1->ftime       == t2->ftime       &&
	     t1->calendar    == t2->calendar    &&
	     t1->unit        == t2->unit        &&
	     t1->fc_unit     == t2->fc_unit     &&
	     t1->numavg      == t2->numavg      &&
	     t1->climatology == t2->climatology &&
	     t1->has_bounds  == t2->has_bounds  &&
	     t1->vdate_lb    == t2->vdate_lb    &&
	     t1->vtime_lb    == t2->vtime_lb    &&
	     t1->vdate_ub    == t2->vdate_ub    &&
	     t1->vtime_ub    == t2->vtime_ub );
}


static int
taxisTxCode ( void )
{
  return TAXIS;
}

enum { taxisNint = 22 };

static int
taxisGetPackSize(void *p, void *context)
{
  taxis_t *taxisptr = (taxis_t*) p;
  int packBufferSize
    = serializeGetSize(taxisNint, CDI_DATATYPE_INT, context)
    + serializeGetSize(1, CDI_DATATYPE_UINT32, 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);
  return packBufferSize;
}

int
taxisUnpack(char * unpackBuffer, int unpackBufferSize, int * unpackBufferPos,
            int originNamespace, void *context, int force_id)
{
  taxis_t * taxisP;
  int intBuffer[taxisNint];
  uint32_t d;
  int idx = 0;

  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                  intBuffer, taxisNint, CDI_DATATYPE_INT, context);
  serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                  &d, 1, CDI_DATATYPE_UINT32, context);

  xassert(cdiCheckSum(CDI_DATATYPE_INT, taxisNint, intBuffer) == d);

  taxisInit();

  cdiResH targetID = namespaceAdaptKey(intBuffer[idx++], originNamespace);
  taxisP = taxisNewEntry(force_id?targetID:CDI_UNDEFID);

  xassert(!force_id || targetID == taxisP->self);

  taxisP->used        = (short)intBuffer[idx++];
  taxisP->type        = intBuffer[idx++];
  taxisP->vdate       = intBuffer[idx++];
  taxisP->vtime       = intBuffer[idx++];
  taxisP->rdate       = intBuffer[idx++];
  taxisP->rtime       = intBuffer[idx++];
  taxisP->fdate       = intBuffer[idx++];
  taxisP->ftime       = intBuffer[idx++];
  taxisP->calendar    = intBuffer[idx++];
  taxisP->unit        = intBuffer[idx++];
  taxisP->fc_unit     = intBuffer[idx++];
  taxisP->numavg      = intBuffer[idx++];
  taxisP->climatology = intBuffer[idx++];
  taxisP->has_bounds  = (short)intBuffer[idx++];
  taxisP->vdate_lb    = intBuffer[idx++];
  taxisP->vtime_lb    = intBuffer[idx++];
  taxisP->vdate_ub    = intBuffer[idx++];
  taxisP->vtime_ub    = intBuffer[idx++];

  if (intBuffer[idx])
    {
      int len = intBuffer[idx];
      char *name = new_refcount_string((size_t)len);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      name, len, CDI_DATATYPE_TXT, context);
      name[len] = '\0';
      taxisP->name = name;
    }
  idx++;
  if (intBuffer[idx])
    {
      int len = intBuffer[idx];
      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[idx])
    {
      int len = intBuffer[idx];
      char *units = new_refcount_string((size_t)len);
      serializeUnpack(unpackBuffer, unpackBufferSize, unpackBufferPos,
                      units, len, CDI_DATATYPE_TXT, context);
      units[len] = '\0';
      taxisP->units = units;
    }

  reshSetStatus(taxisP->self, &taxisOps,
                reshGetStatus(taxisP->self, &taxisOps) & ~RESH_SYNC_BIT);

  return taxisP->self;
}


static void
taxisPack(void * voidP, void * packBuffer, int packBufferSize, int * packBufferPos,
          void *context)
{
  taxis_t *taxisP = (taxis_t *)voidP;
  int intBuffer[taxisNint];
  uint32_t d;
  int idx = 0;

  intBuffer[idx++] = taxisP->self;
  intBuffer[idx++] = taxisP->used;
  intBuffer[idx++] = taxisP->type;
  intBuffer[idx++] = taxisP->vdate;
  intBuffer[idx++] = taxisP->vtime;
  intBuffer[idx++] = taxisP->rdate;
  intBuffer[idx++] = taxisP->rtime;
  intBuffer[idx++] = taxisP->fdate;
  intBuffer[idx++] = taxisP->ftime;
  intBuffer[idx++] = taxisP->calendar;
  intBuffer[idx++] = taxisP->unit;
  intBuffer[idx++] = taxisP->fc_unit;
  intBuffer[idx++] = taxisP->numavg;
  intBuffer[idx++] = taxisP->climatology;
  intBuffer[idx++] = taxisP->has_bounds;
  intBuffer[idx++] = taxisP->vdate_lb;
  intBuffer[idx++] = taxisP->vtime_lb;
  intBuffer[idx++] = taxisP->vdate_ub;
  intBuffer[idx++] = taxisP->vtime_ub;
  intBuffer[idx++] = taxisP->name ? (int)strlen(taxisP->name) : 0;
  intBuffer[idx++] = taxisP->longname ? (int)strlen(taxisP->longname) : 0;
  intBuffer[idx++] = taxisP->units ? (int)strlen(taxisP->units) : 0;

  serializePack(intBuffer, taxisNint, CDI_DATATYPE_INT,
                packBuffer, packBufferSize, packBufferPos, context);
  d = cdiCheckSum(CDI_DATATYPE_INT, taxisNint, intBuffer);
  serializePack(&d, 1, CDI_DATATYPE_UINT32,
                packBuffer, packBufferSize, packBufferPos, context);
  if (taxisP->name)
    serializePack(taxisP->name, intBuffer[15], CDI_DATATYPE_TXT,
                  packBuffer, packBufferSize, packBufferPos, context);
  if (taxisP->longname)
    serializePack(taxisP->longname, intBuffer[16], CDI_DATATYPE_TXT,
                  packBuffer, packBufferSize, packBufferPos, context);
  if (taxisP->units)
    serializePack(taxisP->units, intBuffer[16], CDI_DATATYPE_TXT,
                  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:
 */
#include <math.h>		/* for floor() */


/* convert Julian date into year, months, day */
void decode_julday(int calendar,
		   int64_t julday, /* Julian day number to convert */
		   int *year,	   /* Gregorian year (out)         */
		   int *mon,	   /* Gregorian month (1-12) (out) */
		   int *day)       /* Gregorian day (1-31) (out)   */
{
  int64_t a = julday;

  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
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 julday = (int64_t) (floor(365.25*iy) + (int64_t)(30.6001*(im+1)) + ib + 1720996.5 + day + 0.5);

  return julday;
}


int64_t date_to_julday(int calendar, int64_t date)
{
  int year, month, day;
  cdiDecodeDate(date, &year, &month, &day);

  int64_t julday = encode_julday(calendar, year, month, day);

  return julday;
}


int64_t julday_to_date(int calendar, int64_t julday)
{
  int year, month, day;
  decode_julday(calendar, julday, &year, &month, &day);

  int64_t date = cdiEncodeDate(year, month, day);

  return date;
}


int time_to_sec(int time)
{
  int hour, minute, second;
  cdiDecodeTime(time, &hour, &minute, &second);

  int secofday = hour*3600 + minute*60 + second;

  return secofday;
}


int sec_to_time(int secofday)
{
  int hour   = secofday/3600;
  int minute = secofday/60 - hour*60;
  int second = secofday - hour*3600 - minute*60;

  int time = cdiEncodeTime(hour, minute, second);

  return time;
}

static
void adjust_seconds(int64_t *julday, int64_t *secofday)
{
  int64_t secperday = 86400;

  while ( *secofday >= secperday )
    {
      *secofday -= secperday;
      (*julday)++;
    }

  while ( *secofday <  0 )
    {
      *secofday += secperday;
      (*julday)--;
    }
}


void julday_add_seconds(int64_t seconds, int64_t *julday, int *secofday)
{
  int64_t sec_of_day = *secofday;

  sec_of_day += seconds;

  adjust_seconds(julday, &sec_of_day);

  *secofday = (int) sec_of_day;
}

/* add days and secs to julday/secofday */
void julday_add(int days, int secs, int64_t *julday, int *secofday)
{
  int64_t sec_of_day = *secofday;

  sec_of_day += secs;
  *julday    += days;

  adjust_seconds(julday, &sec_of_day);

  *secofday = (int) sec_of_day;
}

/* subtract julday1/secofday1 from julday2/secofday2 and returns the result in seconds */
double julday_sub(int64_t julday1, int secofday1, int64_t julday2, int secofday2, int64_t *days, int *secs)
{
  *days = julday2 - julday1;
  *secs = secofday2 - secofday1;

  int64_t sec_of_day = *secs;

  adjust_seconds(days, &sec_of_day);

  *secs = (int) sec_of_day;

  int64_t seconds = (int64_t)(*days) * (int64_t)86400 + sec_of_day;

  return (double)seconds;
}


void encode_juldaysec(int calendar, int year, int month, int day, int hour, int minute, int second, int64_t *julday, int *secofday)
{
  *julday = encode_julday(calendar, year, month, day);

  *secofday = (hour*60 + minute)*60 + second;
}


void decode_juldaysec(int calendar, int64_t julday, int secofday, int *year, int *month, int *day, int *hour, int *minute, int *second)
{
  decode_julday(calendar, julday, year, month, day);

  *hour   = secofday/3600;
  *minute = secofday/60 - *hour*60;
  *second = secofday - *hour*3600 - *minute*60;
}


#ifdef TEST
int main(void)
{
  int nmin;
  int64_t vdate0, vdate;
  int vtime0, vtime;
  int ijulinc;
  int j = 0;
  int year, mon, day, hour, minute, second;
  int64_t julday;
  int secofday;
  int calendar = CALENDAR_STANDARD;

  /* 1 - Check valid range of years */

  nmin = 11000;
  vdate0 = -80001201;
  vtime0 = 120500;

  printf("start time: %8ld %4d\n", vdate0, vtime0);

  for ( int i = 0; i < nmin; i++ )
    {
      cdiDecodeDate(vdate0, &year, &mon, &day);
      cdiDecodeTime(vtime0, &hour, &minute, &second);

      julday  = date_to_julday(calendar, vdate0);
      secofday = time_to_sec(vtime0);

      vdate = julday_to_date(calendar, julday);
      vtime = sec_to_time(secofday);

      if ( vdate0 != vdate || vtime0 != vtime )
	printf("%4d %8ld %4d %8ld %4d %9d %9d\n",
	       ++j, vdate0, vtime0, vdate, vtime, julday, secofday);

      year++;
      vdate0 = cdiEncodeDate(year, mon, day);
      vtime0 = cdiEncodeTime(hour, minute, second);
    }

  printf("stop time: %8ld %4d\n", vdate0, vtime0);

  /* 2 - Check time increment of one minute */

  nmin = 120000;
  ijulinc = 60;
  vdate0 = 20001201;
  vtime0 = 0;

  printf("start time: %8ld %4d\n", vdate0, vtime0);

  julday = date_to_julday(calendar, vdate0);
  secofday = time_to_sec(vtime0);
  for ( int i = 0; i < nmin; i++ )
    {
      cdiDecodeDate(vdate0, &year, &mon, &day);
      cdiDecodeTime(vtime0, &hour, &minute, &second);

      if ( ++minute >= 60 )
	{
	  minute = 0;
	  if ( ++hour >= 24 )
	    {
	      hour = 0;
	      if ( ++day >= 32 )
		{
		  day = 1;
		  if ( ++mon >= 13 )
		    {
		      mon = 1;
		      year++;
		    }
		}
	    }
	}

      vdate0 = cdiEncodeDate(year, mon, day);
      vtime0 = cdiEncodeTime(hour, minute, second);

      julday_add_seconds(ijulinc, &julday, &secofday);

      vdate = julday_to_date(calendar, julday);
      vtime = sec_to_time(secofday);
      if ( vdate0 != vdate || vtime0 != vtime )
	printf("%4d %8ld %4d %8ld %4d %9d %9d\n",
	       ++j, vdate0, vtime0, vdate, vtime, julday, secofday);
    }

  printf("stop time: %8ld %4d\n", vdate0, vtime0);

  return 0;
}
#endif


#ifdef TEST2
int main(void)
{
  int julday, secofday;
  int year, month, day, hour, minute, second;
  int value = 30;
  int factor = 86400;
  int calendar = CALENDAR_STANDARD;

  year=1979; month=1; day=15; hour=12; minute=30, second=17;

  printf("%d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);

  encode_juldaysec(calendar, year, month, day, hour, minute, second, &julday, &secofday);

  decode_juldaysec(calendar, julday, secofday, &year, &month, &day, &hour, &minute, &second);
  printf("%d/%02d/%02d %02d:%02d:%02d   %d %d\n", year, month, day, hour, minute, second, julday, secofday);

  for ( int i = 0; i < 420; i++ )
    {

      decode_juldaysec(calendar, julday, secofday, &year, &month, &day, &hour, &minute, &second);
      printf("%2d %d/%02d/%02d %02d:%02d:%02d\n", i, year, month, day, hour, minute, second);
      julday_add_seconds(value*factor, &julday, &secofday);
    }

  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:
 */
#include <limits.h>




static
void tstepsInitEntry(stream_t *streamptr, size_t tsID)
{
  streamptr->tsteps[tsID].curRecID     = CDI_UNDEFID;
  streamptr->tsteps[tsID].position     = 0;
  streamptr->tsteps[tsID].records      = NULL;
  streamptr->tsteps[tsID].recordSize   = 0;
  streamptr->tsteps[tsID].nallrecs     = 0;
  streamptr->tsteps[tsID].recIDs       = NULL;
  streamptr->tsteps[tsID].nrecs        = 0;
  streamptr->tsteps[tsID].next         = 0;

  ptaxisInit(&streamptr->tsteps[tsID].taxis);
}


int tstepsNewEntry(stream_t *streamptr)
{
  size_t tsID            = (size_t)streamptr->tstepsNextID++;
  size_t tstepsTableSize = (size_t)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,
                                         tstepsTableSize * sizeof (tsteps_t));
    }

  streamptr->tstepsTableSize = (int)tstepsTableSize;
  streamptr->tsteps          = tstepsTable;

  tstepsInitEntry(streamptr, tsID);

  streamptr->tsteps[tsID].taxis.used = true;

  return (int)tsID;
}


void cdiCreateTimesteps(stream_t *streamptr)
{
  if ( streamptr->ntsteps < 0 || streamptr->tstepsTableSize > 0 )
    return;

  long ntsteps = (streamptr->ntsteps == 0) ? 1 : streamptr->ntsteps;

  streamptr->tsteps = (tsteps_t *) Malloc((size_t)ntsteps*sizeof(tsteps_t));

  streamptr->tstepsTableSize = (int)ntsteps;
  streamptr->tstepsNextID    = (int)ntsteps;

  for ( long tsID = 0; tsID < ntsteps; tsID++ )
    {
      tstepsInitEntry(streamptr, (size_t)tsID);
      streamptr->tsteps[tsID].taxis.used = true;
    }
}
/*
 * 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";

enum {
  uuidNumHexChars = 36,
};


void cdiUUID2Str(const unsigned char *uuid, char *uuidstr)
{

  if ( uuid == NULL || uuidstr == NULL ) return;

  int iret = sprintf(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 != uuidNumHexChars ) uuidstr[0] = 0;
}


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;
}

#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)
{
  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

/*
 * 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)
  unsigned      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            lbounds;
  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
}
vartable_t;


static vartable_t *vartable;
static unsigned varTablesize = 0;
static unsigned varTableUsed = 0;

static
void paramInitEntry(unsigned varID, int param)
{
  vartable[varID].varID          = (int)varID;
  vartable[varID].param          = param;
  vartable[varID].prec           = 0;
  vartable[varID].tsteptype      = TSTEP_INSTANT;
  varScanKeysInit(&vartable[varID].scanKeys);
  vartable[varID].gridID         = CDI_UNDEFID;
  vartable[varID].zaxistype      = 0;
  vartable[varID].ltype1         = 0;
  vartable[varID].ltype2         = -1;
  vartable[varID].lbounds        = 0;
  vartable[varID].level_sf       = 0;
  vartable[varID].level_unit     = 0;
  vartable[varID].recordTable    = NULL;
  vartable[varID].nsubtypes_alloc= 0;
  vartable[varID].nsubtypes      = 0;
  vartable[varID].instID         = CDI_UNDEFID;
  vartable[varID].modelID        = CDI_UNDEFID;
  vartable[varID].tableID        = CDI_UNDEFID;
  cdiInitKeys(&vartable[varID].keys);
  vartable[varID].comptype       = CDI_COMPRESS_NONE;
  vartable[varID].complevel      = 1;
  vartable[varID].lmissval       = false;
  vartable[varID].missval        = 0;
  vartable[varID].name           = NULL;
  vartable[varID].tiles          = NULL;
}

// Test if a variable specified by the given meta-data has already been registered in "vartable".
static unsigned
varGetEntry(int param, int gridID, int zaxistype, int ltype1, int tsteptype, const char *name, const VarScanKeys *scanKeys, const var_tile_t *tiles)
{
  for ( unsigned varID = 0; varID < varTablesize; varID++ )
    {
      // testing for "param" implicitly checks if we are beyond the current vartable size:
      if ( vartable[varID].param == param )
        {
          const int no_of_tiles = tiles ? tiles->numberOfTiles : -1;
          const int vt_no_of_tiles = vartable[varID].tiles ?
            subtypeGetGlobalDataP(vartable[varID].tiles, SUBTYPE_ATT_NUMBER_OF_TILES) : -1;
          if ( (vartable[varID].zaxistype  == zaxistype)               &&
               (vartable[varID].ltype1     == ltype1   )               &&
               (vartable[varID].tsteptype  == tsteptype)               &&
               (scanKeys == NULL || varScanKeysIsEqual(&vartable[varID].scanKeys, scanKeys))  &&
               (vartable[varID].gridID     == gridID   )               &&
               (vt_no_of_tiles == no_of_tiles) )
            {
              if ( name && name[0] && vartable[varID].name && vartable[varID].name[0] )
                {
                  if ( strcmp(name, vartable[varID].name) == 0 ) return varID;
                }
              else
                {
                  return varID;
                }
            }
        }
    }

  return (unsigned)-1;
}

static
void varFree(void)
{
  if ( CDI_Debug ) Message("call to varFree");

  for ( size_t varID = 0; varID < varTableUsed; varID++ )
    {
      if ( vartable[varID].recordTable )
        {
          for (int isub=0; isub<vartable[varID].nsubtypes_alloc; isub++)
            Free(vartable[varID].recordTable[isub].levelTable);
          Free(vartable[varID].recordTable);
        }

      if ( vartable[varID].name )     Free(vartable[varID].name);
      if ( vartable[varID].tiles )    subtypeDestroyPtr(vartable[varID].tiles);

      cdi_keys_t *keysp = &(vartable[varID].keys);
      cdiDeleteVarKeys(keysp);

      if ( vartable[varID].opt_grib_kvpair )
        {
          for (int i=0; i<vartable[varID].opt_grib_nentries; i++)
            {
              if ( vartable[varID].opt_grib_kvpair[i].keyword )
                Free(vartable[varID].opt_grib_kvpair[i].keyword);
            }
          Free(vartable[varID].opt_grib_kvpair);
        }
      vartable[varID].opt_grib_nentries    = 0;
      vartable[varID].opt_grib_kvpair_size = 0;
      vartable[varID].opt_grib_kvpair      = NULL;
    }

  if ( vartable ) Free(vartable);
  vartable = NULL;
  varTablesize = 0;
  varTableUsed = 0;

  if ( Vct ) Free(Vct);
  Vct = NULL;
  Vctsize = 0;
}

// Search for a tile subtype with subtypeIndex == tile_index.
static int tileGetEntry(unsigned varID, int tile_index)
{
  for (int isub=0; isub<vartable[varID].nsubtypes; isub++)
    if (vartable[varID].recordTable[isub].subtypeIndex == tile_index)
      return isub;
  return CDI_UNDEFID;
}


/* Resizes vartable:recordTable data structure, if necessary. */
static int tileNewEntry(int varID)
{
  int tileID = 0;
  if (vartable[varID].nsubtypes_alloc == 0)
    {
      /* create table for the first time. */
      vartable[varID].nsubtypes_alloc = 2;
      vartable[varID].nsubtypes       = 0;
      vartable[varID].recordTable     =
        (subtypetable_t *) Malloc((size_t)vartable[varID].nsubtypes_alloc * sizeof (subtypetable_t));
      if( vartable[varID].recordTable == NULL )
        SysError("Allocation of leveltable failed!");

      for (int isub = 0; isub<vartable[varID].nsubtypes_alloc; isub++) {
	vartable[varID].recordTable[isub].levelTable     = NULL;
        vartable[varID].recordTable[isub].levelTableSize = 0;
        vartable[varID].recordTable[isub].nlevels        = 0;
        vartable[varID].recordTable[isub].subtypeIndex   = CDI_UNDEFID;
      }
    }
  else
    {
      /* data structure large enough; find a free entry. */
      while(tileID <  vartable[varID].nsubtypes_alloc)
	{
	  if (vartable[varID].recordTable[tileID].levelTable == NULL) break;
	  tileID++;
	}
    }

  /* If the table overflows, double its size. */
  if (tileID == vartable[varID].nsubtypes_alloc)
    {
      tileID = vartable[varID].nsubtypes_alloc;
      vartable[varID].nsubtypes_alloc *= 2;
      vartable[varID].recordTable   =
        (subtypetable_t *) Realloc(vartable[varID].recordTable,
                                   (size_t)vartable[varID].nsubtypes_alloc * sizeof (subtypetable_t));
      if (vartable[varID].recordTable == NULL)
        SysError("Reallocation of leveltable failed");
      for(int isub=tileID; isub<vartable[varID].nsubtypes_alloc; isub++) {
	vartable[varID].recordTable[isub].levelTable     = NULL;
        vartable[varID].recordTable[isub].levelTableSize = 0;
        vartable[varID].recordTable[isub].nlevels        = 0;
        vartable[varID].recordTable[isub].subtypeIndex   = CDI_UNDEFID;
      }
    }

  return tileID;
}


static int levelNewEntry(unsigned varID, int level1, int level2, int tileID)
{
  int levelID = 0;
  int levelTableSize = vartable[varID].recordTable[tileID].levelTableSize;
  leveltable_t *levelTable = vartable[varID].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;

  vartable[varID].recordTable[tileID].nlevels        = (unsigned)levelID+1;
  vartable[varID].recordTable[tileID].levelTableSize = levelTableSize;
  vartable[varID].recordTable[tileID].levelTable     = levelTable;

  return levelID;
}

#define  UNDEF_PARAM  -4711

static unsigned
paramNewEntry(int param)
{
  unsigned varID = 0;

  // Look for a free slot in vartable. (Create the table the first time through).
  if ( ! varTablesize )
    {
      varTablesize = 2;
      vartable = (vartable_t *) Malloc((size_t)varTablesize * sizeof (vartable_t));
      if( vartable == NULL )
	{
          Message("varTablesize = %d", varTablesize);
	  SysError("Allocation of vartable failed");
	}

      for( unsigned i = 0; i < varTablesize; i++ )
        {
          vartable[i].param = UNDEF_PARAM;
          vartable[i].opt_grib_kvpair      = NULL;
          vartable[i].opt_grib_kvpair_size = 0;
          vartable[i].opt_grib_nentries    = 0;
        }
    }
  else
    {
      while( varID < varTablesize )
	{
	  if ( vartable[varID].param == UNDEF_PARAM ) break;
	  varID++;
	}
    }

  // If the table overflows, double its size.
  if ( varID == varTablesize )
    {
      vartable = (vartable_t *) Realloc(vartable, (size_t)(varTablesize *= 2)
                                        * sizeof (vartable_t));
      for ( size_t i = varID; i < varTablesize; i++ )
        {
          vartable[i].param = UNDEF_PARAM;
          vartable[i].opt_grib_kvpair      = NULL;
          vartable[i].opt_grib_kvpair_size = 0;
          vartable[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(vartable_t *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;
  }

  return CDI_UNDEFID;
}


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)
{
  unsigned varID = (CDI_Split_Ltype105 != 1 || zaxistype != ZAXIS_HEIGHT) ?
    varGetEntry(param, gridID, zaxistype, ltype1, tsteptype, name, scanKeys, tiles) : (unsigned) CDI_UNDEFID;

  if ( varID == (unsigned) CDI_UNDEFID )
    {
      varTableUsed++;
      varID = paramNewEntry(param);
      vartable[varID].gridID     = gridID;
      vartable[varID].zaxistype  = zaxistype;
      vartable[varID].ltype1     = ltype1;
      vartable[varID].ltype2     = ltype2;
      vartable[varID].lbounds    = lbounds;
      vartable[varID].level_sf   = level_sf;
      vartable[varID].level_unit = level_unit;
      vartable[varID].tsteptype  = tsteptype;
      if (scanKeys) vartable[varID].scanKeys = *scanKeys;

      if ( name && name[0] )         vartable[varID].name     = strdup(name);
    }
  else
    {
      char paramstr[32];
      cdiParamToString(param, paramstr, sizeof(paramstr));

      if ( vartable[varID].gridID != gridID )
	{
	  Message("param = %s gridID = %d", paramstr, gridID);
	  Error("horizontal grid must not change for same parameter!");
	}
      if ( vartable[varID].zaxistype != zaxistype )
	{
	  Message("param = %s zaxistype = %d", paramstr, zaxistype);
	  Error("zaxistype must not change for same parameter!");
	}
    }

  if ( prec > vartable[varID].prec ) vartable[varID].prec = prec;

  // append current tile to tile subtype info.
  const int this_tile = varInsertTileSubtype(&vartable[varID], tiles);
  int tileID = tileGetEntry(varID, this_tile);
  if ( tile_index ) (*tile_index) = this_tile;
  if ( tileID == CDI_UNDEFID )
    {
      tileID = tileNewEntry((int)varID);
      vartable[varID].recordTable[tileID].subtypeIndex = this_tile;
      vartable[varID].nsubtypes++;
    }

  // append current level to level table info
  const 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);
  vartable[varID].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(&vartable[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)
{
  const int vlistID = streamptr->vlistID;

  int *varids = (int *) Malloc(varTableUsed*sizeof(int));
  for ( size_t varID = 0; varID < varTableUsed; varID++ ) varids[varID] = (int)varID;
  /*
  if ( streamptr->sortname )
    {
      size_t varID;
      for ( varID = 0; varID < varTableUsed; varID++ )
        if (!vartable[varID].name) break;

      if ( varID == varTableUsed )
        {
          struct cdi_generate_varinfo *varInfo
            = (struct cdi_generate_varinfo *) Malloc((size_t)varTableUsed * sizeof(struct cdi_generate_varinfo));

          for ( size_t 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 ( size_t varID = 0; varID < varTableUsed; varID++ )
            {
              varids[varID] = varInfo[varID].varid;
            }
          Free(varInfo);
        }
    }
  */
  for ( size_t index = 0; index < varTableUsed; index++ )
    {
      const int varid      = varids[index];

      const int gridID     = vartable[varid].gridID;
      const int param      = vartable[varid].param;
      const int ltype1     = vartable[varid].ltype1;
      const int ltype2     = vartable[varid].ltype2;
      int zaxistype        = vartable[varid].zaxistype;
      if ( ltype1 == 0 && zaxistype == ZAXIS_GENERIC && cdiDefaultLeveltype != -1 )
	zaxistype = cdiDefaultLeveltype;
      const int lbounds    = vartable[varid].lbounds;
      const int prec       = vartable[varid].prec;
      int instID           = vartable[varid].instID;
      int modelID          = vartable[varid].modelID;
      int tableID          = vartable[varid].tableID;
      const int tsteptype  = vartable[varid].tsteptype;
      const int comptype   = vartable[varid].comptype;

      double level_sf = 1;
      if ( vartable[varid].level_sf != 0 ) level_sf = 1./vartable[varid].level_sf;

      /* consistency check: test if all subtypes have the same levels: */
      const unsigned nlevels = vartable[varid].recordTable[0].nlevels;
      for ( int isub = 1; isub < vartable[varid].nsubtypes; isub++ ) {
        if ( vartable[varid].recordTable[isub].nlevels != nlevels )
          {
            fprintf(stderr, "var \"%s\": isub = %d / %d :: "
                    "nlevels = %d, vartable[varid].recordTable[isub].nlevels = %d\n",
                    vartable[varid].name, isub, vartable[varid].nsubtypes,
                    nlevels, vartable[varid].recordTable[isub].nlevels);
            Error("zaxis size must not change for same parameter!");
          }

        const leveltable_t *t1 = vartable[varid].recordTable[isub-1].levelTable;
        const leveltable_t *t2 = vartable[varid].recordTable[isub  ].levelTable;
        for ( unsigned 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",
                      vartable[varid].name, varid, isub, vartable[varid].nsubtypes,
                      nlevels, vartable[varid].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 = vartable[varid].recordTable[0].levelTable;

      if ( ltype1 == 0 && zaxistype == ZAXIS_GENERIC && nlevels == 1 && levelTable[0].level1 == 0 )
	zaxistype = ZAXIS_SURFACE;

      double *dlevels = (double *) Malloc(nlevels*sizeof(double));

      /*
      if ( lbounds && zaxistype != ZAXIS_HYBRID && zaxistype != ZAXIS_HYBRID_HALF )
	for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
	  dlevels[levelID] = (level_sf*levelTable[levelID].level1 + level_sf*levelTable[levelID].level2) / 2.0;
      else
      */
	for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
	  dlevels[levelID] = level_sf*levelTable[levelID].level1;

      if ( nlevels > 1 )
	{
          bool linc = true, ldec = true, lsort = false;
          for ( unsigned 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, 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, nlevels, sizeof(leveltable_t), cmpLevelTable);
              lsort = true;
            }

          if ( lsort )
            {
              /*
              if ( lbounds && zaxistype != ZAXIS_HYBRID && zaxistype != ZAXIS_HYBRID_HALF )
                for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
                  dlevels[levelID] = (level_sf*levelTable[levelID].level1 + level_sf*levelTable[levelID].level2) / 2.0;
              else
              */
                for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
                  dlevels[levelID] = level_sf*levelTable[levelID].level1;
            }
	}

      double *dlevels1 = NULL;
      double *dlevels2 = NULL;
      if ( lbounds )
	{
	  dlevels1 = (double *) Malloc(nlevels*sizeof(double));
	  for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
	    dlevels1[levelID] = level_sf*levelTable[levelID].level1;
	  dlevels2 = (double *) Malloc(nlevels*sizeof(double));
	  for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
	    dlevels2[levelID] = level_sf*levelTable[levelID].level2;
        }

      const char **cvals = NULL;
      const char *unitptr = cdiUnitNamePtr(vartable[varid].level_unit);
      int zaxisID = varDefZaxis(vlistID, zaxistype, (int)nlevels, dlevels, cvals, 0, lbounds, 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 ( lbounds ) Free(dlevels1);
      if ( lbounds ) Free(dlevels2);
      Free(dlevels);

      // define new subtype for tile set
      int tilesetID = CDI_UNDEFID;
      if ( vartable[varid].tiles ) tilesetID = vlistDefTileSubtype(vlistID, vartable[varid].tiles);

      // generate new variable
      int varID = stream_new_var(streamptr, gridID, zaxisID, tilesetID);
      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 ( vartable[varid].lmissval ) vlistDefVarMissval(vlistID, varID, vartable[varid].missval);
      if ( vartable[varid].name ) cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, vartable[varid].name);

      vlist_t *vlistptr = vlist_to_pointer(vlistID);
      for ( int i = 0; i < vartable[varid].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;
          const int idx = vlistptr->vars[varID].opt_grib_nentries-1;

          vlistptr->vars[varID].opt_grib_kvpair[idx] = vartable[varid].opt_grib_kvpair[i];
          vlistptr->vars[varID].opt_grib_kvpair[idx].keyword = NULL;
	  if ( vartable[varid].opt_grib_kvpair[i].keyword )
	    vlistptr->vars[varID].opt_grib_kvpair[idx].keyword =
	      strdupx(vartable[varid].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 ( size_t index = 0; index < varTableUsed; index++ )
    {
      int varid = varids[index];
      unsigned nlevels = vartable[varid].recordTable[0].nlevels;

      unsigned nsub = vartable[varid].nsubtypes >= 0 ? (unsigned)vartable[varid].nsubtypes : 0U;
      for ( size_t isub = 0; isub < nsub; isub++ )
        {
          sleveltable_t *restrict streamRecordTable = streamptr->vars[index].recordTable + isub;
          leveltable_t *restrict vartableLevelTable = vartable[varid].recordTable[isub].levelTable;
          for ( unsigned levelID = 0; levelID < nlevels; levelID++ )
            {
              streamRecordTable->recordID[levelID] = vartableLevelTable[levelID].recID;
              unsigned lindex;
              for ( lindex = 0; lindex < nlevels; lindex++ )
                if ( levelID == (unsigned)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 zaxisCompare(int zaxisID, int zaxistype, int nlevels, bool lbounds, const double *levels, 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 ltype1_is_equal = (ltype1 == ltype1_0);
  bool ltype2_is_equal = (ltype2 == ltype2_0);

  if ( ltype1_is_equal && ltype2_is_equal && (zaxistype == zaxisInqType(zaxisID) || zaxistype == ZAXIS_GENERIC) )
    {
      bool zlbounds = (zaxisInqLbounds(zaxisID, NULL) > 0);
      if ( nlevels == zaxisInqSize(zaxisID) && zlbounds == lbounds )
	{
	  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 )
	    {
              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] && (strcmp(longname, zlongname) != 0) ) 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] && (strcmp(units, zunits) != 0) ) differ = true;
                }
            }
	}
    }

  return differ;
}

struct varDefZAxisSearchState
{
  int resIDValue;
  int zaxistype;
  int nlevels;
  bool lbounds;
  const double *levels;
  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 ( zaxisCompare(id, state->zaxistype, state->nlevels, state->lbounds,
                    state->levels, 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, bool lbounds,
                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 ( !zaxisCompare(zaxisID, zaxistype, nlevels, lbounds, levels, longname, units, ltype1, ltype2) )
	  {
	    zaxisdefined = true;
	    break;
	  }
      }

  if ( ! zaxisdefined )
    {
      struct varDefZAxisSearchState query;
      query.zaxistype = zaxistype;
      query.nlevels = nlevels;
      query.levels = levels;
      query.lbounds = lbounds;
      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 ( lbounds )
	    {
	      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)
{
  vartable[varID].lmissval = true;
  vartable[varID].missval = missval;
}


void varDefCompType(int varID, int comptype)
{
  if ( vartable[varID].comptype == CDI_COMPRESS_NONE )
    vartable[varID].comptype = comptype;
}


void varDefCompLevel(int varID, int complevel)
{
  vartable[varID].complevel = complevel;
}


int varInqInst(int varID)
{
  return vartable[varID].instID;
}


void varDefInst(int varID, int instID)
{
  vartable[varID].instID = instID;
}


int varInqModel(int varID)
{
  return vartable[varID].modelID;
}


void varDefModel(int varID, int modelID)
{
  vartable[varID].modelID = modelID;
}


int varInqTable(int varID)
{
  return vartable[varID].tableID;
}


void varDefTable(int varID, int tableID)
{
  vartable[varID].tableID = tableID;
}


void varDefKeyInt(int varID, int key, int value)
{
  cdi_keys_t *keysp = &(vartable[varID].keys);
  cdiDefVarKeyInt(keysp, key, value);
}


void varDefKeyBytes(int varID, int key, const unsigned char *bytes, int length)
{
  cdi_keys_t *keysp = &(vartable[varID].keys);
  cdiDefVarKeyBytes(keysp, key, bytes, length);
}


void varDefKeyString(int varID, int key, const char *string)
{
  int length = strlen(string) + 1;
  cdi_keys_t *keysp = &(vartable[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(vartable_t *var, int nentries)
{
  if (var->opt_grib_kvpair_size >= nentries)
    {
      return;   /* nothing to do; array is still large enough */
    }
  else
    {
      if ( CDI_Debug )
        Message("resize data structure, %d -> %d", var->opt_grib_kvpair_size, nentries);

      int i, new_size;
      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 (i=0; i<var->opt_grib_kvpair_size; i++) {
        tmp[i] = var->opt_grib_kvpair[i];
      }
      for (i=var->opt_grib_kvpair_size; i<new_size; i++) {
        tmp[i].int_val =     0;
        tmp[i].dbl_val =     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)
{
  int idx = -1;
  for (int i=0; i<vartable[varID].opt_grib_nentries; i++)
    {
      if ( (strcmp(keyword, vartable[varID].opt_grib_kvpair[i].keyword) == 0 ) &&
           (vartable[varID].opt_grib_kvpair[i].data_type == t_int)             &&
           (vartable[varID].opt_grib_kvpair[i].subtype_index == tile_index) )
        idx = i;
    }

  if (idx == -1)
    {
      resize_vartable_opt_grib_entries(&vartable[varID], vartable[varID].opt_grib_nentries+1);
      vartable[varID].opt_grib_nentries += 1;
      idx = vartable[varID].opt_grib_nentries -1;
    }
  else
    {
      if (vartable[varID].opt_grib_kvpair[idx].keyword)
        Free(vartable[varID].opt_grib_kvpair[idx].keyword);
    }
  vartable[varID].opt_grib_kvpair[idx].data_type     = t_int;
  vartable[varID].opt_grib_kvpair[idx].int_val       = (int) lval;
  vartable[varID].opt_grib_kvpair[idx].keyword       = strdupx(keyword);
  vartable[varID].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)
{
  int idx = -1;
  for (int i=0; i<vartable[varID].opt_grib_nentries; i++)
    {
      if ( (strcmp(keyword, vartable[varID].opt_grib_kvpair[i].keyword) == 0 ) &&
           (vartable[varID].opt_grib_kvpair[i].data_type == t_double)          &&
           (vartable[varID].opt_grib_kvpair[i].subtype_index == tile_index) )
        idx = i;
    }

  if (idx == -1)
    {
      resize_vartable_opt_grib_entries(&vartable[varID], vartable[varID].opt_grib_nentries+1);
      vartable[varID].opt_grib_nentries += 1;
      idx = vartable[varID].opt_grib_nentries -1;
    }
  else
    {
      if (vartable[varID].opt_grib_kvpair[idx].keyword)
        Free(vartable[varID].opt_grib_kvpair[idx].keyword);
    }
  vartable[varID].opt_grib_kvpair[idx].data_type     = t_double;
  vartable[varID].opt_grib_kvpair[idx].dbl_val       = dval;
  vartable[varID].opt_grib_kvpair[idx].keyword       = strdupx(keyword);
  vartable[varID].opt_grib_kvpair[idx].subtype_index = tile_index;
}
#endif


#ifdef HAVE_LIBGRIB_API
int varOptGribNentries(int varID)
{
  int nentries = vartable[varID].opt_grib_nentries;
  return 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:
 */
#ifdef  HAVE_CONFIG_H
#endif



#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


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);

#if !defined(__cplusplus)
const
#endif
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(vlist_t *vlistptr)
{
  int idx = vlistptr->self;

  reshRemove(idx, &vlistOps );

  Free(vlistptr);

  if ( VLIST_Debug )
    Message("Removed idx %d from vlist list", idx);
}

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);

  vlist_delete_entry(vlistptr);
}


/*
@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)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if ( vlistptr->internal )
    Warning("Attempt to destroy an internal vlist object by the user (vlistID=%d).", vlistID);
  else
    vlist_delete(vlistptr);
}

// destroy an internal vlist object
void cdiVlistDestroy_(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if(!vlistptr->internal)
    Warning("Destroying a vlist object that is owned by the user.\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.");

  vlist_delete(vlistptr);
}

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 = strdupx(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  vlistID2  Target variable list ID.
    @Item  vlistID1  Source variable list ID.

@Description
The function @func{vlistCopy} copies all entries from vlistID1 to vlistID2.

@EndFunction
*/
void vlistCopy(int vlistID2, int vlistID1)
{
  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(vlistIDnew, vlistID);
  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;
  bool lbounds;
  const double *levels;
};

static enum cdiApplyRet
vgzZAxisSearch(int id, void *res, void *data)
{
  struct vgzSearchState *state = (struct vgzSearchState *)data;
  (void)res;
  if (zaxisCompare(id, state->zaxistype, state->nlevels, state->lbounds,
                   state->levels, NULL, NULL, 0, -1)
      == false)
    {
      state->resIDValue = id;
      return CDI_APPLY_STOP;
    }
  else
    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;
  bool has_bounds = false;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int nzaxis = vlistptr->nzaxis;

  if ( lbounds && ubounds ) has_bounds = true;

  for ( int index = 0; index < nzaxis; ++index )
    {
      zaxisID = vlistptr->zaxisIDs[index];

      if ( zaxisCompare(zaxisID, zaxistype, nlevels, has_bounds, levels, NULL, NULL, 0, -1) == false )
        {
          zaxisdefined = true;
          break;
        }
    }

  if ( ! zaxisdefined )
    {
      struct vgzSearchState query;
      query.zaxistype = zaxistype;
      query.nlevels = nlevels;
      query.levels = levels;
      query.lbounds = has_bounds;

      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 ( has_bounds )
	    {
	      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;
}

/*
@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;
      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;
	      }

	    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++ )
	{
          size_t ngp1 = gridInqSize(vars1[varID].gridID);
          size_t 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 ( strcmp(name1, name2) != 0 ) 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 ( 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 vlistNrecs(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)
{
  int number, number2;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int datatype = vlistptr->vars[0].datatype;
  if (  datatype== CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64 )
    number = CDI_COMP;
  else
    number = CDI_REAL;

  for ( int varID = 1; varID < vlistptr->nvars; varID++ )
    {
      datatype = vlistptr->vars[varID].datatype;
      if ( datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64 )
        number2 = CDI_COMP;
      else
        number2 = CDI_REAL;

      if ( number2 != number )
        {
          number = CDI_BOTH;
          break;
        }
    }

  return number;
}

/*
@Function  vlistNgrids
@Title     Number of grids in a variable list

@Prototype int vlistNgrids(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistNgrids} returns the number of grids in the variable list vlistID.

@Result
@func{vlistNgrids} returns the number of grids in a variable list.

@EndFunction
*/
int vlistNgrids(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  return vlistptr->ngrids;
}

/*
@Function  vlistNzaxis
@Title     Number of zaxis in a variable list

@Prototype int vlistNzaxis(int vlistID)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.

@Description
The function @func{vlistNzaxis} returns the number of zaxis in the variable list vlistID.

@Result
@func{vlistNzaxis} returns the number of zaxis in a variable list.

@EndFunction
*/
int vlistNzaxis(int vlistID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  return vlistptr->nzaxis;
}


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)
{
  const int vlistID = vlistptr->self;
  fprintf ( fp, "#\n# vlistID %d\n#\n", vlistID);

  const 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 iorank 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;
          int iorank = vlistptr->vars[varID].iorank;

          char paramstr[32];
          cdiParamToString(param, paramstr, sizeof(paramstr));
          fprintf(fp, "%6d %-8s %6d  %6d  %6d  %6d  %5d %6d %-8s %s [%s]\n",
                  varID, paramstr, gridID, zaxisID, subtypeID, tsteptype, flag, iorank, 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 iorank\n", fp);
      for ( int varID = 0; varID < nvars; varID++ )
        fprintf(fp, "%3d %8zu %6d\n", varID,
                zaxisInqSize(vlistptr->vars[varID].zaxisID) * gridInqSize(vlistptr->vars[varID].gridID),
                vlistptr->vars[varID].iorank);
    }
}


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;
}


size_t vlistGridsizeMax(int vlistID)
{
  size_t gridsizemax = 0;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for ( int index = 0 ; index < vlistptr->ngrids ; index++ )
    {
      int gridID = vlistptr->gridIDs[index];
      size_t 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;
      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;
      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;
            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;

        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) )
    {
      for ( int varID = 0; varID <  vlistptr->nvars; varID++ )
        if ( vlistptr->vars[varID].timetype != TIME_CONSTANT )
          {
            hastime = true;
            break;
          }
    }

  return (int)hastime;
}

enum {
  vlist_nints=6,
};

static int vlistTxCode ( void )
{
  return VLIST;
}


static
int  vlistGetSizeP ( void * vlistptr, void *context)
{
  vlist_t *p = (vlist_t*) vlistptr;
  int txsize = serializeGetSize(vlist_nints, 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[vlist_nints];
  vlist_t *p = (vlist_t*) vlistptr;
  tempbuf[0] = p->self;
  tempbuf[1] = p->nvars;
  tempbuf[2] = p->taxisID;
  tempbuf[3] = p->tableID;
  tempbuf[4] = p->instID;
  tempbuf[5] = p->modelID;
  serializePack(tempbuf, vlist_nints, 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);
    }
}

void vlistUnpack(char * buf, int size, int *position, int originNamespace,
                 void *context, int force_id)
{
  int tempbuf[vlist_nints];
  serializeUnpack(buf, size, position, tempbuf, vlist_nints, CDI_DATATYPE_INT, context);
  int nvars = tempbuf[1];
  int targetID = namespaceAdaptKey(tempbuf[0], originNamespace);
  vlist_t *p = vlist_new_entry(force_id?targetID:CDI_UNDEFID);
  xassert(!force_id || p->self == targetID);
  if (!force_id)
    targetID = p->self;
  cdiVlistMakeInternal(p->self);
  p->taxisID = namespaceAdaptKey(tempbuf[2], originNamespace);
  p->tableID = tempbuf[3];
  p->instID = namespaceAdaptKey(tempbuf[4], originNamespace);
  p->modelID = namespaceAdaptKey(tempbuf[5], originNamespace);
  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);
}


void vlist_check_contents(int vlistID)
{
  int nzaxis = vlistNzaxis(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 */
    }
  else
    {
      if ( CDI_Debug )
        Message("resize data structure, %d -> %d", var->opt_grib_kvpair_size, nentries);

      int i, new_size;
      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 (i=0; i<var->opt_grib_kvpair_size; i++) {
        tmp[i] = var->opt_grib_kvpair[i];
      }
      for (i=var->opt_grib_kvpair_size; i<new_size; i++) {
        tmp[i].int_val =     0;
        tmp[i].dbl_val =     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:
 */


static
cdi_keys_t *vlist_get_keysp(vlist_t *vlistptr, int varID)
{
  if      (varID == CDI_GLOBAL) return &vlistptr->keys;
  else 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;
  else if (varID == CDI_XAXIS)  return &gridptr->x.keys;
  else if (varID == CDI_YAXIS)  return &gridptr->y.keys;

  return NULL;
}

static
cdi_keys_t *zaxis_get_keysp(zaxis_t *zaxisptr, int varID)
{
  if (varID == CDI_GLOBAL) return &zaxisptr->keys;

  return 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->length = 0;
  keyp->type = 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 ( size_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 ( size_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)
{
  if      ( reshGetTxCode(objID) == GRID )  return grid_get_keysp(grid_to_pointer(objID), varID);
  else if ( reshGetTxCode(objID) == ZAXIS ) return zaxis_get_keysp(zaxis_to_pointer(objID), varID);
  else if ( reshGetTxCode(objID) == 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->type == KEY_BYTES)
    {
      if (keypa->length != keypb->length) return 1;
      return memcmp(keypa->v.s, keypb->v.s, keypa->length);
    }
  else if (keypa->type == KEY_FLOAT)
    {
      if (IS_NOT_EQUAL(keypa->v.d, keypb->v.d)) return 1;
    }
  else if (keypa->type == KEY_INT)
    {
      if (keypa->v.i != keypb->v.i) return 1;
    }

  return 0;
}


void cdiDeleteVarKeys(cdi_keys_t *keysp)
{
  for ( int keyid = 0; keyid < (int)keysp->nelems; keyid++ )
    {
      cdi_key_t *keyp = &(keysp->value[keyid]);
      if ( keyp->length )
        {
          free(keyp->v.s);
          keyp->v.s = NULL;
          keyp->length = 0;
        }
    }

  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)
{
  for ( int keyid = 0; keyid < (int)keysp->nelems; keyid++ )
    {
      cdi_key_t *keyp = &(keysp->value[keyid]);
      if (keyp->type == KEY_BYTES)
        {
          printf("%d key %d length %d value %s\n", keyid+1, keyp->key, keyp->length,  keyp->v.s);
        }
      else if (keyp->type == KEY_FLOAT)
        {
          printf("%d key %d value %g\n", keyid+1, keyp->key, keyp->v.d);
        }
      else if (keyp->type == KEY_INT)
        {
          printf("%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;

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  const cdi_key_t *keyp = find_key(keysp, key);
  if ( keyp != NULL )
    {
      *length = keyp->length;
      if ( *length == 0 ) *length = 1;
      status = CDI_NOERR;
    }

  return status;
}

static
void cdi_define_key(const cdi_key_t *keyp, cdi_keys_t *keysp)
{
  if      ( keyp->type == KEY_INT )
    cdiDefVarKeyInt(keysp, keyp->key, keyp->v.i);
  else if ( keyp->type == KEY_FLOAT )
    cdiDefVarKeyFloat(keysp, keyp->key, keyp->v.d);
  else if ( keyp->type == KEY_BYTES )
    cdiDefVarKeyBytes(keysp, keyp->key, keyp->v.s, keyp->length);
}


int cdiDeleteKey(int cdiID, int varID, int key)
{
  int status = -1;

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdi_key_t *keyp = find_key(keysp, key);
  if ( keyp != NULL ) // key in use
    {
      if ( keyp->length )
        {
          free(keyp->v.s);
          keyp->v.s = NULL;
          keyp->length = 0;
        }
    }

  return status;
}


void cdiCopyVarKeys(const cdi_keys_t *keysp1, cdi_keys_t *keysp2)
{
  for ( size_t keyid = 0; keyid < keysp1->nelems; keyid++ )
    {
      const cdi_key_t *keyp = &(keysp1->value[keyid]);
      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;

  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 = KEY_INT;
          keyp->v.i = value;
        }
    }
}

/*
@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);

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdi_key_t *keyp = find_key(keysp, key);
  if ( keyp != NULL ) // key in use
    {
      if ( keyp->type == KEY_INT )
	{
          *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 == KEY_INT ) 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 )
    {
      //if ( keyp->v.i != value )
        {
          keyp->type = KEY_INT;
          keyp->v.d = value;
        }
    }
}

/*
@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);

  cdi_keys_t *keysp = cdi_get_keysp(cdiID, varID);
  xassert(keysp != NULL);

  cdi_key_t *keyp = find_key(keysp, key);
  if ( keyp != NULL ) // key in use
    {
      if ( keyp->type == KEY_FLOAT )
	{
          *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);

  if ( keyp != NULL )
    {
      if ( keyp->length != 0 && keyp->length != length )
        {
          free(keyp->v.s);
          keyp->length = 0;
        }
      if ( keyp->length == 0 )
        {
          keyp->v.s = (unsigned char *) malloc((size_t) length);
          keyp->length = length;
        }

      memcpy(keyp->v.s, bytes, length);
      keyp->type = KEY_BYTES;
    }
}

/*
@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);
  if ( keyp != NULL ) // key in use
    {
      if ( keyp->type == KEY_BYTES )
	{
          if ( keyp->length < *length ) *length = keyp->length;
          memcpy(bytes, keyp->v.s, *length);
          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);

  int length = strlen(string) + 1;
  int status = cdiDefKeyBytes(cdiID, varID, key, (const unsigned char *) string, 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 (status != CDI_NOERR) *length = 0;
  else string[maxlength-1] = '\0';

  return status;
}

const char *cdiInqVarKeyStringPtr(cdi_keys_t *keysp, int key)
{
  const cdi_key_t *keyp = find_key(keysp, key);
  if ( keyp != NULL ) // key in use
    {
      if ( keyp->type == KEY_BYTES ) return (const char *)keyp->v.s;
    }

  return NULL;
}

void cdiInitKeys(cdi_keys_t *keysp)
{
  keysp->nalloc = MAX_KEYS;
  keysp->nelems = 0;
  for ( int 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:
 */
#include <assert.h>
#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 && strcmp(attp->name, name) == 0) 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 && strcmp(attp->name, name) == 0) type = attp->exdtype;
    }

  return type;
}


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]);
      if ( attp->name   )
        {
          Free(attp->name);
          attp->name = NULL;
          attp->namesz = 0;
        }
      if ( attp->xvalue ) Free(attp->xvalue);
    }

  attsp->nelems = 0;

  return status;
}


int cdiDelAtt(int cdiID, int varID, const char *name)
{
  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]);
      if (attp->name && strcmp(attp->name, name) == 0)
        {
          Free(attp->name);
          attp->name = NULL;
          attp->namesz = 0;
          if ( attp->xvalue )
            {
              Free(attp->xvalue);
              attp->xvalue = NULL;
            }
        }
    }

  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 ) // Null arg
    return CDI_EINVAL;

  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 ) // Null arg
    return CDI_EINVAL;

  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:
 */


static
void vlistvarInitEntry(int vlistID, int varID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  vlistptr->vars[varID].fvarID        = varID;
  vlistptr->vars[varID].mvarID        = varID;
  vlistptr->vars[varID].flag          = 0;
  vlistptr->vars[varID].param         = 0;
  vlistptr->vars[varID].datatype      = CDI_UNDEFID;
  vlistptr->vars[varID].timetype      = CDI_UNDEFID;
  vlistptr->vars[varID].tsteptype     = TSTEP_INSTANT;
  vlistptr->vars[varID].chunktype     = CDI_Chunk_Type;
  vlistptr->vars[varID].xyz           = 321;
  vlistptr->vars[varID].gridID        = CDI_UNDEFID;
  vlistptr->vars[varID].zaxisID       = CDI_UNDEFID;
  vlistptr->vars[varID].subtypeID     = CDI_UNDEFID;
  vlistptr->vars[varID].instID        = CDI_UNDEFID;
  vlistptr->vars[varID].modelID       = CDI_UNDEFID;
  vlistptr->vars[varID].tableID       = CDI_UNDEFID;
  vlistptr->vars[varID].missvalused   = false;
  vlistptr->vars[varID].missval       = CDI_Default_Missval;
  vlistptr->vars[varID].addoffset     = 0.0;
  vlistptr->vars[varID].scalefactor   = 1.0;
  vlistptr->vars[varID].extra         = NULL;
  vlistptr->vars[varID].levinfo       = NULL;
  vlistptr->vars[varID].comptype      = CDI_COMPRESS_NONE;
  vlistptr->vars[varID].complevel     = 1;
  vlistptr->vars[varID].keys.nalloc   = MAX_KEYS;
  vlistptr->vars[varID].keys.nelems   = 0;
  for ( int i = 0; i < MAX_KEYS; ++i )
    vlistptr->vars[varID].keys.value[i].length = 0;
  vlistptr->vars[varID].atts.nalloc   = MAX_ATTRIBUTES;
  vlistptr->vars[varID].atts.nelems   = 0;
  vlistptr->vars[varID].lvalidrange   = false;
  vlistptr->vars[varID].validrange[0] = VALIDMISS;
  vlistptr->vars[varID].validrange[1] = VALIDMISS;
  vlistptr->vars[varID].iorank        = CDI_UNDEFID;
  vlistptr->vars[varID].opt_grib_kvpair_size = 0;
  vlistptr->vars[varID].opt_grib_kvpair      = NULL;
  vlistptr->vars[varID].opt_grib_nentries    = 0;
}

static
int vlistvarNewEntry(int vlistID)
{
  int varID = 0;
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  int vlistvarSize = vlistptr->varsAllocated;
  var_t *vlistvar = vlistptr->vars;
  // Look for a free slot in vlistvar. (Create the table the first time through).
  if ( ! vlistvarSize )
    {
      vlistvarSize = 2;
      vlistvar = (var_t *) Malloc((size_t)vlistvarSize * sizeof (var_t));
      for ( int i = 0; i < vlistvarSize; i++ ) vlistvar[i].isUsed = false;
    }
  else
    {
      while (varID < vlistvarSize && vlistvar[varID].isUsed)
        ++varID;
    }
  // If the table overflows, double its size.
  if ( varID == vlistvarSize )
    {
      vlistvar = (var_t *) Realloc(vlistvar, (size_t)(vlistvarSize *= 2) * sizeof(var_t));
      for ( int i = varID; i < vlistvarSize; i++ ) vlistvar[i].isUsed = false;
    }

  vlistptr->varsAllocated = vlistvarSize;
  vlistptr->vars          = vlistvar;

  vlistvarInitEntry(vlistID, varID);

  vlistptr->vars[varID].isUsed = true;

  return varID;
}

void vlistCheckVarID(const char *caller, int vlistID, int varID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if ( vlistptr == NULL ) Errorc("vlist undefined!");

  if ( varID < 0 || varID >= vlistptr->nvars ) Errorc("varID %d undefined!", varID);

  if ( ! vlistptr->vars[varID].isUsed ) Errorc("varID %d undefined!", 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);

  const int varID = vlistvarNewEntry(vlistID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  vlistptr->nvars++;
  vlistptr->vars[varID].gridID    = gridID;
  vlistptr->vars[varID].zaxisID   = zaxisID;
  vlistptr->vars[varID].timetype = timetype;
  vlistptr->vars[varID].subtypeID = tilesetID;

  if ( timetype < 0 )
    {
      Message("Unexpected time type %d, set to TIME_VARYING!", timetype);
      vlistptr->vars[varID].timetype = TIME_VARYING;
    }

  vlistAdd2GridIDs(vlistptr, gridID);
  vlistAdd2ZaxisIDs(vlistptr, zaxisID);
  vlistAdd2SubtypeIDs(vlistptr, tilesetID);

  vlistptr->vars[varID].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)
{
  xassert(varID >= 0 && varID < vlistptr->nvars && vlistptr->vars[varID].levinfo == NULL);

  const int zaxisID = vlistptr->vars[varID].zaxisID;
  const size_t nlevs = (size_t)zaxisInqSize(zaxisID);

  vlistptr->vars[varID].levinfo = (levinfo_t*) Malloc(nlevs * sizeof(levinfo_t));

  for (size_t levID = 0; levID < nlevs; levID++ )
    vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].param != param)
    {
      vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  const int param = vlistptr->vars[varID].param;
  int pnum, pcat, pdis;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);
  const int newParam = cdiEncodeParam(code, pcat, pdis);
  if (vlistptr->vars[varID].param != newParam)
    {
      vlistptr->vars[varID].param = newParam;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


void vlistInqVar(int vlistID, int varID, int *gridID, int *zaxisID, int *timetype)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  *gridID   = vlistptr->vars[varID].gridID;
  *zaxisID  = vlistptr->vars[varID].zaxisID;
  *timetype = vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  const int param = vlistptr->vars[varID].param;
  int pdis, pcat, pnum;
  cdiDecodeParam(param, &pnum, &pcat, &pdis);
  int code = pnum;
  if ( pdis != 255 ) code = -varID-1; // GRIB2 Parameter

  int tableID = vlistptr->vars[varID].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;
  (void)cdiInqKeyString(vlistID, varID, CDI_KEY_NAME, name, &length);

  if (!name[0])
    {
      vlistCheckVarID(__func__, vlistID, varID);

      vlist_t *vlistptr = vlist_to_pointer(vlistID);

      int param = vlistptr->vars[varID].param;
      int pdis, pcat, pnum;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      if ( pdis == 255 )
	{
	  int code = pnum;
	  int tableID = vlistptr->vars[varID].tableID;
          tableInqEntry(tableID, code, -1, name, NULL, NULL);
	  if (!name[0]) sprintf(name, "var%d", code);
	}
      else
	{
	  sprintf(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);

  vlistCheckVarID(__func__, vlistID, varID);
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  // Otherwise we check if we should use the table of parameter descriptions.
  int param = vlistptr->vars[varID].param;
  int discipline, category, number;
  cdiDecodeParam(param, &number, &category, &discipline);
  char *result = NULL;
  if (discipline == 255)
    {
      int tableID = vlistptr->vars[varID].tableID;
      tableInqEntry(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>".
          result = (char *) Malloc(3 + 3 * sizeof (int) * CHAR_BIT / 8 + 2);
          sprintf(result, "var%d", number);
        }
    }
  else
    {
      result = (char *) Malloc(5 + 2 + 3 * (3 * sizeof (int) * CHAR_BIT + 1) + 1);
      sprintf(result, "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;
  (void)cdiInqKeyString(vlistID, varID, CDI_KEY_LONGNAME, longname, &length);

  if (!longname[0])
    {
      vlistCheckVarID(__func__, vlistID, varID);

      vlist_t *vlistptr = vlist_to_pointer(vlistID);

      int param = vlistptr->vars[varID].param;
      int pdis, pcat, pnum;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      if ( pdis == 255 )
	{
	  int code = pnum;
          int tableID = vlistptr->vars[varID].tableID;
          tableInqEntry(tableID, code, -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);
}

/*
@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;
  (void)cdiInqKeyString(vlistID, varID, CDI_KEY_UNITS, units, &length);

  if (!units[0])
    {
      vlistCheckVarID(__func__, vlistID, varID);

      vlist_t *vlistptr = vlist_to_pointer(vlistID);

      int param = vlistptr->vars[varID].param;
      int pdis, pcat, pnum;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      if ( pdis == 255 )
	{
	  int code = pnum;
	  int tableID = vlistptr->vars[varID].tableID;
          tableInqEntry(tableID, code, -1, NULL, NULL, units);
	}
    }
}

// used in MPIOM !
int vlistInqVarID(int vlistID, int code)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  for ( int varID = 0; varID < vlistptr->nvars; varID++ )
    {
      const int param = vlistptr->vars[varID].param;
      int pdis, pcat, pnum;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      if ( pnum == code ) return varID;
    }

  return CDI_UNDEFID;
}


size_t vlistInqVarSize(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  int zaxisID, gridID, timetype;
  vlistInqVar(vlistID, varID, &gridID, &zaxisID, &timetype);

  const size_t nlevs = (size_t)zaxisInqSize(zaxisID);
  const size_t 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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].datatype;
}


int vlistInqVarNumber(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  int number = CDI_REAL;
  if ( vlistptr->vars[varID].datatype == CDI_DATATYPE_CPX32 ||
       vlistptr->vars[varID].datatype == CDI_DATATYPE_CPX64 )
    number = CDI_COMP;

  return number;
}

/*
@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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->vars[varID].datatype != datatype)
    {
      vlistptr->vars[varID].datatype = datatype;

      if ( !vlistptr->vars[varID].missvalused )
        switch (datatype)
          {
          case CDI_DATATYPE_INT8:   vlistptr->vars[varID].missval = -SCHAR_MAX; break;
          case CDI_DATATYPE_UINT8:  vlistptr->vars[varID].missval =  UCHAR_MAX; break;
          case CDI_DATATYPE_INT16:  vlistptr->vars[varID].missval = -SHRT_MAX;  break;
          case CDI_DATATYPE_UINT16: vlistptr->vars[varID].missval =  USHRT_MAX; break;
          case CDI_DATATYPE_INT32:  vlistptr->vars[varID].missval = -INT_MAX;   break;
          case CDI_DATATYPE_UINT32: vlistptr->vars[varID].missval =  UINT_MAX;  break;
          case CDI_DATATYPE_FLT32:  vlistptr->vars[varID].missval =  (float) CDI_Default_Missval;  break;
          }
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


void vlistDefVarInstitut(int vlistID, int varID, int instID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].instID != instID)
    {
      vlistptr->vars[varID].instID = instID;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarInstitut(int vlistID, int varID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].instID;
}


void vlistDefVarModel(int vlistID, int varID, int modelID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].modelID != modelID)
    {
      vlistptr->vars[varID].modelID = modelID;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarModel(int vlistID, int varID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].modelID;
}


void vlistDefVarTable(int vlistID, int varID, int tableID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->vars[varID].tableID != tableID)
    {
      vlistptr->vars[varID].tableID = tableID;
      const int tablenum = tableInqNum(tableID);

      const int param = vlistptr->vars[varID].param;
      int pnum, pcat, pdis;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      vlistptr->vars[varID].param = cdiEncodeParam(pnum, tablenum, pdis);
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarTable(int vlistID, int varID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].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)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  vlistptr->vars[varID].missval = missval;
  vlistptr->vars[varID].missvalused = true;
}

/*
@Function  vlistDefVarExtra
@Title     Define extra information of a Variable

@Prototype void vlistDefVarExtra(int vlistID, int varID, const char *extra)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate}.
    @Item  varID    Variable identifier.
    @Item  extra    Extra information.

@Description
The function @func{vlistDefVarExtra} defines the extra information of a variable.

@EndFunction
*/
void vlistDefVarExtra(int vlistID, int varID, const char *extra)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if ( extra )
    {
      if ( vlistptr->vars[varID].extra )
	{
	  Free(vlistptr->vars[varID].extra);
	  vlistptr->vars[varID].extra = NULL;
	}

      vlistptr->vars[varID].extra = strdupx(extra);
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}

/*
@Function  vlistInqVarExtra
@Title     Get extra information of a Variable

@Prototype void vlistInqVarExtra(int vlistID, int varID, char *extra)
@Parameter
    @Item  vlistID  Variable list ID, from a previous call to @fref{vlistCreate} or @fref{streamInqVlist}.
    @Item  varID    Variable identifier.
    @Item  extra    Returned variable extra information. 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{vlistInqVarExtra} returns the extra information of a variable.

@Result
@func{vlistInqVarExtra} returns the extra information of the variable to the parameter extra if available,
otherwise the result is an empty string.

@EndFunction
*/
void vlistInqVarExtra(int vlistID, int varID, char *extra)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if ( vlistptr->vars[varID].extra == NULL )
    sprintf(extra, "-");
  else
    strcpy(extra, vlistptr->vars[varID].extra);
}


int vlistInqVarValidrange(int vlistID, int varID, double *validrange)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if ( validrange != NULL && vlistptr->vars[varID].lvalidrange )
    {
      validrange[0] = vlistptr->vars[varID].validrange[0];
      validrange[1] = vlistptr->vars[varID].validrange[1];
    }

  return (int)vlistptr->vars[varID].lvalidrange;
}


void vlistDefVarValidrange(int vlistID, int varID, const double *validrange)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  vlistptr->vars[varID].validrange[0] = validrange[0];
  vlistptr->vars[varID].validrange[1] = validrange[1];
  vlistptr->vars[varID].lvalidrange = true;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}


double vlistInqVarScalefactor(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].scalefactor;
}


double vlistInqVarAddoffset(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].addoffset;
}


void vlistDefVarScalefactor(int vlistID, int varID, double scalefactor)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if ( IS_NOT_EQUAL(vlistptr->vars[varID].scalefactor, scalefactor) )
    {
      vlistptr->vars[varID].scalefactor = scalefactor;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


void vlistDefVarAddoffset(int vlistID, int varID, double addoffset)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if ( IS_NOT_EQUAL(vlistptr->vars[varID].addoffset, addoffset))
    {
      vlistptr->vars[varID].addoffset = addoffset;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


void vlistDefVarTimetype(int vlistID, int varID, int timetype)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].timetype != timetype)
    {
      vlistptr->vars[varID].timetype = timetype;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarTimetype(int vlistID, int varID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].timetype;
}


void vlistDefVarTsteptype(int vlistID, int varID, int tsteptype)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].tsteptype != tsteptype)
    {
      vlistptr->vars[varID].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)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].tsteptype;
}


int vlistInqVarMissvalUsed(int vlistID, int varID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return (int)vlistptr->vars[varID].missvalused;
}


void vlistDefFlag(int vlistID, int varID, int levID, int flag)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  levinfo_t li = DEFAULT_LEVINFO(levID);
  if (vlistptr->vars[varID].levinfo)
    ;
  else if (flag != li.flag)
    cdiVlistCreateVarLevInfo(vlistptr, varID);
  else
    return;

  vlistptr->vars[varID].levinfo[levID].flag = flag;

  vlistptr->vars[varID].flag = 0;

  int nlevs = zaxisInqSize(vlistptr->vars[varID].zaxisID);
  for ( int levelID = 0; levelID < nlevs; levelID++ )
    {
      if ( vlistptr->vars[varID].levinfo[levelID].flag )
        {
          vlistptr->vars[varID].flag = 1;
          break;
        }
    }

  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}


int vlistInqFlag(int vlistID, int varID, int levID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->vars[varID].levinfo)
    return vlistptr->vars[varID].levinfo[levID].flag;
  else
    {
      levinfo_t li = DEFAULT_LEVINFO(levID);
      return li.flag;
    }
}


int vlistFindVar(int vlistID, int fvarID)
{
  int varID;
  const 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;
      Message("varID not found for fvarID %d in vlistID %d!", fvarID, vlistID);
    }

  return varID;
}


int vlistFindLevel(int vlistID, int fvarID, int flevelID)
{
  int levelID = -1;
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  const int varID = vlistFindVar(vlistID, fvarID);

  if ( varID != -1 )
    {
      const int nlevs = zaxisInqSize(vlistptr->vars[varID].zaxisID);
      for ( levelID = 0; levelID < nlevs; levelID++ )
	{
	  if ( vlistptr->vars[varID].levinfo[levelID].flevelID == flevelID ) break;
	}

      if ( levelID == nlevs )
	{
	  levelID = -1;
	  Message("levelID not found for fvarID %d and levelID %d in vlistID %d!",
		  fvarID, flevelID, vlistID);
	}
    }

  return levelID;
}


int vlistMergedVar(int vlistID, int varID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].mvarID;
}


int vlistMergedLevel(int vlistID, int varID, int levelID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->vars[varID].levinfo)
    return vlistptr->vars[varID].levinfo[levelID].mlevelID;
  else
    {
      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);

  levinfo_t li = DEFAULT_LEVINFO(levelID);
  if (vlistptr->vars[varID].levinfo)
    ;
  else if (index != li.index)
    cdiVlistCreateVarLevInfo(vlistptr, varID);
  else
    return;

  vlistptr->vars[varID].levinfo[levelID].index = index;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}


int vlistInqIndex(int vlistID, int varID, int levelID)
{
  const vlist_t *vlistptr = vlist_to_pointer(vlistID);

  if (vlistptr->vars[varID].levinfo)
    return vlistptr->vars[varID].levinfo[levelID].index;
  else
    {
      levinfo_t li = DEFAULT_LEVINFO(levelID);
      return li.index;
    }
}


void vlistChangeVarZaxis(int vlistID, int varID, int zaxisID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  vlistCheckVarID(__func__, vlistID, varID);

  const int nlevs1 = zaxisInqSize(vlistptr->vars[varID].zaxisID);
  const int nlevs2 = zaxisInqSize(zaxisID);

  if ( nlevs1 != nlevs2 ) Error("Number of levels must not change!");

  const int nvars = vlistptr->nvars;
  int found = 0;
  const int oldZaxisID = vlistptr->vars[varID].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)
    {
      const int nzaxis = vlistptr->nzaxis;
      for (int i = 0; i < nzaxis; ++i)
	if (vlistptr->zaxisIDs[i] == oldZaxisID )
	  vlistptr->zaxisIDs[i] = zaxisID;
    }
  else
    vlistAdd2ZaxisIDs(vlistptr, zaxisID);

  vlistptr->vars[varID].zaxisID = zaxisID;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}


void vlistChangeVarGrid(int vlistID, int varID, int gridID)
{
  vlist_t *vlistptr = vlist_to_pointer(vlistID);

  vlistCheckVarID(__func__, vlistID, varID);

  const 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);

  vlistptr->vars[varID].gridID = gridID;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}


void vlistDefVarCompType(int vlistID, int varID, int comptype)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].comptype != comptype)
    {
      vlistptr->vars[varID].comptype = comptype;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarCompType(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].comptype;
}


void vlistDefVarCompLevel(int vlistID, int varID, int complevel)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].complevel != complevel)
    {
      vlistptr->vars[varID].complevel = complevel;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarCompLevel(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].complevel;
}


void  vlistDefVarChunkType(int vlistID, int varID, int chunktype)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].chunktype != chunktype)
    {
      vlistptr->vars[varID].chunktype = chunktype;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarChunkType(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].chunktype;
}

static
int vlistEncodeXyz(int (*dimorder)[3])
{
  return (*dimorder)[0]*100 + (*dimorder)[1]*10 + (*dimorder)[2];
}


static
void vlistDecodeXyz(int xyz, int (*outDimorder)[3])
{
  (*outDimorder)[0] = xyz/100, xyz -= (*outDimorder)[0]*100;
  (*outDimorder)[1] = xyz/10, xyz -= (*outDimorder)[1]*10;
  (*outDimorder)[2] = xyz;
}


void vlistDefVarXYZ(int vlistID, int varID, int xyz)
{
  vlistCheckVarID(__func__, 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
      {
        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;}
        xyz = vlistEncodeXyz(&dimorder);
      }
  }

  assert(xyz == 123 || xyz == 312 || xyz == 231 || xyz == 321 || xyz == 132 || xyz == 213);

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  vlistptr->vars[varID].xyz = xyz;
  reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
}


void vlistInqVarDimorder(int vlistID, int varID, int (*outDimorder)[3])
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  vlistDecodeXyz(vlistptr->vars[varID].xyz, outDimorder);
}


int vlistInqVarXYZ(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].xyz;
}


void vlistDefVarIOrank(int vlistID, int varID, int iorank)
{
  vlistCheckVarID ( __func__, vlistID, varID );

  vlist_t *vlistptr = vlist_to_pointer(vlistID);
  if (vlistptr->vars[varID].iorank != iorank)
    {
      vlistptr->vars[varID].iorank = iorank;
      reshSetStatus(vlistID, &vlistOps, RESH_DESYNC_IN_USE);
    }
}


int vlistInqVarIOrank(int vlistID, int varID)
{
  vlistCheckVarID(__func__, vlistID, varID);

  const vlist_t *vlistptr = vlist_to_pointer(vlistID);
  return vlistptr->vars[varID].iorank;
}


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 = FCMP(fvarID) | FCMP(mvarID) | FCMP(flag) | FCMP(param)
    | FCMP(datatype) | FCMP(timetype) | FCMP(tsteptype)
    | FCMP(chunktype) | FCMP(xyz) | FCMP2(gridID) | FCMP2(zaxisID)
    | FCMP2(instID) | FCMP2(modelID) | FCMP2(tableID) | FCMP(missvalused)
    | FCMPFLT(missval) | FCMPFLT(addoffset) | FCMPFLT(scalefactor)
    | FCMPSTR(extra)
    | 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)
    {
      const int zaxisID = pva->zaxisID;
      const size_t nlevs = (size_t)zaxisInqSize(zaxisID);
      diff |= (memcmp(pva->levinfo, pvb->levinfo, sizeof (levinfo_t) * nlevs) != 0);
      if (diff) return 1;
    }

  const 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);

  const 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:
 */

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,
  VLISTVAR_PACK_INT_IDX_IORANK,
  VLISTVAR_PACK_INT_IDX_EXTRALEN,
  vlistvarNint
};

enum {
  vlistvar_ndbls = 3,
};

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->extra)
    varsize += serializeGetSize((int)strlen(var->extra), CDI_DATATYPE_TXT, context);
  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)
{
  double dtempbuf[vlistvar_ndbls];
  var_t *var = p->vars + varID;
  int tempbuf[vlistvarNint], extralen;

  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;
  int nlevs = var->levinfo ? zaxisInqSize(var->zaxisID) : 0;
  tempbuf[VLISTVAR_PACK_INT_IDX_NLEVS] = nlevs;
  tempbuf[VLISTVAR_PACK_INT_IDX_IORANK] = var->iorank;
  tempbuf[VLISTVAR_PACK_INT_IDX_EXTRALEN] = extralen = var->extra?(int)strlen(var->extra):0;
  dtempbuf[0] = var->missval;
  dtempbuf[1] = var->scalefactor;
  dtempbuf[2] = var->addoffset;
  serializePack(tempbuf, vlistvarNint, CDI_DATATYPE_INT, buf, size, position, context);
  serializePack(dtempbuf, vlistvar_ndbls, CDI_DATATYPE_FLT64,buf, size, position, context);
  if (extralen)
    serializePack(var->extra, extralen, CDI_DATATYPE_TXT, buf, size, position, context);
  if (nlevs)
    {
      int *levbuf = (int*) malloc(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)
{
  double dtempbuf[vlistvar_ndbls];
  int tempbuf[vlistvarNint];
  char *varname = NULL;
  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,
			 namespaceAdaptKey ( tempbuf[VLISTVAR_PACK_INT_IDX_GRIDID], originNamespace ),
			 namespaceAdaptKey ( tempbuf[VLISTVAR_PACK_INT_IDX_ZAXISID], originNamespace ),
			 tempbuf[VLISTVAR_PACK_INT_IDX_TIMETYPE]);
  if (tempbuf[VLISTVAR_PACK_INT_IDX_EXTRALEN])
    varname = (char *)Malloc((size_t)tempbuf[VLISTVAR_PACK_INT_IDX_EXTRALEN] + 1);
  if (tempbuf[VLISTVAR_PACK_INT_IDX_EXTRALEN])
    {
      serializeUnpack(buf, size, position,
                      varname, tempbuf[VLISTVAR_PACK_INT_IDX_EXTRALEN], CDI_DATATYPE_TXT, context);
      varname[tempbuf[VLISTVAR_PACK_INT_IDX_EXTRALEN]] = '\0';
      vlistDefVarExtra(vlistID, newvar, varname);
    }
  Free(varname);
  vlistDefVarDatatype(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_DATATYPE]);
  vlistDefVarInstitut ( vlistID, newvar,
			namespaceAdaptKey ( tempbuf[VLISTVAR_PACK_INT_IDX_INSTID], originNamespace ));
  vlistDefVarModel ( vlistID, newvar,
		     namespaceAdaptKey ( tempbuf[VLISTVAR_PACK_INT_IDX_MODELID], originNamespace ));
  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[0]);
  vlistDefVarScalefactor(vlistID, newvar, dtempbuf[1]);
  vlistDefVarAddoffset(vlistID, newvar, dtempbuf[2]);
  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(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);
    }
  vlistDefVarIOrank(vlistID, newvar, tempbuf[VLISTVAR_PACK_INT_IDX_IORANK]);

  serializeKeysUnpack(buf, size, position, &var->keys, context);

  cdiAttsUnpack(vlistID, newvar, 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:
 */
#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 ( (strcmp(name, vlistptr->vars[varID].opt_grib_kvpair[idx].keyword) == 0 ) &&
         (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 = strdupx(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 ( (strcmp(name, vlistptr->vars[varID].opt_grib_kvpair[idx].keyword) == 0 ) &&
         (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 = strdupx(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
}


/* cdiClearAdditionalKeys: Clears the list of additional GRIB keys. */
void cdiClearAdditionalKeys()
{
#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] = strdupx(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 ( strcmp(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword) == 0 )
	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 ( (strcmp(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword) == 0 ) &&
           (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 ( (strcmp(name, vlistptr->vars[varID].opt_grib_kvpair[i].keyword) == 0 ) &&
           (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;
}

/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
#include <string.h>
#include <math.h>
#include <float.h>



#define  LevelUp    1
#define  LevelDown  2


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",               "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",             "",               ""},
};

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 );

static const resOps zaxisOps = {
  (int (*)(void *, void *))zaxisCompareP,
  zaxisDestroyP,
  zaxisPrintP,
  zaxisGetPackSize,
  zaxisPack,
  zaxisTxCode
};

const resOps *getZaxisOps(void)
{
  return &zaxisOps;
}

static int  ZAXIS_Debug = 0;   /* If set to 1, debugging */

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->datatype      = CDI_DATATYPE_FLT64;
  zaxisptr->size          = 0;
  zaxisptr->vctsize       = 0;
  zaxisptr->vct           = NULL;

  cdiInitKeys(&zaxisptr->keys);
  zaxisptr->atts.nalloc    = MAX_ATTRIBUTES;
  zaxisptr->atts.nelems    = 0;
}

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;

  const char *env = getenv("ZAXIS_DEBUG");
  if ( env ) ZAXIS_Debug = atoi(env);
}

static
void zaxis_copy(zaxis_t *zaxisptr2, zaxis_t *zaxisptr1)
{
  const 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;

  if ( zaxistype >= CDI_NumZaxistype || zaxistype < 0 )
    Error("Internal problem! zaxistype=%d out of range (min=0/max=%d)!", zaxistype, CDI_NumZaxistype-1);

  const int zaxisID = zaxisptr->self;
  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;

  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 );

  const int id = zaxisptr->self;

  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 );

  cdiDeleteKeys(id, CDI_GLOBAL);
  cdiDeleteAtts(id, CDI_GLOBAL);

  Free(zaxisptr);

  reshRemove(id, &zaxisOps);
}

/*
@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);
}


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));
}

/*
@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)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  if ( zaxisptr->datatype != datatype )
    {
      zaxisptr->datatype = datatype;
      reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
    }
}


int zaxisInqDatatype(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);
  return zaxisptr->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 = 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*sizeof(char));
          memcpy(zaxisptr->cvals[ilev], cvals[ilev], clen*sizeof(char));
        }
      reshSetStatus(zaxisID, &zaxisOps, RESH_DESYNC_IN_USE);
    }
#else
  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);
  const 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;
#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));
            }
          }
    }
#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 )
    {
      const 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 )
    {
      const 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 == KEY_BYTES)
    cdiDefVarKeyBytes(&zaxisptr2->keys, key, (const unsigned char*)keyp->v.s, (int)keyp->length);
}


int zaxisDuplicate(int zaxisID)
{
  zaxis_t *zaxisptr = zaxis_to_pointer(zaxisID);

  const int zaxistype = zaxisInqType(zaxisID);
  const int zaxissize = zaxisInqSize(zaxisID);

  const 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);

  const int type    = zaxisptr->type;
  const int nlevels = zaxisptr->size;
  const int datatype = zaxisptr->datatype;

  const 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 )
    {
      const 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))
    | (z1->direction != z2->direction)
    | (z1->datatype != z2->datatype)
    | (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)
{
  return ZAXIS;
}

enum { zaxisNint     = 7,
       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_UCHAR, context);

  return packBufferSize;
}


void
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[0], originNamespace)
                    : CDI_UNDEFID);

  zaxisP->datatype  = intBuffer[1];
  zaxisP->type      = intBuffer[2];
  zaxisP->size      = intBuffer[3];
  zaxisP->direction = intBuffer[4];
  zaxisP->vctsize   = intBuffer[5];
  memberMask        = intBuffer[6];

  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);
}

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[0]  = zaxisP->self;
  intBuffer[1]  = zaxisP->datatype;
  intBuffer[2]  = zaxisP->type;
  intBuffer[3]  = zaxisP->size;
  intBuffer[4]  = zaxisP->direction;
  intBuffer[5]  = zaxisP->vctsize;
  intBuffer[6]  = 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[] = "1.9.9";
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

/* 
   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


#endif	 /* __CFORTRAN_LOADED */
#endif
/* Automatically generated by make_fint.c, don't edit! */

#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 ignored "-Wmissing-prototypes"
#endif

static inline
int int64_t_c2f(int64_t value_int64_t)
{
  assert(value_int64_t < INT_MAX);
  return (int) value_int64_t;
}

static inline
int size_t_c2f(size_t value_size_t)
{
  assert(value_size_t < INT_MAX);
  return (int) value_size_t;
}


/*  Byte order  */


/*  Error identifier  */


/*  File types  */


/*  Compress types  */


/*  external data types  */


/*  internal data types  */


/*  Chunks  */


/*  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)
FCALLSCSUB1 (cdiDefMissval, CDIDEFMISSVAL, cdidefmissval, DOUBLE)
FCALLSCFUN0 (DOUBLE, cdiInqMissval, CDIINQMISSVAL, cdiinqmissval)
FCALLSCFUN0 (DOUBLE, cdiInqGridMissval, CDIINQGRIDMISSVAL, cdiinqgridmissval)
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  */

static void cdiDecodeDate_fwrap(int date, int *year, int *month, int *day)
{
  cdiDecodeDate((int64_t)date, year, month, day);
}
FCALLSCSUB4 (cdiDecodeDate_fwrap, CDIDECODEDATE, cdidecodedate, INT, PINT, PINT, PINT)
static int cdiEncodeDate_fwrap(int year, int month, int day)
{
  int64_t v;
  v = cdiEncodeDate(year, month, day);
  return int64_t_c2f(v);
}
FCALLSCFUN3 (INT, cdiEncodeDate_fwrap, 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, 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 (streamDefNumWorker, STREAMDEFNUMWORKER, streamdefnumworker, INT, 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 (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)
{
  size_t v;
  v = streamNvals(streamID);
  return size_t_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 nmiss)
{
  streamWriteVar(streamID, varID, data, (size_t)nmiss);
}
FCALLSCSUB4 (streamWriteVar_fwrap, STREAMWRITEVAR, streamwritevar, INT, INT, DOUBLEV, INT)
static void streamWriteVarF_fwrap(int streamID, int varID, const float data[], int nmiss)
{
  streamWriteVarF(streamID, varID, data, (size_t)nmiss);
}
FCALLSCSUB4 (streamWriteVarF_fwrap, STREAMWRITEVARF, streamwritevarf, INT, INT, FLOATV, INT)
static void streamReadVar_fwrap(int streamID, int varID, double data[], int *nmiss)
{
  size_t nmiss_size_t;
  streamReadVar(streamID, varID, data, &nmiss_size_t);
  assert(nmiss_size_t < INT_MAX);
  *nmiss = nmiss_size_t;
}
FCALLSCSUB4 (streamReadVar_fwrap, STREAMREADVAR, streamreadvar, INT, INT, DOUBLEV, PINT)
static void streamReadVarF_fwrap(int streamID, int varID, float data[], int *nmiss)
{
  size_t nmiss_size_t;
  streamReadVarF(streamID, varID, data, &nmiss_size_t);
  assert(nmiss_size_t < INT_MAX);
  *nmiss = nmiss_size_t;
}
FCALLSCSUB4 (streamReadVarF_fwrap, STREAMREADVARF, streamreadvarf, INT, INT, FLOATV, PINT)
static void streamWriteVarSlice_fwrap(int streamID, int varID, int levelID, const double data[], int nmiss)
{
  streamWriteVarSlice(streamID, varID, levelID, data, (size_t)nmiss);
}
FCALLSCSUB5 (streamWriteVarSlice_fwrap, STREAMWRITEVARSLICE, streamwritevarslice, INT, INT, INT, DOUBLEV, INT)
static void streamWriteVarSliceF_fwrap(int streamID, int varID, int levelID, const float data[], int nmiss)
{
  streamWriteVarSliceF(streamID, varID, levelID, data, (size_t)nmiss);
}
FCALLSCSUB5 (streamWriteVarSliceF_fwrap, STREAMWRITEVARSLICEF, streamwritevarslicef, INT, INT, INT, FLOATV, INT)
static void streamReadVarSlice_fwrap(int streamID, int varID, int levelID, double data[], int *nmiss)
{
  size_t nmiss_size_t;
  streamReadVarSlice(streamID, varID, levelID, data, &nmiss_size_t);
  assert(nmiss_size_t < INT_MAX);
  *nmiss = nmiss_size_t;
}
FCALLSCSUB5 (streamReadVarSlice_fwrap, STREAMREADVARSLICE, streamreadvarslice, INT, INT, INT, DOUBLEV, PINT)
static void streamReadVarSliceF_fwrap(int streamID, int varID, int levelID, float data[], int *nmiss)
{
  size_t nmiss_size_t;
  streamReadVarSliceF(streamID, varID, levelID, data, &nmiss_size_t);
  assert(nmiss_size_t < INT_MAX);
  *nmiss = nmiss_size_t;
}
FCALLSCSUB5 (streamReadVarSliceF_fwrap, STREAMREADVARSLICEF, streamreadvarslicef, INT, INT, INT, FLOATV, PINT)
static void streamWriteVarChunk_fwrap(int streamID, int varID, const int rect[3][2], const double data[], int nmiss)
{
  streamWriteVarChunk(streamID, varID, rect, data, (size_t)nmiss);
}
FCALLSCSUB5 (streamWriteVarChunk_fwrap, STREAMWRITEVARCHUNK, streamwritevarchunk, INT, INT, INTVV, DOUBLEV, INT)

/*  STREAM record I/O routines (sequential access)  */

FCALLSCSUB3 (streamDefRecord, STREAMDEFRECORD, streamdefrecord, INT, INT, INT)
FCALLSCSUB3 (streamInqRecord, STREAMINQRECORD, streaminqrecord, INT, PINT, PINT)
static void streamWriteRecord_fwrap(int streamID, const double data[], int nmiss)
{
  streamWriteRecord(streamID, data, (size_t)nmiss);
}
FCALLSCSUB3 (streamWriteRecord_fwrap, STREAMWRITERECORD, streamwriterecord, INT, DOUBLEV, INT)
static void streamWriteRecordF_fwrap(int streamID, const float data[], int nmiss)
{
  streamWriteRecordF(streamID, data, (size_t)nmiss);
}
FCALLSCSUB3 (streamWriteRecordF_fwrap, STREAMWRITERECORDF, streamwriterecordf, INT, FLOATV, INT)
static void streamReadRecord_fwrap(int streamID, double data[], int *nmiss)
{
  size_t nmiss_size_t;
  streamReadRecord(streamID, data, &nmiss_size_t);
  assert(nmiss_size_t < INT_MAX);
  *nmiss = nmiss_size_t;
}
FCALLSCSUB3 (streamReadRecord_fwrap, STREAMREADRECORD, streamreadrecord, INT, DOUBLEV, PINT)
static void streamReadRecordF_fwrap(int streamID, float data[], int *nmiss)
{
  size_t nmiss_size_t;
  streamReadRecordF(streamID, data, &nmiss_size_t);
  assert(nmiss_size_t < INT_MAX);
  *nmiss = nmiss_size_t;
}
FCALLSCSUB3 (streamReadRecordF_fwrap, STREAMREADRECORDF, streamreadrecordf, INT, FLOATV, PINT)
FCALLSCSUB2 (streamCopyRecord, STREAMCOPYRECORD, streamcopyrecord, 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 (vlistCopy, VLISTCOPY, vlistcopy, INT, 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, vlistNgrids, VLISTNGRIDS, vlistngrids, INT)
FCALLSCFUN1 (INT, vlistNzaxis, VLISTNZAXIS, vlistnzaxis, 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)
{
  size_t v;
  v = vlistGridsizeMax(vlistID);
  return size_t_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, vlistNrecs, VLISTNRECS, vlistnrecs, 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 (vlistDefVarChunkType, VLISTDEFVARCHUNKTYPE, vlistdefvarchunktype, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarChunkType, VLISTINQVARCHUNKTYPE, vlistinqvarchunktype, INT, INT)
FCALLSCSUB3 (vlistDefVarXYZ, VLISTDEFVARXYZ, vlistdefvarxyz, INT, INT, INT)
FCALLSCFUN2 (INT, vlistInqVarXYZ, VLISTINQVARXYZ, vlistinqvarxyz, 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)
FCALLSCSUB3 (vlistDefVarExtra, VLISTDEFVAREXTRA, vlistdefvarextra, INT, INT, STRING)
FCALLSCSUB3 (vlistInqVarExtra, VLISTINQVAREXTRA, vlistinqvarextra, INT, INT, PSTRING)
FCALLSCSUB3 (vlistDefVarScalefactor, VLISTDEFVARSCALEFACTOR, vlistdefvarscalefactor, INT, INT, DOUBLE)
FCALLSCFUN2 (DOUBLE, vlistInqVarScalefactor, VLISTINQVARSCALEFACTOR, vlistinqvarscalefactor, INT, INT)
FCALLSCSUB3 (vlistDefVarAddoffset, VLISTDEFVARADDOFFSET, vlistdefvaraddoffset, INT, INT, DOUBLE)
FCALLSCFUN2 (DOUBLE, vlistInqVarAddoffset, VLISTINQVARADDOFFSET, vlistinqvaraddoffset, INT, INT)
static int vlistInqVarSize_fwrap(int vlistID, int varID)
{
  size_t v;
  v = vlistInqVarSize(vlistID, varID);
  return size_t_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)
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, (size_t)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)
{
  size_t v;
  v = gridInqSize(gridID);
  return size_t_c2f(v);
}
FCALLSCFUN1 (INT, gridInqSize_fwrap, GRIDINQSIZE, gridinqsize, INT)
static void gridDefXsize_fwrap(int gridID, int xsize)
{
  gridDefXsize(gridID, (size_t)xsize);
}
FCALLSCSUB2 (gridDefXsize_fwrap, GRIDDEFXSIZE, griddefxsize, INT, INT)
static int gridInqXsize_fwrap(int gridID)
{
  size_t v;
  v = gridInqXsize(gridID);
  return size_t_c2f(v);
}
FCALLSCFUN1 (INT, gridInqXsize_fwrap, GRIDINQXSIZE, gridinqxsize, INT)
static void gridDefYsize_fwrap(int gridID, int ysize)
{
  gridDefYsize(gridID, (size_t)ysize);
}
FCALLSCSUB2 (gridDefYsize_fwrap, GRIDDEFYSIZE, griddefysize, INT, INT)
static int gridInqYsize_fwrap(int gridID)
{
  size_t v;
  v = gridInqYsize(gridID);
  return size_t_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[])
{
  size_t v;
  v = gridInqXvals(gridID, xvals);
  return size_t_c2f(v);
}
FCALLSCFUN2 (INT, gridInqXvals_fwrap, GRIDINQXVALS, gridinqxvals, INT, DOUBLEV)
static int gridInqXvalsPart_fwrap(int gridID, int start, int size, double xvals[])
{
  size_t v;
  v = gridInqXvalsPart(gridID, start, (size_t)size, xvals);
  return size_t_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[])
{
  size_t v;
  v = gridInqYvals(gridID, yvals);
  return size_t_c2f(v);
}
FCALLSCFUN2 (INT, gridInqYvals_fwrap, GRIDINQYVALS, gridinqyvals, INT, DOUBLEV)
static int gridInqYvalsPart_fwrap(int gridID, int start, int size, double yvals[])
{
  size_t v;
  v = gridInqYvalsPart(gridID, start, (size_t)size, yvals);
  return size_t_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, (size_t)index);
  return v;
}
FCALLSCFUN2 (DOUBLE, gridInqXval_fwrap, GRIDINQXVAL, gridinqxval, INT, INT)
static double gridInqYval_fwrap(int gridID, int index)
{
  double v;
  v = gridInqYval(gridID, (size_t)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, gridIsCircular, GRIDISCIRCULAR, gridiscircular, 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)

/*  Lambert Conformal Conic grid  */

FCALLSCSUB12 (gridDefParamLCC, GRIDDEFPARAMLCC, griddefparamlcc, INT, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE)
FCALLSCFUN12 (INT, gridInqParamLCC, GRIDINQPARAMLCC, gridinqparamlcc, INT, DOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE)

/*  Polar stereographic grid  */

FCALLSCSUB10 (gridDefParamSTERE, GRIDDEFPARAMSTERE, griddefparamstere, INT, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE)
FCALLSCFUN10 (INT, gridInqParamSTERE, GRIDINQPARAMSTERE, gridinqparamstere, INT, DOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE, PDOUBLE)
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[])
{
  size_t v;
  v = gridInqXbounds(gridID, xbounds);
  return size_t_c2f(v);
}
FCALLSCFUN2 (INT, gridInqXbounds_fwrap, GRIDINQXBOUNDS, gridinqxbounds, INT, DOUBLEV)
static int gridInqXboundsPart_fwrap(int gridID, int start, int size, double xbounds[])
{
  size_t v;
  v = gridInqXboundsPart(gridID, start, (size_t)size, xbounds);
  return size_t_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[])
{
  size_t v;
  v = gridInqYbounds(gridID, ybounds);
  return size_t_c2f(v);
}
FCALLSCFUN2 (INT, gridInqYbounds_fwrap, GRIDINQYBOUNDS, gridinqybounds, INT, DOUBLEV)
static int gridInqYboundsPart_fwrap(int gridID, int start, int size, double ybounds[])
{
  size_t v;
  v = gridInqYboundsPart(gridID, start, (size_t)size, ybounds);
  return size_t_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)
static void taxisDefVdate_fwrap(int taxisID, int date)
{
  taxisDefVdate(taxisID, (int64_t)date);
}
FCALLSCSUB2 (taxisDefVdate_fwrap, TAXISDEFVDATE, taxisdefvdate, INT, INT)
FCALLSCSUB2 (taxisDefVtime, TAXISDEFVTIME, taxisdefvtime, INT, INT)
static int taxisInqVdate_fwrap(int taxisID)
{
  int64_t v;
  v = taxisInqVdate(taxisID);
  return int64_t_c2f(v);
}
FCALLSCFUN1 (INT, taxisInqVdate_fwrap, TAXISINQVDATE, taxisinqvdate, INT)
FCALLSCFUN1 (INT, taxisInqVtime, TAXISINQVTIME, taxisinqvtime, INT)
static void taxisDefRdate_fwrap(int taxisID, int date)
{
  taxisDefRdate(taxisID, (int64_t)date);
}
FCALLSCSUB2 (taxisDefRdate_fwrap, TAXISDEFRDATE, taxisdefrdate, INT, INT)
FCALLSCSUB2 (taxisDefRtime, TAXISDEFRTIME, taxisdefrtime, INT, INT)
static int taxisInqRdate_fwrap(int taxisID)
{
  int64_t v;
  v = taxisInqRdate(taxisID);
  return int64_t_c2f(v);
}
FCALLSCFUN1 (INT, taxisInqRdate_fwrap, TAXISINQRDATE, taxisinqrdate, INT)
FCALLSCFUN1 (INT, taxisInqRtime, TAXISINQRTIME, taxisinqrtime, INT)
static void taxisDefFdate_fwrap(int taxisID, int date)
{
  taxisDefFdate(taxisID, (int64_t)date);
}
FCALLSCSUB2 (taxisDefFdate_fwrap, TAXISDEFFDATE, taxisdeffdate, INT, INT)
FCALLSCSUB2 (taxisDefFtime, TAXISDEFFTIME, taxisdefftime, INT, INT)
static int taxisInqFdate_fwrap(int taxisID)
{
  int64_t v;
  v = taxisInqFdate(taxisID);
  return int64_t_c2f(v);
}
FCALLSCFUN1 (INT, taxisInqFdate_fwrap, TAXISINQFDATE, taxisinqfdate, INT)
FCALLSCFUN1 (INT, taxisInqFtime, TAXISINQFTIME, taxisinqftime, INT)
FCALLSCFUN1 (INT, taxisHasBounds, TAXISHASBOUNDS, taxishasbounds, INT)
FCALLSCSUB1 (taxisWithBounds, TAXISWITHBOUNDS, taxiswithbounds, INT)
FCALLSCSUB1 (taxisDeleteBounds, TAXISDELETEBOUNDS, taxisdeletebounds, INT)
static void taxisDefVdateBounds_fwrap(int taxisID, int vdate_lb, int vdate_ub)
{
  taxisDefVdateBounds(taxisID, (int64_t)vdate_lb, (int64_t)vdate_ub);
}
FCALLSCSUB3 (taxisDefVdateBounds_fwrap, TAXISDEFVDATEBOUNDS, taxisdefvdatebounds, INT, INT, INT)
FCALLSCSUB3 (taxisDefVtimeBounds, TAXISDEFVTIMEBOUNDS, taxisdefvtimebounds, INT, INT, INT)
static void taxisInqVdateBounds_fwrap(int taxisID, int *vdate_lb, int *vdate_ub)
{
  int64_t vdate_lb_int64_t;
  int64_t vdate_ub_int64_t;
  taxisInqVdateBounds(taxisID, &vdate_lb_int64_t, &vdate_ub_int64_t);
  assert(vdate_lb_int64_t < INT_MAX);
  *vdate_lb = vdate_lb_int64_t;
  assert(vdate_ub_int64_t < INT_MAX);
  *vdate_ub = vdate_ub_int64_t;
}
FCALLSCSUB3 (taxisInqVdateBounds_fwrap, 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, 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)

#if defined __clang__
#  pragma GCC diagnostic pop
#endif
#endif
