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

#ifdef HAVE_LIBNETCDF

#include "dmemory.h"
#include "cdi_int.h"
#include "cdi_uuid.h"
#include "stream_cdf.h"
#include "stream_cdf_postdef.h"
#include "cdf_int.h"
#include "cdf_util.h"
#include "namespace.h"
#include "vlist.h"
#include "zaxis.h"

static const char bndsName[] = "bnds";

void
cdfCopyField(stream_t *streamptr2, stream_t *streamptr1)
{
  int vlistID1 = streamptr1->vlistID;
  int tsID = streamptr1->curTsID;
  int vrecID = streamptr1->tsteps[tsID].curRecID;
  int recID = streamptr1->tsteps[tsID].recIDs[vrecID];
  int ivarID = streamptr1->tsteps[tsID].recinfo[recID].varID;
  int gridID = vlistInqVarGrid(vlistID1, ivarID);
  size_t datasize = (size_t) gridInqSize(gridID);
  int datatype = vlistInqVarDatatype(vlistID1, ivarID);
  int memtype = (datatype != CDI_DATATYPE_FLT32) ? MEMTYPE_DOUBLE : MEMTYPE_FLOAT;

  void *data = Malloc(datasize * ((memtype == MEMTYPE_DOUBLE) ? sizeof(double) : sizeof(float)));

  size_t numMissVals;
  cdf_read_field(streamptr1, memtype, data, &numMissVals);
  cdf_write_field(streamptr2, memtype, data, numMissVals);

  Free(data);
}

void
cdfDefField(stream_t *streamptr)
{
  (void) streamptr;
}

static void
cdfDefComplex(stream_t *streamptr, int gridID, int gridIndex)
{
  int dimID = 0;
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  for (int index = 0; index < gridIndex; ++index)
  {
    if (cdfGridList[index].ncIdList[CDF_DIMID_X] != CDI_UNDEFID)
    {
      int gridID0 = cdfGridList[index].gridID;
      int gridtype0 = gridInqType(gridID0);
      if (gridtype0 == GRID_SPECTRAL || gridtype0 == GRID_FOURIER)
      {
        dimID = cdfGridList[index].ncIdList[CDF_DIMID_X];
        goto dimIDEstablished;
      }
    }
  }

  {
    static const char axisname[] = "nc2";
    size_t dimlen = 2;
    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    cdf_def_dim(fileID, axisname, dimlen, &dimID);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }

dimIDEstablished:
  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] = dimID;
}

struct idSearch
{
  int numNonMatching, foundID;
  size_t foundIdx;
};

static inline struct idSearch
cdfSearchIDBySize(size_t startIdx, size_t numIDs, const CdfGrid cdfGridList[/*numIDs*/], int ncIDType, int searchType,
                  SizeType searchSize, int (*typeInq)(int id), SizeType (*sizeInq)(int id))
{
  int numNonMatching = 0, foundID = CDI_UNDEFID;
  size_t foundIdx = SIZE_MAX;
  for (size_t index = startIdx; index < numIDs; index++)
  {
    if (cdfGridList[index].ncIdList[ncIDType] != CDI_UNDEFID)
    {
      int id0 = cdfGridList[index].gridID, id0Type = typeInq(id0);
      if (id0Type == searchType)
      {
        SizeType size0 = sizeInq(id0);
        if (searchSize == size0)
        {
          foundID = cdfGridList[index].ncIdList[ncIDType];
          foundIdx = index;
          break;
        }
        numNonMatching++;
      }
    }
  }
  return (struct idSearch){ .numNonMatching = numNonMatching, .foundID = foundID, .foundIdx = foundIdx };
}

static SizeType
cdfGridInqHalfSize(int gridID)
{
  return gridInqSize(gridID) / 2;
}

static void
cdfDefSPorFC(stream_t *streamptr, int gridID, int gridIndex, char *axisname, size_t maxlen, int gridRefType)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = gridInqSize(gridID) / 2;

  struct idSearch search
      = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_Y, gridRefType, dimlen, gridInqType, cdfGridInqHalfSize);
  int dimID = search.foundID;
  int iz = search.numNonMatching;

  if (dimID == CDI_UNDEFID)
  {
    int fileID = streamptr->fileID;
    size_t len = strlen(axisname);
    if (iz) snprintf(axisname + len, maxlen - len, "%1d", iz + 1);

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    cdf_def_dim(fileID, axisname, (size_t) dimlen, &dimID);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[CDF_DIMID_Y] = dimID;
}

static void
cdfDefSP(stream_t *streamptr, int gridID, int gridIndex)
{
  // char longname[] = "Spherical harmonic coefficient";
  char axisname[5] = "nsp";
  cdfDefSPorFC(streamptr, gridID, gridIndex, axisname, sizeof(axisname), GRID_SPECTRAL);
}

static void
cdfDefFC(stream_t *streamptr, int gridID, int gridIndex)
{
  char axisname[5] = "nfc";
  cdfDefSPorFC(streamptr, gridID, gridIndex, axisname, sizeof(axisname), GRID_FOURIER);
}

static const struct cdfDefGridAxisInqs
{
  SizeType (*axisSize)(int gridID);
  double (*axisVal)(int gridID, SizeType index);
  const double *(*axisValsPtr)(int gridID);
  const double *(*axisBoundsPtr)(int gridID);
  enum cdfIDIdx dimIdx, varIdx;
  char axisSym;
  enum gridPropInq valsQueryKey, bndsQueryKey;
  char axisPanoplyName[4];
} gridInqsX = {
  .axisSize = gridInqXsize,
  .axisVal = gridInqXval,
  .axisValsPtr = gridInqXvalsPtr,
  .axisBoundsPtr = gridInqXboundsPtr,
  .dimIdx = CDF_DIMID_X,
  .varIdx = CDF_VARID_X,
  .axisSym = 'X',
  .valsQueryKey = GRID_PROP_XVALS,
  .bndsQueryKey = GRID_PROP_XBOUNDS,
  .axisPanoplyName = "Lon",
}, gridInqsY = {
  .axisSize = gridInqYsize,
  .axisVal = gridInqYval,
  .axisValsPtr = gridInqYvalsPtr,
  .axisBoundsPtr = gridInqYboundsPtr,
  .dimIdx = CDF_DIMID_Y,
  .varIdx = CDF_VARID_Y,
  .axisSym = 'Y',
  .valsQueryKey = GRID_PROP_YVALS,
  .bndsQueryKey = GRID_PROP_YBOUNDS,
  .axisPanoplyName = "Lat",
};

static void
cdfPutGridStdAtts(int fileID, int ncvarid, int gridID, int dimtype)
{
  size_t len;

  int axisKey = (dimtype == 'Z') ? CDI_GLOBAL : ((dimtype == 'X') ? CDI_XAXIS : CDI_YAXIS);

  {
    char stdname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_STDNAME, stdname, &length);
    if (stdname[0] && (len = strlen(stdname))) cdf_put_att_text(fileID, ncvarid, "standard_name", len, stdname);
  }
  {
    char longname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_LONGNAME, longname, &length);
    if (longname[0] && (len = strlen(longname))) cdf_put_att_text(fileID, ncvarid, "long_name", len, longname);
  }
  {
    char units[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_UNITS, units, &length);
    if (units[0] && (len = strlen(units))) cdf_put_att_text(fileID, ncvarid, "units", len, units);
  }
}

static int
grid_inq_xtype(int gridID)
{
  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  return (datatype == CDI_DATATYPE_FLT32) ? NC_FLOAT : NC_DOUBLE;
}

static void
cdfDefTrajLatLon(stream_t *streamptr, int gridID, int gridIndex, const struct cdfDefGridAxisInqs *inqs)
{
  nc_type xtype = grid_inq_xtype(gridID);
  CdfGrid *ncgrid = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = inqs->axisSize(gridID);
  if (dimlen != 1) Error("%c size isn't 1 for %s grid!", inqs->axisSym, gridNamePtr(gridInqType(gridID)));

  int ncvarid = ncgrid[gridIndex].ncIdList[inqs->dimIdx];
  if (ncvarid == CDI_UNDEFID)
  {
    int dimNcID = streamptr->basetime.ncvarid;
    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    char axisname[CDI_MAX_NAME];
    int axistype = (inqs->axisSym == 'X') ? CDI_XAXIS : CDI_YAXIS;
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axistype, CDI_KEY_NAME, axisname, &length);
    cdf_def_var(fileID, axisname, xtype, 1, &dimNcID, &ncvarid);
    cdfPutGridStdAtts(fileID, ncvarid, gridID, inqs->axisSym);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }

  ncgrid[gridIndex].gridID = gridID;
  // var ID for trajectory !!!
  ncgrid[gridIndex].ncIdList[inqs->dimIdx] = ncvarid;
}

static void
cdfDefTrajLon(stream_t *streamptr, int gridID, int gridIndex)
{
  cdfDefTrajLatLon(streamptr, gridID, gridIndex, &gridInqsX);
}

static void
cdfDefTrajLat(stream_t *streamptr, int gridID, int gridIndex)
{
  cdfDefTrajLatLon(streamptr, gridID, gridIndex, &gridInqsY);
}

static int
checkDimName(int fileID, size_t dimlen, char *dimname)
{
  // check whether the dimenion name is already defined with the same length
  unsigned iz = 0;
  int dimid = CDI_UNDEFID;
  char name[CDI_MAX_NAME];

  size_t len = strlen(dimname);
  memcpy(name, dimname, len + 1);

  do {
    if (iz) snprintf(name + len, CDI_MAX_NAME - len, "_%u", iz + 1);

    int dimid0;
    int status = nc_inq_dimid(fileID, name, &dimid0);
    if (status != NC_NOERR) break;
    size_t dimlen0;
    cdf_inq_dimlen(fileID, dimid0, &dimlen0);
    if (dimlen0 == dimlen)
    {
      dimid = dimid0;
      break;
    }
    iz++;
  } while (iz <= 99);

  if (iz) snprintf(dimname + len, CDI_MAX_NAME - len, "_%u", iz + 1);

  return dimid;
}

static unsigned
checkGridName(char *axisname, int fileID)
{
  // check that the name is not already defined
  unsigned iz = 0;

  char axisname2[CDI_MAX_NAME];
  size_t axisnameLen = strlen(axisname);
  memcpy(axisname2, axisname, axisnameLen + 1);

  do {
    if (iz) snprintf(axisname2 + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

    int ncdimid;
    if (nc_inq_varid(fileID, axisname2, &ncdimid) != NC_NOERR) break;

    ++iz;
  } while (iz <= 99);

  if (iz) snprintf(axisname + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

  return iz;
}

static int
checkZaxisName(char *axisname, int fileID, int vlistID, int zaxisID, int nzaxis)
{
  char axisname2[CDI_MAX_NAME];

  // check that the name is not already defined
  unsigned iz = 0;

  size_t axisnameLen = strlen(axisname);
  memcpy(axisname2, axisname, axisnameLen + 1);
  do {
    if (iz) snprintf(axisname2 + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

    int ncdimid;
    int status = nc_inq_varid(fileID, axisname2, &ncdimid);
    if (status != NC_NOERR)
    {
      if (iz)
      {
        // check that the name does not exist for other zaxes
        for (int index = 0; index < nzaxis; index++)
        {
          int zaxisID0 = vlistZaxis(vlistID, index);
          if (zaxisID != zaxisID0)
          {
            const char *axisname0 = zaxisInqNamePtr(zaxisID0);
            if (str_is_equal(axisname0, axisname2)) goto nextSuffix;
          }
        }
      }
      break;
    }

  nextSuffix:
    ++iz;
  } while (iz <= 99);

  if (iz) snprintf(axisname + axisnameLen, CDI_MAX_NAME - axisnameLen, "_%u", iz + 1);

  return (int) iz;
}

struct cdfPostDefPutVar
{
  int fileID, ncvarid;
  union
  {
    const void *array;
    int int1;
  } values;
};

static void
cdfDelayedPutVarDouble(void *data)
{
  struct cdfPostDefPutVar *put = (struct cdfPostDefPutVar *) data;
  cdf_put_var_double(put->fileID, put->ncvarid, (const double *) put->values.array);
}

static void
cdfDelayedPutVarInt1(void *data)
{
  struct cdfPostDefPutVar *put = (struct cdfPostDefPutVar *) data;
  cdf_put_var_int(put->fileID, put->ncvarid, &put->values.int1);
}

void
cdfDelayedPutVarDeepCleanup(void *data)
{
  struct cdfPostDefPutVar *what = (struct cdfPostDefPutVar *) data;
  Free((void *) what->values.array);
  Free(what);
}

static void
cdfPostDefActionApply(size_t numActions, struct cdfPostDefAction *actions)
{
  for (size_t i = 0; i < numActions; ++i) actions[i].action(actions[i].data);
}

static void
cdfPostDefActionListDelete(struct cdfPostDefActionList *list)
{
  struct cdfPostDefAction *actions = list->actions;
  for (size_t i = 0, len = list->len; i < len; ++i)
  {
    void (*cleanup)(void *) = actions[i].cleanup;
    void *data = actions[i].data;
    (cleanup == (void (*)(void *))(void (*)(void)) memFree) ? Free(data) : cleanup(data);
  }
  Free(list);
}

struct cdfPostDefActionList *
cdfPostDefActionAdd(struct cdfPostDefActionList *list, struct cdfPostDefAction addendum)
{
  size_t appendPos = list ? list->len : 0;
  if (!list || list->size == list->len)
  {
    enum
    {
      initialListSize = 1
    };
    size_t newSize = list ? (list->size * 2) : initialListSize, newLen = list ? list->len + 1 : 1,
           newAllocSize = sizeof(struct cdfPostDefActionList) + newSize * sizeof(struct cdfPostDefAction);
    list = (struct cdfPostDefActionList *) Realloc(list, newAllocSize);
    list->size = newSize;
    list->len = newLen;
  }
  else
    ++(list->len);
  list->actions[appendPos] = addendum;
  return list;
}

static struct cdfPostDefActionList *
cdfPostDefActionConcat(struct cdfPostDefActionList *listA, const struct cdfPostDefActionList *listB)
{
  size_t appendPos = listA ? listA->len : 0, appendLen = listB ? listB->len : 0;
  if (appendLen)
  {
    size_t newLen = appendPos + appendLen;
    if (!listA || listA->size < newLen)
    {
      enum
      {
        initialListSize = 1
      };
      size_t newSize = listA ? listA->size : initialListSize;
      while (newSize < newLen) newSize *= 2;
      size_t newAllocSize = sizeof(struct cdfPostDefActionList) + newSize * sizeof(struct cdfPostDefAction);
      listA = (struct cdfPostDefActionList *) Realloc(listA, newAllocSize);
      listA->size = newSize;
      listA->len = newLen;
    }
    else
      listA->len = newLen;
    struct cdfPostDefAction *restrict actionsA = listA->actions;
    const struct cdfPostDefAction *restrict actionsB = listB->actions;
    for (size_t i = 0; i < appendLen; ++i) actionsA[appendPos + i] = actionsB[i];
  }
  return listA;
}

void
cdfPostDefActionAddPutVal(struct cdfPostDefActionList **list_, int fileID, int ncvarid, const double *values,
                          void (*cleanup)(void *))
{
  struct cdfPostDefPutVar *delayedPutVals = (struct cdfPostDefPutVar *) Malloc(sizeof(*delayedPutVals));
  delayedPutVals->values.array = values;
  delayedPutVals->fileID = fileID;
  delayedPutVals->ncvarid = ncvarid;
  *list_ = cdfPostDefActionAdd(
      *list_, (struct cdfPostDefAction){ .data = (void *) delayedPutVals, .action = cdfDelayedPutVarDouble, .cleanup = cleanup });
}

static inline void
cdfPostDefActionAddPut1Int(struct cdfPostDefActionList **list_, int fileID, int ncvarid, int iVal, void (*cleanup)(void *))
{
  struct cdfPostDefPutVar *delayedPutVals = (struct cdfPostDefPutVar *) Malloc(sizeof(*delayedPutVals));
  delayedPutVals->values.int1 = iVal;
  delayedPutVals->fileID = fileID;
  delayedPutVals->ncvarid = ncvarid;
  *list_ = cdfPostDefActionAdd(
      *list_, (struct cdfPostDefAction){ .data = (void *) delayedPutVals, .action = cdfDelayedPutVarInt1, .cleanup = cleanup });
}

static void
cdfGridCompress(int fileID, int ncvarid, size_t gridsize, int filetype, int comptype, size_t *chunks)
{
  if (gridsize >= 32 && comptype == CDI_COMPRESS_ZIP
      && (filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C || filetype == CDI_FILETYPE_NCZARR))
  {
    cdf_def_var_chunking(fileID, ncvarid, NC_CHUNKED, chunks);
    int shuffle = 1, compLevel = 1;
    cdfDefVarDeflate(fileID, ncvarid, shuffle, compLevel);
  }
}

static struct cdfPostDefActionList *
cdfDefAxisCommon(stream_t *streamptr, int gridID, int gridIndex, int ndims, bool addVarToGrid,
                 const struct cdfDefGridAxisInqs *gridAxisInq, int axisKey, char axisLetter,
                 void (*finishCyclicBounds)(double *pbounds, SizeType dimlen, const double *pvals))
{
  int dimID = CDI_UNDEFID;
  SizeType dimlen = gridAxisInq->axisSize(gridID);
  nc_type xtype = grid_inq_xtype(gridID);

  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  bool hasVals = gridInqPropPresence(gridID, gridAxisInq->valsQueryKey);
  char dimname[CDI_MAX_NAME + 3];
  dimname[0] = 0;
  int length = sizeof(dimname);
  if (ndims && !hasVals) cdiInqKeyString(gridID, axisKey, CDI_KEY_DIMNAME, dimname, &length);

  for (int index = 0; index < gridIndex; ++index)
  {
    int gridID0 = cdfGridList[index].gridID;
    assert(gridID0 != CDI_UNDEFID);
    int gridType0 = gridInqType(gridID0);
    if (gridType0 == GRID_GAUSSIAN || gridType0 == GRID_LONLAT || gridType0 == GRID_PROJECTION || gridType0 == GRID_GENERIC)
    {
      SizeType dimlen0 = gridAxisInq->axisSize(gridID0);
      char dimname0[CDI_MAX_NAME];
      dimname0[0] = 0;
      length = sizeof(dimname0);
      if (dimname[0]) cdiInqKeyString(gridID0, axisKey, CDI_KEY_DIMNAME, dimname0, &length);
      bool lname = dimname0[0] ? str_is_equal(dimname, dimname0) : true;
      if (dimlen == dimlen0 && lname)
      {
        double (*inqVal)(int gridID, SizeType index) = gridAxisInq->axisVal;
        if (IS_EQUAL(inqVal(gridID0, 0), inqVal(gridID, 0)) && IS_EQUAL(inqVal(gridID0, dimlen - 1), inqVal(gridID, dimlen - 1)))
        {
          dimID = cdfGridList[index].ncIdList[(axisLetter == 'X') ? CDF_DIMID_X : CDF_DIMID_Y];
          break;
        }
      }
    }
  }

  struct cdfPostDefActionList *delayed = NULL;
  if (dimID == CDI_UNDEFID)
  {
    int ncvarid = CDI_UNDEFID;
    char axisname[CDI_MAX_NAME];
    length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, axisKey, CDI_KEY_NAME, axisname, &length);
    int fileID = streamptr->fileID;
    if (axisname[0] == 0) Error("axis name undefined!");

    checkGridName(axisname, fileID);
    size_t axisnameLen = strlen(axisname);

    bool switchNCMode = (streamptr->ncmode == 2 && (hasVals || ndims));
    if (switchNCMode)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    if (ndims)
    {
      if (dimname[0] == 0) strcpy(dimname, axisname);
      dimID = checkDimName(fileID, (size_t) dimlen, dimname);
      if (dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);
    }

    if (hasVals)
    {
      cdf_def_var(fileID, axisname, xtype, ndims, &dimID, &ncvarid);

      int chunkSize = 0;
      int chunkType = CDI_CHUNK_GRID;
      cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKTYPE, &chunkType);
      cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKSIZE, &chunkSize);
      if (chunkSize > 0) chunkType = CDI_CHUNK_AUTO;

      if (chunkType == CDI_CHUNK_GRID && dimlen > ChunkSizeLim) chunkType = CDI_CHUNK_LINES;

      size_t chunk = calc_chunksize_x(chunkType, chunkSize, (size_t) dimlen, true);
      cdfGridCompress(fileID, ncvarid, (size_t) dimlen, streamptr->filetype, streamptr->comptype, &chunk);

      cdfPutGridStdAtts(fileID, ncvarid, gridID, axisLetter);
      {
        char axisStr[2] = { axisLetter, '\0' };
        cdf_put_att_text(fileID, ncvarid, "axis", 1, axisStr);
      }
      cdfFuncPtrPostDefActionGridProp mycdfPostDefActionGridProp
          = (cdfFuncPtrPostDefActionGridProp) namespaceSwitchGet(NSSWITCH_CDF_POSTDEFACTION_GRID_PROP).func;
      mycdfPostDefActionGridProp(streamptr, gridID, ncvarid, gridAxisInq->valsQueryKey, &delayed);

      bool genBounds = false, hasBounds = gridInqPropPresence(gridID, gridAxisInq->bndsQueryKey);
      bool grid_is_cyclic = (gridIsCyclic(gridID) > 0);
      double *restrict pbounds;
      size_t nvertex = (size_t) gridInqNvertex(gridID);
      if (CDI_CMOR_Mode && grid_is_cyclic && !hasBounds)
      {
        const double *pvals = gridAxisInq->axisValsPtr(gridID);
        genBounds = true;
        nvertex = 2;
        pbounds = (double *) Malloc(2 * (size_t) dimlen * sizeof(double));
        for (size_t i = 0; i < (size_t) dimlen - 1; ++i)
        {
          pbounds[i * 2 + 1] = (pvals[i] + pvals[i + 1]) * 0.5;
          pbounds[i * 2 + 2] = (pvals[i] + pvals[i + 1]) * 0.5;
        }
        finishCyclicBounds(pbounds, dimlen, pvals);
      }
      else
        pbounds = (double *) gridAxisInq->axisBoundsPtr(gridID);

      int nvdimID = CDI_UNDEFID;
      if (pbounds)
      {
        if (nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR) cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);
      }
      if ((hasBounds || genBounds) && nvdimID != CDI_UNDEFID)
      {
        char boundsname[CDI_MAX_NAME];
        memcpy(boundsname, axisname, axisnameLen);
        boundsname[axisnameLen] = '_';
        memcpy(boundsname + axisnameLen + 1, bndsName, sizeof(bndsName));
        int dimIDs[2] = { dimID, nvdimID };
        int ncbvarid;
        cdf_def_var(fileID, boundsname, xtype, 2, dimIDs, &ncbvarid);
        cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), boundsname);
        cdfPostDefActionAddPutVal(&delayed, fileID, ncbvarid, pbounds,
                                  genBounds ? cdfDelayedPutVarDeepCleanup : (void (*)(void *))(void (*)(void)) memFree);
      }
    }

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }

    if (ndims == 0 || addVarToGrid) cdfGridList[gridIndex].ncIdList[(axisLetter == 'X') ? CDF_VARID_X : CDF_VARID_Y] = ncvarid;
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[(axisLetter == 'X') ? CDF_DIMID_X : CDF_DIMID_Y] = dimID;

  return delayed;
}

static void
finishCyclicXBounds(double *pbounds, SizeType dimlen, const double *pvals)
{
  pbounds[0] = (pvals[0] + pvals[dimlen - 1] - 360) * 0.5;
  pbounds[2 * dimlen - 1] = (pvals[dimlen - 1] + pvals[0] + 360) * 0.5;
}

static void
finishCyclicYBounds(double *pbounds, SizeType dimlen, const double *pvals)
{
  pbounds[0] = copysign(90.0, pvals[0]);
  pbounds[2 * dimlen - 1] = copysign(90.0, pvals[dimlen - 1]);
}

static struct cdfPostDefActionList *
cdfDefXaxis(stream_t *streamptr, int gridID, int gridIndex, int ndims, bool addVarToGrid)
{
  return cdfDefAxisCommon(streamptr, gridID, gridIndex, ndims, addVarToGrid, &gridInqsX, CDI_XAXIS, 'X', finishCyclicXBounds);
}

static struct cdfPostDefActionList *
cdfDefYaxis(stream_t *streamptr, int gridID, int gridIndex, int ndims, bool addVarToGrid)
{
  return cdfDefAxisCommon(streamptr, gridID, gridIndex, ndims, addVarToGrid, &gridInqsY, CDI_YAXIS, 'Y', finishCyclicYBounds);
}

static void
cdfDefGridReference(stream_t *streamptr, int gridID)
{
  int fileID = streamptr->fileID;

  int number = 0;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_NUMBEROFGRIDUSED, &number);
  if (number > 0) cdf_put_att_int(fileID, NC_GLOBAL, "number_of_grid_used", NC_INT, 1, &number);

  grid_t *gridptr = grid_to_pointer(gridID);
  const char *gridfile = cdiInqVarKeyStringPtr(&gridptr->keys, CDI_KEY_REFERENCEURI);
  if (gridfile && gridfile[0] != 0) cdf_put_att_text(fileID, NC_GLOBAL, "grid_file_uri", strlen(gridfile), gridfile);
}

static void
cdfDefGridUUID(stream_t *streamptr, int gridID)
{
  unsigned char uuid[CDI_UUID_SIZE] = { 0 };
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(gridID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
  if (!cdiUUIDIsNull(uuid))
  {
    char uuidStr[uuidNumHexChars + 1] = { 0 };
    if (cdiUUID2Str(uuid, uuidStr) == uuidNumHexChars)
    {
      int fileID = streamptr->fileID;
      // if (streamptr->ncmode == 2) cdf_redef(fileID);
      cdf_put_att_text(fileID, NC_GLOBAL, "uuidOfHGrid", uuidNumHexChars, uuidStr);
      // if (streamptr->ncmode == 2  cdf_enddef(fileID, streamptr->self);
    }
  }
}

void
cdfPostDefActionGridProp(stream_t *streamptr, int gridID, int ncvarid, enum gridPropInq gridProp,
                         struct cdfPostDefActionList **delayed)
{
  const void *valsPtr = NULL;
  switch (gridProp)
  {
    case GRID_PROP_MASK:
    case GRID_PROP_MASK_GME: Error("unsupported key: %d", (int) gridProp); break;
    case GRID_PROP_XVALS: valsPtr = gridInqXvalsPtr(gridID); break;
    case GRID_PROP_YVALS: valsPtr = gridInqYvalsPtr(gridID); break;
    case GRID_PROP_AREA: valsPtr = gridInqAreaPtr(gridID); break;
    case GRID_PROP_XBOUNDS: valsPtr = gridInqXboundsPtr(gridID); break;
    case GRID_PROP_YBOUNDS: valsPtr = gridInqYboundsPtr(gridID); break;
  }
  cdfPostDefActionAddPutVal(delayed, streamptr->fileID, ncvarid, (const double *) valsPtr,
                            (void (*)(void *))(void (*)(void)) memFree);
}

static int
cdfDefIrregularGridAxisSetup(stream_t *streamptr, int gridID, nc_type xtype, int varID, size_t dimlens, int ndims, int dimIDs[],
                             size_t *chunks, const struct cdfDefGridAxisInqs *inqs, struct cdfPostDefActionList **delayed)
{
  int ncvarid = CDI_UNDEFID;
  int fileID = streamptr->fileID;
  if (gridInqPropPresence(gridID, inqs->valsQueryKey))
  {
    char axisname[CDI_MAX_NAME];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, varID, CDI_KEY_NAME, axisname, &length);
    checkGridName(axisname, fileID);
    cdf_def_var(fileID, axisname, xtype, ndims - 1, dimIDs, &ncvarid);
    cdfGridCompress(fileID, ncvarid, dimlens, streamptr->filetype, streamptr->comptype, chunks);

    cdfPutGridStdAtts(fileID, ncvarid, gridID, inqs->axisSym);
    cdfFuncPtrPostDefActionGridProp mycdfPostDefActionGridProp
        = (cdfFuncPtrPostDefActionGridProp) namespaceSwitchGet(NSSWITCH_CDF_POSTDEFACTION_GRID_PROP).func;
    mycdfPostDefActionGridProp(streamptr, gridID, ncvarid, inqs->valsQueryKey, delayed);

    // attribute for Panoply
    if (!CDI_CMOR_Mode && ndims == 3) cdf_put_att_text(fileID, ncvarid, "_CoordinateAxisType", 3, inqs->axisPanoplyName);

    if (gridInqPropPresence(gridID, inqs->bndsQueryKey) && dimIDs[ndims - 1] != CDI_UNDEFID)
    {
      size_t axisnameLen = strlen(axisname);
      axisname[axisnameLen] = '_';
      memcpy(axisname + axisnameLen + 1, bndsName, sizeof(bndsName));
      int ncbvarid;
      cdf_def_var(fileID, axisname, xtype, ndims, dimIDs, &ncbvarid);
      cdfGridCompress(fileID, ncbvarid, dimlens, streamptr->filetype, streamptr->comptype, chunks);

      cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), axisname);
      mycdfPostDefActionGridProp(streamptr, gridID, ncbvarid, inqs->bndsQueryKey, delayed);
    }
  }
  return ncvarid;
}

struct cdfDefIrregularGridCommonIDs
{
  int xdimID, ydimID, ncxvarid, ncyvarid, ncavarid;
  struct cdfPostDefActionList *delayed;
};

static struct cdfDefIrregularGridCommonIDs
cdfDefIrregularGridCommon(stream_t *streamptr, int gridID, size_t xsize, size_t ysize, int ndims, const char *xdimname_default,
                          size_t nvertex, const char *vdimname_default, bool setVdimname)
{
  nc_type xtype = grid_inq_xtype(gridID);
  int xdimID = CDI_UNDEFID;
  int ydimID = CDI_UNDEFID;
  int fileID = streamptr->fileID;

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    cdf_redef(fileID);
    streamptr->ncmode = 1;
  }

  {
    char xdimname[CDI_MAX_NAME + 3];
    int length = sizeof(xdimname);
    cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_DIMNAME, xdimname, &length);
    if (xdimname[0] == 0) strcpy(xdimname, xdimname_default);
    xdimID = checkDimName(fileID, xsize, xdimname);
    if (xdimID == CDI_UNDEFID) cdf_def_dim(fileID, xdimname, xsize, &xdimID);
  }

  if (ndims == 3)
  {
    char ydimname[CDI_MAX_NAME + 3];
    int length = sizeof(ydimname);
    cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_DIMNAME, ydimname, &length);
    if (ydimname[0] == 0)
    {
      ydimname[0] = 'y';
      ydimname[1] = 0;
    }
    ydimID = checkDimName(fileID, ysize, ydimname);
    if (ydimID == CDI_UNDEFID) cdf_def_dim(fileID, ydimname, ysize, &ydimID);
  }

  int nvdimID = CDI_UNDEFID;
  int dimIDs[3];
  dimIDs[ndims - 1] = CDI_UNDEFID;
  if (setVdimname)
  {
    char vdimname[CDI_MAX_NAME + 3];
    int length = CDI_MAX_NAME;
    cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_VDIMNAME, vdimname, &length);
    if (vdimname[0] == 0) strcpy(vdimname, vdimname_default);
    nvdimID = dimIDs[ndims - 1] = checkDimName(fileID, nvertex, vdimname);
    if (nvdimID == CDI_UNDEFID) cdf_def_dim(fileID, vdimname, nvertex, dimIDs + ndims - 1);
  }

  size_t gridsize = xsize * ysize;
  size_t chunks[3] = { 1, 1, 1 };
  int chunkSize = 0;
  int chunkType = CDI_CHUNK_GRID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKTYPE, &chunkType);
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_CHUNKSIZE, &chunkSize);
  if (chunkSize > 0 && ydimID == CDI_UNDEFID) chunkType = CDI_CHUNK_AUTO;

  if (chunkType == CDI_CHUNK_GRID && gridsize > ChunkSizeLim) chunkType = CDI_CHUNK_LINES;

  if (ndims == 3)
  {
    chunks[0] = calc_chunksize_y(chunkType, gridsize, xsize, ysize);
    chunks[1] = calc_chunksize_x(chunkType, chunkSize, xsize, (ydimID == CDI_UNDEFID));
    dimIDs[0] = ydimID;
    dimIDs[1] = xdimID;
  }
  else  // ndims == 2
  {
    chunks[0] = calc_chunksize_x(chunkType, chunkSize, xsize, (ydimID == CDI_UNDEFID));
    dimIDs[0] = xdimID;
    cdfDefGridReference(streamptr, gridID);
    cdfDefGridUUID(streamptr, gridID);
  }

  struct cdfPostDefActionList *delayed = NULL;
  int ncxvarid
      = cdfDefIrregularGridAxisSetup(streamptr, gridID, xtype, CDI_XAXIS, gridsize, ndims, dimIDs, chunks, &gridInqsX, &delayed);
  int ncyvarid
      = cdfDefIrregularGridAxisSetup(streamptr, gridID, xtype, CDI_YAXIS, gridsize, ndims, dimIDs, chunks, &gridInqsY, &delayed);

  int ncavarid = CDI_UNDEFID;
  if (gridInqPropPresence(gridID, GRID_PROP_AREA))
  {
    static const char axisname_[] = "cell_area";
    static const char units[] = "m2";
    static const char longname[] = "area of grid cell";
    static const char stdname[] = "cell_area";

    cdf_def_var(fileID, axisname_, xtype, ndims - 1, dimIDs, &ncavarid);

    cdf_put_att_text(fileID, ncavarid, "standard_name", sizeof(stdname) - 1, stdname);
    cdf_put_att_text(fileID, ncavarid, "long_name", sizeof(longname) - 1, longname);
    cdf_put_att_text(fileID, ncavarid, "units", sizeof(units) - 1, units);
    cdfFuncPtrPostDefActionGridProp mycdfPostDefActionGridProp
        = (cdfFuncPtrPostDefActionGridProp) namespaceSwitchGet(NSSWITCH_CDF_POSTDEFACTION_GRID_PROP).func;
    mycdfPostDefActionGridProp(streamptr, gridID, ncavarid, GRID_PROP_AREA, &delayed);
  }

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  return (struct cdfDefIrregularGridCommonIDs){
    .xdimID = xdimID, .ydimID = ydimID, .ncxvarid = ncxvarid, .ncyvarid = ncyvarid, .ncavarid = ncavarid, .delayed = delayed
  };
}

static struct cdfPostDefActionList *
cdfDefCurvilinear(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = gridInqSize(gridID);
  SizeType xdimlen = gridInqXsize(gridID);
  SizeType ydimlen = gridInqYsize(gridID);

  int xdimID = CDI_UNDEFID, ydimID = CDI_UNDEFID;
  int ncxvarid = CDI_UNDEFID, ncyvarid = CDI_UNDEFID, ncavarid = CDI_UNDEFID;

  size_t ofs = 0;
  do {
    struct idSearch search
        = cdfSearchIDBySize(ofs, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_CURVILINEAR, dimlen, gridInqType, gridInqSize);
    size_t index = search.foundIdx;
    if (index != SIZE_MAX)
    {
      int gridID0 = cdfGridList[index].gridID;
      if (IS_EQUAL(gridInqXval(gridID0, 0), gridInqXval(gridID, 0))
          && IS_EQUAL(gridInqXval(gridID0, dimlen - 1), gridInqXval(gridID, dimlen - 1))
          && IS_EQUAL(gridInqYval(gridID0, 0), gridInqYval(gridID, 0))
          && IS_EQUAL(gridInqYval(gridID0, dimlen - 1), gridInqYval(gridID, dimlen - 1)))
      {
        xdimID = cdfGridList[index].ncIdList[CDF_DIMID_X];
        ydimID = cdfGridList[index].ncIdList[CDF_DIMID_Y];
        ncxvarid = cdfGridList[index].ncIdList[CDF_VARID_X];
        ncyvarid = cdfGridList[index].ncIdList[CDF_VARID_Y];
        break;
      }
      ofs = search.foundIdx;
      if (ofs < (size_t) gridIndex) continue;
    }
  } while (false);

  struct cdfPostDefActionList *delayed = NULL;
  if (xdimID == CDI_UNDEFID || ydimID == CDI_UNDEFID)
  {
    struct cdfDefIrregularGridCommonIDs createdIDs = cdfDefIrregularGridCommon(
        streamptr, gridID, (size_t) xdimlen, (size_t) ydimlen, 3, "x", 4, "nv4",
        gridInqPropPresence(gridID, GRID_PROP_XBOUNDS) || gridInqPropPresence(gridID, GRID_PROP_YBOUNDS));
    xdimID = createdIDs.xdimID;
    ydimID = createdIDs.ydimID;
    ncxvarid = createdIDs.ncxvarid;
    ncyvarid = createdIDs.ncyvarid;
    ncavarid = createdIDs.ncavarid;
    delayed = createdIDs.delayed;
  }

  CdfGrid *cdfGrid = &cdfGridList[gridIndex];
  cdfGrid->gridID = gridID;
  cdfGrid->ncIdList[CDF_DIMID_X] = xdimID;
  cdfGrid->ncIdList[CDF_DIMID_Y] = ydimID;
  cdfGrid->ncIdList[CDF_VARID_X] = ncxvarid;
  cdfGrid->ncIdList[CDF_VARID_Y] = ncyvarid;
  cdfGrid->ncIdList[CDF_VARID_A] = ncavarid;

  return delayed;
}

static struct cdfPostDefActionList *
cdfDefUnstructured(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  SizeType dimlen = gridInqSize(gridID);

  int dimID = CDI_UNDEFID;
  int ncxvarid = CDI_UNDEFID, ncyvarid = CDI_UNDEFID, ncavarid = CDI_UNDEFID;

  size_t ofs = 0;
  do {
    struct idSearch search
        = cdfSearchIDBySize(ofs, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_UNSTRUCTURED, dimlen, gridInqType, gridInqSize);
    size_t index = search.foundIdx;
    if (index != SIZE_MAX)
    {
      int gridID0 = cdfGridList[index].gridID;
      if (gridInqNvertex(gridID0) == gridInqNvertex(gridID) && IS_EQUAL(gridInqXval(gridID0, 0), gridInqXval(gridID, 0))
          && IS_EQUAL(gridInqXval(gridID0, dimlen - 1), gridInqXval(gridID, dimlen - 1))
          && IS_EQUAL(gridInqYval(gridID0, 0), gridInqYval(gridID, 0))
          && IS_EQUAL(gridInqYval(gridID0, dimlen - 1), gridInqYval(gridID, dimlen - 1)))
      {
        dimID = cdfGridList[index].ncIdList[CDF_DIMID_X];
        ncxvarid = cdfGridList[index].ncIdList[CDF_VARID_X];
        ncyvarid = cdfGridList[index].ncIdList[CDF_VARID_Y];
        ncavarid = cdfGridList[index].ncIdList[CDF_VARID_A];
        break;
      }
      ofs = search.foundIdx;
      if (ofs < (size_t) gridIndex) continue;
    }
  } while (false);

  struct cdfPostDefActionList *delayed = NULL;
  if (dimID == CDI_UNDEFID)
  {
    size_t nvertex = (size_t) gridInqNvertex(gridID);
    struct cdfDefIrregularGridCommonIDs createdIDs
        = cdfDefIrregularGridCommon(streamptr, gridID, (size_t) dimlen, 1, 2, "ncells", nvertex, "vertices", nvertex > 0);
    dimID = createdIDs.xdimID;
    ncxvarid = createdIDs.ncxvarid;
    ncyvarid = createdIDs.ncyvarid;
    ncavarid = createdIDs.ncavarid;
    delayed = createdIDs.delayed;
  }

  CdfGrid *cdfGrid = &cdfGridList[gridIndex];
  cdfGrid->gridID = gridID;
  cdfGrid->ncIdList[CDF_DIMID_X] = dimID;
  cdfGrid->ncIdList[CDF_VARID_X] = ncxvarid;
  cdfGrid->ncIdList[CDF_VARID_Y] = ncyvarid;
  cdfGrid->ncIdList[CDF_VARID_A] = ncavarid;

  return delayed;
}

struct attTxtTab
{
  const char *txt;
  size_t txtLen;
};

struct attTxtTab2
{
  const char *attName, *attVal;
  size_t valLen;
};

static struct cdfPostDefActionList *
cdf_def_vct_echam(stream_t *streamptr, int zaxisID)
{
  int type = zaxisInqType(zaxisID);

  int ilev;
  struct cdfPostDefActionList *delayed = NULL;
  if ((type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF) && (ilev = zaxisInqVctSize(zaxisID) / 2) != 0)
  {
    int mlev = ilev - 1;
    CdfInfo *cdfInfo = &(streamptr->cdfInfo);
    if (cdfInfo->vct.ilev > 0)
    {
      if (cdfInfo->vct.ilev != ilev) Error("More than one VCT for each file unsupported!");
      return delayed;
    }

    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    int ncdimid = -1, ncdimid2 = -1;
    int hyaiid, hybiid, hyamid = -1, hybmid = -1;

    cdf_def_dim(fileID, "nhyi", (size_t) ilev, &ncdimid2);
    cdf_def_var(fileID, "hyai", NC_DOUBLE, 1, &ncdimid2, &hyaiid);
    cdf_def_var(fileID, "hybi", NC_DOUBLE, 1, &ncdimid2, &hybiid);
    if (mlev > 0)
    {
      cdf_def_dim(fileID, "nhym", (size_t) mlev, &ncdimid);
      cdf_def_var(fileID, "hyam", NC_DOUBLE, 1, &ncdimid, &hyamid);
      cdf_def_var(fileID, "hybm", NC_DOUBLE, 1, &ncdimid, &hybmid);
    }

    cdfInfo->vct.ilev = ilev;
    cdfInfo->vct.mlev = mlev;
    cdfInfo->vct.mlevID = ncdimid;
    cdfInfo->vct.ilevID = ncdimid2;

    {
      static const char lname_n[] = "long_name", units_n[] = "units", lname_v_ai[] = "hybrid A coefficient at layer interfaces",
                        units_v_ai[] = "Pa", lname_v_bi[] = "hybrid B coefficient at layer interfaces", units_v_bi[] = "1";
      static const struct attTxtTab2 tab[] = {
        { lname_n, lname_v_ai, sizeof(lname_v_ai) - 1 },
        { units_n, units_v_ai, sizeof(units_v_ai) - 1 },
        { lname_n, lname_v_bi, sizeof(lname_v_bi) - 1 },
        { units_n, units_v_bi, sizeof(units_v_bi) - 1 },
      };
      enum
      {
        tabLen = sizeof(tab) / sizeof(tab[0])
      };
      int ids[tabLen] = { hyaiid, hyaiid, hybiid, hybiid };
      for (size_t i = 0; i < tabLen; ++i) cdf_put_att_text(fileID, ids[i], tab[i].attName, tab[i].valLen, tab[i].attVal);
    }

    {
      static const char lname_n[] = "long_name", units_n[] = "units", lname_v_am[] = "hybrid A coefficient at layer midpoints",
                        units_v_am[] = "Pa", lname_v_bm[] = "hybrid B coefficient at layer midpoints", units_v_bm[] = "1";
      static const struct attTxtTab2 tab[] = {
        { lname_n, lname_v_am, sizeof(lname_v_am) - 1 },
        { units_n, units_v_am, sizeof(units_v_am) - 1 },
        { lname_n, lname_v_bm, sizeof(lname_v_bm) - 1 },
        { units_n, units_v_bm, sizeof(units_v_bm) - 1 },
      };
      enum
      {
        tabLen = sizeof(tab) / sizeof(tab[0])
      };
      int ids[tabLen] = { hyamid, hyamid, hybmid, hybmid };
      for (size_t i = 0; i < tabLen; ++i) cdf_put_att_text(fileID, ids[i], tab[i].attName, tab[i].valLen, tab[i].attVal);
    }

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }

    const double *vctptr = zaxisInqVctPtr(zaxisID);

    cdfPostDefActionAddPutVal(&delayed, fileID, hyaiid, vctptr, (void (*)(void *))(void (*)(void)) memFree);
    cdfPostDefActionAddPutVal(&delayed, fileID, hybiid, vctptr + ilev, (void (*)(void *))(void (*)(void)) memFree);
    {
      double *restrict amidVal = (double *) Malloc((size_t) mlev * sizeof(*amidVal));
      for (size_t i = 0; i < (size_t) mlev; ++i) amidVal[i] = (vctptr[i] + vctptr[i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hyamid, amidVal, cdfDelayedPutVarDeepCleanup);
    }
    {
      double *restrict bmidVal = (double *) Malloc((size_t) mlev * sizeof(*bmidVal));
      for (size_t i = 0; i < (size_t) mlev; ++i) bmidVal[i] = (vctptr[(size_t) ilev + i] + vctptr[(size_t) ilev + i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hybmid, bmidVal, cdfDelayedPutVarDeepCleanup);
    }
  }
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_vct_cf(stream_t *streamptr, int zaxisID, int nclevID, int ncbndsID, int p0status, double p0value)
{
  int type = zaxisInqType(zaxisID);

  struct cdfPostDefActionList *delayed = NULL;
  int ilev;
  if ((type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF) && (ilev = zaxisInqVctSize(zaxisID) / 2) != 0)
  {
    int mlev = ilev - 1;
    CdfInfo *cdfInfo = &(streamptr->cdfInfo);
    if (cdfInfo->vct.ilev > 0)
    {
      if (cdfInfo->vct.ilev != ilev) Error("more than one VCT for each file unsupported!");
      return delayed;
    }

    int fileID = streamptr->fileID;

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      cdf_redef(fileID);
      streamptr->ncmode = 1;
    }

    int dimIDs[2] = { nclevID, ncbndsID };

    cdfInfo->vct.mlev = mlev;
    cdfInfo->vct.ilev = ilev;
    cdfInfo->vct.mlevID = nclevID;
    cdfInfo->vct.ilevID = nclevID;

    int hyamid, hybmid;
    cdf_def_var(fileID, (p0status == 0) ? "a" : "ap", NC_DOUBLE, 1, dimIDs, &hyamid);
    cdf_def_var(fileID, "b", NC_DOUBLE, 1, dimIDs, &hybmid);

    {
      static const char anametab[][10] = { "long_name", "units" };
      static const char lname_v_a[] = "vertical coordinate formula term: ap(k)",
                        lname_v_b[] = "vertical coordinate formula term: b(k)", units_v_a[] = "Pa", units_v_b[] = "1";
      static struct attTxtTab attvtab[][2] = { { { lname_v_a, sizeof(lname_v_a) - 1 }, { units_v_a, sizeof(units_v_a) - 1 } },
                                               { { lname_v_b, sizeof(lname_v_b) - 1 }, { units_v_b, sizeof(units_v_b) - 1 } } };
      int termid[] = { hyamid, hybmid };
      enum
      {
        numTerms = sizeof(termid) / sizeof(termid[0]),
        numAtts = sizeof(anametab) / sizeof(anametab[0]),
      };
      for (size_t termIdx = 0; termIdx < numTerms; ++termIdx)
        for (size_t attIdx = 0; attIdx < numAtts; ++attIdx)
          cdf_put_att_text(fileID, termid[termIdx], anametab[attIdx], attvtab[termIdx][attIdx].txtLen,
                           attvtab[termIdx][attIdx].txt);
    }
    double *restrict vctptr = (double *) zaxisInqVctPtr(zaxisID);
    if (p0status == 0 && IS_NOT_EQUAL(p0value, 0))
    {
      double *restrict temp = (double *) Malloc((size_t) 2 * ilev * sizeof(*temp));
      memcpy(temp, vctptr, (size_t) 2 * ilev * sizeof(*temp));
      for (size_t i = 0; i < (size_t) ilev; ++i) temp[i] /= p0value;
      vctptr = temp;
    }

    {
      double *restrict mlevValA = (double *) Malloc((size_t) mlev * sizeof(*mlevValA));
      for (size_t i = 0; i < (size_t) mlev; ++i) mlevValA[i] = (vctptr[i] + vctptr[i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hyamid, mlevValA, cdfDelayedPutVarDeepCleanup);
    }
    {
      double *restrict mlevValB = (double *) Malloc((size_t) mlev * sizeof(*mlevValB));
      for (size_t i = 0; i < (size_t) mlev; ++i) mlevValB[i] = (vctptr[(size_t) ilev + i] + vctptr[(size_t) ilev + i + 1]) * 0.5;
      cdfPostDefActionAddPutVal(&delayed, fileID, hybmid, mlevValB, cdfDelayedPutVarDeepCleanup);
    }

    if (ncbndsID != -1)
    {
      int hyaiid, hybiid;
      cdf_def_var(fileID, (p0status == 0) ? "a_bnds" : "ap_bnds", NC_DOUBLE, 2, dimIDs, &hyaiid);
      cdf_def_var(fileID, "b_bnds", NC_DOUBLE, 2, dimIDs, &hybiid);
      static const char anametab[][10] = { "long_name", "units" };
      static const char lname_v_a[] = "vertical coordinate formula term: ap(k+1/2)",
                        lname_v_b[] = "vertical coordinate formula term: b(k+1/2)", units_v_a[] = "Pa", units_v_b[] = "1";
      static struct attTxtTab attvtab[][2] = { { { lname_v_a, sizeof(lname_v_a) - 1 }, { units_v_a, sizeof(units_v_a) - 1 } },
                                               { { lname_v_b, sizeof(lname_v_b) - 1 }, { units_v_b, sizeof(units_v_b) - 1 } } };
      int termid[] = { hyaiid, hybiid };
      enum
      {
        numTerms = sizeof(termid) / sizeof(termid[0]),
        numAtts = sizeof(anametab) / sizeof(anametab[0]),
      };
      for (size_t termIdx = 0; termIdx < numTerms; ++termIdx)
        for (size_t attIdx = 0; attIdx < numAtts; ++attIdx)
          cdf_put_att_text(fileID, termid[termIdx], anametab[attIdx], attvtab[termIdx][attIdx].txtLen,
                           attvtab[termIdx][attIdx].txt);

      {
        double *restrict ilevValA = (double *) Malloc((size_t) mlev * 2 * sizeof(*ilevValA));
        for (size_t i = 0; i < (size_t) mlev; ++i)
        {
          ilevValA[2 * i] = vctptr[i];
          ilevValA[2 * i + 1] = vctptr[i + 1];
        }
        cdfPostDefActionAddPutVal(&delayed, fileID, hyaiid, ilevValA, cdfDelayedPutVarDeepCleanup);
      }
      {
        double *restrict ilevValB = (double *) Malloc((size_t) mlev * 2 * sizeof(*ilevValB));
        for (size_t i = 0; i < (size_t) mlev; ++i)
        {
          ilevValB[2 * i] = vctptr[(size_t) ilev + i];
          ilevValB[2 * i + 1] = vctptr[(size_t) ilev + i + 1];
        }
        cdfPostDefActionAddPutVal(&delayed, fileID, hybiid, ilevValB, cdfDelayedPutVarDeepCleanup);
      }
    }
    if (p0status == 0 && IS_NOT_EQUAL(p0value, 0)) Free(vctptr);

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }
  }
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_zaxis_hybrid_echam(stream_t *streamptr, int type, int *ncvaridp, int zaxisID, int zaxisindex, int xtype, size_t dimlen,
                           int *dimID, char *axisname)
{
  int fileID = streamptr->fileID;
  struct cdfPostDefActionList *delayed = NULL;

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    streamptr->ncmode = 1;
    cdf_redef(fileID);
  }

  cdf_def_dim(fileID, axisname, dimlen, dimID);
  cdf_def_var(fileID, axisname, (nc_type) xtype, 1, dimID, ncvaridp);
  int ncvarid = *ncvaridp;

  {
    static const char sname[] = "hybrid_sigma_pressure";
    cdf_put_att_text(fileID, ncvarid, "standard_name", sizeof(sname) - 1, sname);
  }
  {
    static const char *attName[] = { "long_name", "formula", "formula_terms" };
    enum
    {
      nAtt = sizeof(attName) / sizeof(attName[0])
    };
    static const char lname_m[] = "hybrid level at layer midpoints", formula_m[] = "hyam hybm (mlev=hyam+hybm*aps)",
                      fterms_m[] = "ap: hyam b: hybm ps: aps", lname_i[] = "hybrid level at layer interfaces",
                      formula_i[] = "hyai hybi (ilev=hyai+hybi*aps)", fterms_i[] = "ap: hyai b: hybi ps: aps";
    static const struct attTxtTab tab[2][nAtt]
        = { { { lname_i, sizeof(lname_i) - 1 }, { formula_i, sizeof(formula_i) - 1 }, { fterms_i, sizeof(fterms_i) - 1 } },
            { { lname_m, sizeof(lname_m) - 1 }, { formula_m, sizeof(formula_m) - 1 }, { fterms_m, sizeof(fterms_m) - 1 } } };

    size_t tabSelect = type == ZAXIS_HYBRID;
    for (size_t i = 0; i < nAtt; ++i)
      cdf_put_att_text(fileID, ncvarid, attName[i], tab[tabSelect][i].txtLen, tab[tabSelect][i].txt);
  }

  {
    static const char units[] = "level";
    cdf_put_att_text(fileID, ncvarid, "units", sizeof(units) - 1, units);
  }
  {
    static const char direction[] = "down";
    cdf_put_att_text(fileID, ncvarid, "positive", sizeof(direction) - 1, direction);
  }

  if (zaxisInqLevels(zaxisID, NULL))
    cdfPostDefActionAddPutVal(&delayed, fileID, ncvarid, zaxisInqLevelsPtr(zaxisID), (void (*)(void *))(void (*)(void)) memFree);

  {
    struct cdfPostDefActionList *delayedVct = cdf_def_vct_echam(streamptr, zaxisID);
    delayed = cdfPostDefActionConcat(delayed, delayedVct);
    Free(delayedVct);
  }

  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  if (*dimID == CDI_UNDEFID) cdfInfo->zaxisIdList[zaxisindex] = (type == ZAXIS_HYBRID) ? cdfInfo->vct.mlevID : cdfInfo->vct.ilevID;

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_zaxis_hybrid_cf(stream_t *streamptr, int type, int *ncvaridp, int zaxisID, int zaxisindex, int xtype, size_t dimlen,
                        int *dimID, char *axisname)
{
  char psname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_PSNAME, psname, &length);
  if (psname[0] == 0) strcpy(psname, "ps");

  int fileID = streamptr->fileID;

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    streamptr->ncmode = 1;
    cdf_redef(fileID);
  }

  char p0name[CDI_MAX_NAME];
  p0name[0] = 0;
  static double p0value = 1;
  int p0varid = CDI_UNDEFID;
  int p0status = cdiInqKeyFloat(zaxisID, CDI_GLOBAL, CDI_KEY_P0VALUE, &p0value);
  if (p0status == CDI_NOERR)
  {
    length = CDI_MAX_NAME;
    cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_P0NAME, p0name, &length);
    if (p0name[0] == 0) strcpy(p0name, "p0");
    cdf_def_var(fileID, p0name, NC_DOUBLE, 0, 0, &p0varid);
    static const char longname[] = "reference pressure";
    cdf_put_att_text(fileID, p0varid, "long_name", sizeof(longname) - 1, longname);
    static const char units[] = "Pa";
    cdf_put_att_text(fileID, p0varid, "units", sizeof(units) - 1, units);
  }

  char zname[CDI_MAX_NAME];
  char zlongname[CDI_MAX_NAME];
  char zunits[CDI_MAX_NAME];
  length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, zname, &length);
  if (zname[0]) strcpy(axisname, zname);
  zlongname[0] = 0;
  size_t zlongnameLen;
  if (zlongname[0] == 0)
  {
    static const char default_zlongname[] = "hybrid sigma pressure coordinate";
    memcpy(zlongname, default_zlongname, sizeof(default_zlongname));
    zlongnameLen = sizeof(default_zlongname) - 1;
  }
  else
    zlongnameLen = strlen(zlongname);
  length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, zunits, &length);
  size_t zunitsLen;
  if (zunits[0] == 0)
  {
    zunits[0] = '1';
    zunits[1] = '\0';
    zunitsLen = 1;
  }
  else
    zunitsLen = strlen(zunits);

  cdf_def_dim(fileID, axisname, dimlen, dimID);
  cdf_def_var(fileID, axisname, (nc_type) xtype, 1, dimID, ncvaridp);
  int ncvarid = *ncvaridp;

  {
    static const char sname[] = "standard_name", lname[] = "long_name", sname_v[] = "atmosphere_hybrid_sigma_pressure_coordinate",
                      axis[] = "axis", axis_v[] = "Z", direction[] = "positive", direction_v[] = "down", units[] = "units";
    struct attTxtTab2 tab[] = {
      { sname, sname_v, sizeof(sname_v) - 1 },
      { axis, axis_v, sizeof(axis_v) - 1 },
      { direction, direction_v, sizeof(direction_v) - 1 },
      { units, zunits, zunitsLen },
      { lname, zlongname, zlongnameLen },
    };
    enum
    {
      nAtt = sizeof(tab) / sizeof(tab[0])
    };
    for (size_t i = 0; i < nAtt; ++i) cdf_put_att_text(fileID, ncvarid, tab[i].attName, tab[i].valLen, tab[i].attVal);
  }

  size_t len = 0;
  char txt[CDI_MAX_NAME * 2 + 30];
  if (p0status == 0)
    len = (size_t) (snprintf(txt, sizeof(txt), "%s%s %s%s", "a: a b: b p0: ", p0name, "ps: ", psname));
  else
    len = (size_t) (snprintf(txt, sizeof(txt), "%s%s", "ap: ap b: b ps: ", psname));
  cdf_put_att_text(fileID, ncvarid, "formula_terms", len, txt);

  int ncbvarid = CDI_UNDEFID;
  int nvdimID = CDI_UNDEFID;

  double *buffer = (double *) Malloc(2 * dimlen * sizeof(double));
  double *lbounds = buffer;
  double *ubounds = buffer + dimlen;
  double *restrict levels;

  bool hasLevels = zaxisInqLevels(zaxisID, NULL) != 0;
  if (hasLevels) { levels = (double *) zaxisInqLevelsPtr(zaxisID); }
  else
  {
    levels = (double *) Malloc(sizeof(*levels) * dimlen);
    for (size_t i = 0; i < dimlen; ++i) levels[i] = (double) (i + 1);
  }

  if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
  {
    zaxisInqLbounds(zaxisID, lbounds);
    zaxisInqUbounds(zaxisID, ubounds);
  }
  else
  {
    for (size_t i = 0; i < dimlen; ++i) lbounds[i] = levels[i];
    for (size_t i = 0; i < dimlen - 1; ++i) ubounds[i] = levels[i + 1];
    ubounds[dimlen - 1] = levels[dimlen - 1] + 1;
  }

  // if ( zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL) )
  {
    size_t nvertex = 2;
    if (dimlen > 1 && nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR) cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);

    if (nvdimID != CDI_UNDEFID)
    {
      size_t axisnameLen = strlen(axisname);
      axisname[axisnameLen] = '_';
      memcpy(axisname + axisnameLen + 1, bndsName, sizeof(bndsName));
      axisnameLen += sizeof(bndsName);
      int dimIDs[2] = { *dimID, nvdimID };
      cdf_def_var(fileID, axisname, (nc_type) xtype, 2, dimIDs, &ncbvarid);
      cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen, axisname);
      size_t formulatermsLen;
      if (p0status == 0)
        formulatermsLen = (size_t) (snprintf(txt, sizeof(txt), "%s%s %s%s", "a: a_bnds b: b_bnds p0: ", p0name, "ps: ", psname));
      else
        formulatermsLen = (size_t) (snprintf(txt, sizeof(txt), "%s%s", "ap: ap_bnds b: b_bnds ps: ", psname));

      {
        static const char sname[] = "standard_name", sname_v[] = "atmosphere_hybrid_sigma_pressure_coordinate",
                          formulaterms[] = "formula_terms", units[] = "units";
        struct attTxtTab2 tab[] = {
          { sname, sname_v, sizeof(sname_v) - 1 },
          { units, zunits, zunitsLen },
          { formulaterms, txt, formulatermsLen },
        };
        enum
        {
          nAtt = sizeof(tab) / sizeof(tab[0])
        };
        for (size_t i = 0; i < nAtt; ++i) cdf_put_att_text(fileID, ncbvarid, tab[i].attName, tab[i].valLen, tab[i].attVal);
      }
    }
  }

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  struct cdfPostDefActionList *delayed = NULL;

  if (p0varid != CDI_UNDEFID)
    cdfPostDefActionAddPutVal(&delayed, fileID, p0varid, &p0value, (void (*)(void *))(void (*)(void)) memFree);

  cdfPostDefActionAddPutVal(&delayed, fileID, ncvarid, levels,
                            hasLevels ? (void (*)(void *))(void (*)(void)) memFree : cdfDelayedPutVarDeepCleanup);

  if (ncbvarid != CDI_UNDEFID)
  {
    double *zbounds = (double *) Malloc(2 * dimlen * sizeof(*zbounds));
    for (size_t i = 0; i < dimlen; ++i)
    {
      zbounds[2 * i] = lbounds[i];
      zbounds[2 * i + 1] = ubounds[i];
    }
    cdfPostDefActionAddPutVal(&delayed, fileID, ncbvarid, zbounds, cdfDelayedPutVarDeepCleanup);
  }

  {
    struct cdfPostDefActionList *delayedVct = cdf_def_vct_cf(streamptr, zaxisID, *dimID, nvdimID, p0status, p0value);
    delayed = cdfPostDefActionConcat(delayed, delayedVct);
    Free(delayedVct);
  }

  CdfInfo *cdfInfo = &(streamptr->cdfInfo);
  if (*dimID == CDI_UNDEFID) cdfInfo->zaxisIdList[zaxisindex] = (type == ZAXIS_HYBRID) ? cdfInfo->vct.mlevID : cdfInfo->vct.ilevID;

  free(buffer);
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_zaxis_hybrid(stream_t *streamptr, int type, int *ncvarid, int zaxisID, int zaxisindex, int xtype, size_t dimlen, int *dimID,
                     char *axisname)
{
  bool isHybridEcham = ((!CDI_CMOR_Mode && CDI_Convention == CDI_CONVENTION_ECHAM) || type == ZAXIS_HYBRID_HALF);
  struct cdfPostDefActionList *(*def_zaxis_hybrid_delegate)(stream_t * streamptr, int type, int *ncvarid, int zaxisID,
                                                            int zaxisindex, int xtype, size_t dimlen, int *dimID, char *axisname)
      = isHybridEcham ? cdf_def_zaxis_hybrid_echam : cdf_def_zaxis_hybrid_cf;
  return def_zaxis_hybrid_delegate(streamptr, type, ncvarid, zaxisID, zaxisindex, xtype, dimlen, dimID, axisname);
}

static void
cdfDefZaxisUUID(stream_t *streamptr, int zaxisID)
{
  unsigned char uuid[CDI_UUID_SIZE] = { 0 };
  int length = CDI_UUID_SIZE;
  cdiInqKeyBytes(zaxisID, CDI_GLOBAL, CDI_KEY_UUID, uuid, &length);
  if (!cdiUUIDIsNull(uuid))
  {
    char uuidStr[uuidNumHexChars + 1] = { 0 };
    if (cdiUUID2Str(uuid, uuidStr) == uuidNumHexChars)
    {
      int fileID = streamptr->fileID;

      bool switchNCMode = (streamptr->ncmode == 2);
      if (switchNCMode)
      {
        streamptr->ncmode = 1;
        cdf_redef(fileID);
      }

      cdf_put_att_text(fileID, NC_GLOBAL, "uuidOfVGrid", uuidNumHexChars, uuidStr);

      if (switchNCMode)
      {
        cdf_enddef(fileID, streamptr->self);
        streamptr->ncmode = 2;
      }
    }
  }
}

#ifndef USE_MPI
static void
cdfDefZaxisChar(stream_t *streamptr, int zaxisID, char *axisname, int *dimID, size_t dimlen, int zaxisindex)
{
  int fileID = streamptr->fileID;
  int ncvarID = CDI_UNDEFID;
  if (streamptr->ncmode == 2) cdf_redef(fileID);

  // Check StrlenID
  char strlen[8] = "strlen\0";
  size_t clen = (size_t) zaxisInqCLen(zaxisID);
  if (clen == 0)
    Error("Maximal string length value is 0.\nA given character axis requires a dimension to save the maximal string length.");
  int strlenID = CDI_UNDEFID;
  strlenID = checkDimName(fileID, clen, strlen);
  if (strlenID == CDI_UNDEFID) cdf_def_dim(fileID, strlen, clen, &strlenID);

  // Check 'areatype'dimID
  char dimname[CDI_MAX_NAME + 3];
  int length = sizeof(dimname);
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
  *dimID = checkDimName(fileID, dimlen, dimname);
  if (dimlen == 0) Error("No strings delivered for a character axis.");
  if (dimname[0] == 0)
  {
    memcpy(dimname, "area_type", 10);
    dimname[10] = 0;
  }

  if (*dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, dimlen, dimID);

  int dimIDs[2];
  dimIDs[0] = *dimID;
  dimIDs[1] = strlenID;

  // Get Stringvalues
  char **cvals = zaxisInqCValsPtr(zaxisID);

  if (cvals)
  {
    // Define variable and its attributes
    cdf_def_var(fileID, axisname, NC_CHAR, 2, dimIDs, &ncvarID);

    cdfPutGridStdAtts(fileID, ncvarID, zaxisID, 'Z');
    cdf_put_att_text(fileID, ncvarID, "axis", 1, "Z");
    cdfDefineAttributes(streamptr->filetype, zaxisID, CDI_GLOBAL, fileID, ncvarID);

    streamptr->cdfInfo.ncZvarIdList[zaxisindex] = ncvarID;
    cdf_enddef(fileID, streamptr->self);

    // Write Stringvalues
    size_t start[2] = { 0, 0 }, count[2] = { 1, clen };
    for (size_t i = 0; i < dimlen; i++)
    {
      start[0] = i;
      nc_put_vara_text(fileID, ncvarID, start, count, cvals[i]);
    }
  }

  streamptr->ncmode = 2;
}
#endif

static int
zaxis_datatype_to_xtype(int datatype)
{
  if (datatype == CDI_DATATYPE_FLT32) return NC_FLOAT;
  if (datatype == CDI_DATATYPE_INT32) return NC_INT;
  if (datatype == CDI_DATATYPE_INT16) return NC_SHORT;
  return NC_DOUBLE;
}

static int
zaxis_inq_xtype(int zaxisID)
{
  int datatype = CDI_UNDEFID;
  cdiInqKeyInt(zaxisID, CDI_GLOBAL, CDI_KEY_DATATYPE, &datatype);
  return zaxis_datatype_to_xtype(datatype);
}

static struct cdfPostDefActionList *
cdfDefZaxis(stream_t *streamptr, int zaxisID)
{
  // char zaxisname0[CDI_MAX_NAME];
  int ncvarid = CDI_UNDEFID, ncbvarid = CDI_UNDEFID;
  int xtype = zaxis_inq_xtype(zaxisID);

  size_t dimlen = (size_t) zaxisInqSize(zaxisID);
  int type = zaxisInqType(zaxisID);

  int ndims = 1;
  struct cdfPostDefActionList *delayed = NULL;

  if (dimlen == 1)
  {
    bool isScalar = zaxisInqScalar(zaxisID) > 0;
    if (!isScalar && CDI_CMOR_Mode)
    {
      isScalar = true;
      zaxisDefScalar(zaxisID);
    }

    if (isScalar) ndims = 0;
    if (CDI_Reduce_Dim) return delayed;

    switch (type)
    {
      case ZAXIS_SURFACE:
      case ZAXIS_CLOUD_BASE:
      case ZAXIS_CLOUD_TOP:
      case ZAXIS_ISOTHERM_ZERO:
      case ZAXIS_TROPOPAUSE:
      case ZAXIS_TOA:
      case ZAXIS_SEA_BOTTOM:
      case ZAXIS_ATMOSPHERE:
      case ZAXIS_MEANSEA:
      case ZAXIS_LAKE_BOTTOM:
      case ZAXIS_SEDIMENT_BOTTOM:
      case ZAXIS_SEDIMENT_BOTTOM_TA:
      case ZAXIS_SEDIMENT_BOTTOM_TW:
      case ZAXIS_MIX_LAYER: return delayed;
    }
  }

  int vlistID = streamptr->vlistID;
  char axisname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_NAME, axisname, &length);
  int zaxisindex = vlistZaxisIndex(vlistID, zaxisID);
  int nzaxis = vlistNumZaxis(vlistID);
  int fileID = streamptr->fileID;

  checkZaxisName(axisname, fileID, vlistID, zaxisID, nzaxis);

  char dimname[CDI_MAX_NAME + 3];
  dimname[0] = 0;
  if (dimname[0] == 0) strcpy(dimname, axisname);

  if (type == ZAXIS_REFERENCE) cdfDefZaxisUUID(streamptr, zaxisID);

  int dimID = CDI_UNDEFID;
  if (type == ZAXIS_HYBRID || type == ZAXIS_HYBRID_HALF)
  {
    delayed = cdf_def_zaxis_hybrid(streamptr, type, &ncvarid, zaxisID, zaxisindex, xtype, dimlen, &dimID, axisname);

    int natts;
    cdiInqNatts(zaxisID, CDI_GLOBAL, &natts);
    cdfDefineAttributes(streamptr->filetype, zaxisID, CDI_GLOBAL, fileID, ncvarid);
  }
#ifndef USE_MPI
  else if (type == ZAXIS_CHAR)
    cdfDefZaxisChar(streamptr, zaxisID, axisname, &dimID, dimlen, zaxisindex);
#endif
  else
  {
    dimID = checkDimName(fileID, dimlen, dimname);

    bool switchNCMode = (streamptr->ncmode == 2);
    if (switchNCMode)
    {
      streamptr->ncmode = 1;
      cdf_redef(fileID);
    }

    if (ndims && dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, dimlen, &dimID);

    if (zaxisInqLevels(zaxisID, NULL))
    {
      cdf_def_var(fileID, axisname, (nc_type) xtype, ndims, &dimID, &ncvarid);

      cdfPutGridStdAtts(fileID, ncvarid, zaxisID, 'Z');

      {
        int positive = zaxisInqPositive(zaxisID);
        static const char positive_up[] = "up", positive_down[] = "down";
        static const struct attTxtTab tab[2] = {
          { positive_up, sizeof(positive_up) - 1 },
          { positive_down, sizeof(positive_down) - 1 },
        };
        if (positive == POSITIVE_UP || positive == POSITIVE_DOWN)
        {
          size_t select = (positive == POSITIVE_DOWN);
          cdf_put_att_text(fileID, ncvarid, "positive", tab[select].txtLen, tab[select].txt);
        }
      }
      cdf_put_att_text(fileID, ncvarid, "axis", 1, "Z");
      cdfPostDefActionAddPutVal(&delayed, fileID, ncvarid, zaxisInqLevelsPtr(zaxisID), (void (*)(void *))(void (*)(void)) memFree);

      if (zaxisInqLbounds(zaxisID, NULL) && zaxisInqUbounds(zaxisID, NULL))
      {
        int nvdimID = CDI_UNDEFID;
        size_t nvertex = 2;
        if (nc_inq_dimid(fileID, bndsName, &nvdimID) != NC_NOERR) cdf_def_dim(fileID, bndsName, nvertex, &nvdimID);

        if (nvdimID != CDI_UNDEFID)
        {
          {
            size_t axisnameLen = strlen(axisname);
            axisname[axisnameLen] = '_';
            memcpy(axisname + axisnameLen + 1, bndsName, sizeof(bndsName));
            int dimIDs[2];
            dimIDs[0] = dimID;
            dimIDs[ndims] = nvdimID;
            cdf_def_var(fileID, axisname, (nc_type) xtype, ndims + 1, dimIDs, &ncbvarid);
            cdf_put_att_text(fileID, ncvarid, "bounds", axisnameLen + sizeof(bndsName), axisname);
          }
          {
            double *zbounds = (double *) Malloc(4 * dimlen * sizeof(*zbounds)), *lbounds = zbounds + 2 * dimlen,
                   *ubounds = zbounds + 3 * dimlen;
            zaxisInqLbounds(zaxisID, lbounds);
            zaxisInqUbounds(zaxisID, ubounds);
            for (size_t i = 0; i < dimlen; ++i)
            {
              zbounds[2 * i] = lbounds[i];
              zbounds[2 * i + 1] = ubounds[i];
            }
            zbounds = (double *) Realloc(zbounds, 2 * dimlen * sizeof(*zbounds));
            cdfPostDefActionAddPutVal(&delayed, fileID, ncbvarid, zbounds, cdfDelayedPutVarDeepCleanup);
          }
        }
      }
      cdfDefineAttributes(streamptr->filetype, zaxisID, CDI_GLOBAL, fileID, ncvarid);
    }

    if (switchNCMode)
    {
      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;
    }

    if (zaxisInqLevels(zaxisID, NULL) && ndims == 0) streamptr->cdfInfo.ncZvarIdList[zaxisindex] = ncvarid;
  }

  if (dimID != CDI_UNDEFID) streamptr->cdfInfo.zaxisIdList[zaxisindex] = dimID;
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_grid_indices(stream_t *streamptr, int gridID, int gridIndex)
{
  int fileID = streamptr->fileID;

  int dataType = CDI_UNDEFID;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_DATATYPE, &dataType);
  nc_type xtype = (dataType == CDI_DATATYPE_INT32) ? NC_INT : NC_INT64;

  if (xtype == NC_INT64)
  {
    int fileFormat;
    int status = nc_inq_format(fileID, &fileFormat);
    if (status == NC_NOERR && fileFormat != NC_FORMAT_NETCDF4 && fileFormat != NC_FORMAT_NETCDF4_CLASSIC)
    {
      Warning("The grid cell indices couldn't be stored because 64-bit integer is not supported by data format!");
      return NULL;
    }
  }

  SizeType dimlen = gridInqSize(gridID);

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    cdf_redef(fileID);
    streamptr->ncmode = 1;
  }

  char dimname[CDI_MAX_NAME];
  dimname[0] = 0;
  int length = sizeof(dimname);
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
  if (dimname[0] == 0) strcpy(dimname, "cell");

  int dimID = checkDimName(fileID, (size_t) dimlen, dimname);
  if (dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);

  static const char stdname[] = "healpix_index";
  int ncivarid;
  cdf_def_var(fileID, dimname, xtype, 1, &dimID, &ncivarid);

  cdf_put_att_text(fileID, ncivarid, "standard_name", sizeof(stdname) - 1, stdname);
  // cdf_put_att_text(fileID, ncivarid, "units", sizeof(units) - 1, units);

  // if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  int64_t *cellIndices = (int64_t *) Malloc((size_t) dimlen * sizeof(int64_t));
  gridInqIndices(gridID, cellIndices);
  cdf_put_var_int64(fileID, ncivarid, cellIndices);
  Free(cellIndices);

  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;
  cdfGridList[gridIndex].gridID = gridID;
  struct cdfPostDefActionList *delayed = NULL;
  return delayed;
}

static struct cdfPostDefActionList *
cdf_def_mapping(stream_t *streamptr, int gridID)
{
  struct cdfPostDefActionList *delayed = NULL;

  int natts;
  cdiInqNatts(gridID, CDI_GLOBAL, &natts);
  if (natts == 0) return delayed;

  int datatype = -1;
  int status = cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARTYPE, &datatype);
  nc_type gmapvartype = (status == CDI_NOERR) ? (nc_type) datatype : NC_INT;
  char gmapvarname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname, &length);
  if (!gmapvarname[0]) strcpy(gmapvarname, "crs");

  int fileID = streamptr->fileID;
  if (checkGridName(gmapvarname, fileID)) { cdiDefKeyString(gridID, CDI_GLOBAL, CDI_KEY_GRIDMAP_VARNAME, gmapvarname); }

  bool switchNCMode = (streamptr->ncmode == 2);
  if (switchNCMode)
  {
    streamptr->ncmode = 1;
    cdf_redef(fileID);
  }

  int ncvarid;
  int ncerrcode = nc_def_var(fileID, gmapvarname, gmapvartype, 0, NULL, &ncvarid);
  if (ncerrcode == NC_NOERR) cdfDefineAttributes(streamptr->filetype, gridID, CDI_GLOBAL, fileID, ncvarid);

  if (switchNCMode)
  {
    cdf_enddef(fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  if (ncerrcode == NC_NOERR && !xtypeIsText(gmapvartype))
  {
    cdfPostDefActionAddPut1Int(&delayed, fileID, ncvarid, 1, (void (*)(void *))(void (*)(void)) memFree);
  }
  return delayed;
}

#ifndef USE_MPI
static void
cdfDefCharacter(stream_t *streamptr, int gridID, int gridIndex, int cdiAxisID, int strlen)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;
  if (cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] != CDI_UNDEFID) return;

  bool isXaxis = (cdiAxisID == CDI_XAXIS);

  SizeType dimlen = isXaxis ? gridInqXsize(gridID) : gridInqYsize(gridID);

  // Check for all grids up to gridIndex whether it already is defined

  for (int index = 0; index < gridIndex; index++)
  {
    int gridID0 = cdfGridList[index].gridID;
    int gridtype0 = gridInqType(gridID0);
    if (gridtype0 == GRID_CHARXY)
    {
      if (gridInqXIsc(gridID0) == strlen && gridInqXsize(gridID0) == dimlen) { return; }
      else if (gridInqYIsc(gridID0) == strlen && gridInqYsize(gridID0) == dimlen) { return; }
    }
  }

  int fileID = streamptr->fileID;

  if (streamptr->ncmode == 2) cdf_redef(fileID);

  // Define Dims

  char dimname[CDI_MAX_NAME + 3];
  int length = sizeof(dimname);
  cdiInqKeyString(gridID, cdiAxisID, CDI_KEY_DIMNAME, dimname, &length);
  if (dimname[0] == 0)
  {
    memcpy(dimname, "region", 7);
    dimname[6] = 0;
  }
  int dimID = checkDimName(fileID, (size_t) dimlen, dimname);
  if (dimID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);

  // Define strlength dim

  strcpy(dimname, "strlen");
  int strlenID = checkDimName(fileID, strlen, dimname);
  if (strlenID == CDI_UNDEFID) cdf_def_dim(fileID, dimname, strlen, &strlenID);

  // Define Variable

  int dimIDs[2];
  dimIDs[0] = dimID;
  dimIDs[1] = strlenID;

  char axisname[CDI_MAX_NAME];
  char **cvals = (char **) Malloc((size_t) dimlen * sizeof(char *));
  for (size_t i = 0; i < (size_t) dimlen; i++) cvals[i] = (char *) Malloc(strlen * sizeof(char));
  int ncaxisid;
  length = CDI_MAX_NAME;
  cdiInqKeyString(gridID, cdiAxisID, CDI_KEY_NAME, axisname, &length);
  gridInqXCvals(gridID, cvals);

  int status = nc_inq_varid(fileID, axisname, &ncaxisid);
  if (status == NC_NOERR) return;

  cdf_def_var(fileID, axisname, NC_CHAR, 2, dimIDs, &ncaxisid);
  cdfPutGridStdAtts(fileID, ncaxisid, gridID, isXaxis ? 'X' : 'Y');

  cdf_enddef(fileID, streamptr->self);

  // Write Var

  size_t start[2] = { 0, 0 }, count[2] = { 1, strlen };
  for (size_t i = 0; i < (size_t) dimlen; i++)
  {
    start[0] = i;
    (void) nc_put_vara_text(fileID, ncaxisid, start, count, cvals[i]);
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[isXaxis ? CDF_DIMID_X : CDF_DIMID_Y] = dimID;
  cdfGridList[gridIndex].ncIdList[isXaxis ? CDF_VARID_X : CDF_VARID_Y] = ncaxisid;

  streamptr->ncmode = 2;
}
#endif

static void
cdfDefReducedGrid(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;

  cdfGridList[gridIndex].gridID = gridID;

  {
    SizeType dimlen = gridInqSize(gridID);

    struct idSearch search = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_GAUSSIAN_REDUCED, dimlen,
                                               gridInqType, gridInqSize);
    int iz = search.numNonMatching;
    int dimID = search.foundID;

    if (dimID == CDI_UNDEFID)
    {
      int fileID = streamptr->fileID;

      char axisname[16] = "rgrid";
      size_t len = strlen(axisname);
      if (iz) snprintf(axisname + len, sizeof(axisname) - len, "%1d", iz + 1);

      bool switchNCMode = (streamptr->ncmode == 2);
      if (switchNCMode)
      {
        streamptr->ncmode = 1;
        cdf_redef(fileID);
      }

      cdf_def_dim(fileID, axisname, (size_t) dimlen, &dimID);

      if (switchNCMode)
      {
        cdf_enddef(fileID, streamptr->self);
        streamptr->ncmode = 2;
      }
    }

    cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] = dimID;
  }

  {
    SizeType dimlen = gridInqYsize(gridID);

    struct idSearch search = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_RP, GRID_GAUSSIAN_REDUCED, dimlen,
                                               gridInqType, gridInqSize);
    int iz = search.numNonMatching;
    int dimID = search.foundID;

    if (dimID == CDI_UNDEFID)
    {
      int fileID = streamptr->fileID;

      char axisname[32] = "reduced_points";
      size_t len = strlen(axisname);
      if (iz) snprintf(axisname + len, sizeof(axisname) - len, "%1d", iz + 1);

      if (streamptr->ncmode == 2) cdf_redef(fileID);

      cdf_def_dim(fileID, axisname, (size_t) dimlen, &dimID);

      int ncvarid = CDI_UNDEFID;
      cdf_def_var(fileID, axisname, NC_INT, 1, &dimID, &ncvarid);

      cdf_enddef(fileID, streamptr->self);
      streamptr->ncmode = 2;

      int *reducedPoints = (int *) Malloc((size_t) dimlen * sizeof(int));
      gridInqReducedPoints(gridID, reducedPoints);
      cdf_put_var_int(fileID, ncvarid, reducedPoints);
      Free(reducedPoints);

      cdfGridList[gridIndex].ncIdList[CDF_VARID_RP] = ncvarid;
    }

    cdfGridList[gridIndex].ncIdList[CDF_DIMID_RP] = dimID;
  }
}

static void
cdf_define_generic_dim(stream_t *streamptr, int gridID, int gridIndex)
{
  CdfGrid *cdfGridList = streamptr->cdfInfo.cdfGridList;
  SizeType dimlen = gridInqSize(gridID);
  int dimID = CDI_UNDEFID;

  if (gridInqYsize(gridID) == 0)
  {
    struct idSearch search
        = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_X, GRID_GENERIC, dimlen, gridInqType, gridInqSize);
    dimID = search.foundID;
  }

  if (gridInqXsize(gridID) == 0)
  {
    struct idSearch search
        = cdfSearchIDBySize(0, (size_t) gridIndex, cdfGridList, CDF_DIMID_Y, GRID_GENERIC, dimlen, gridInqType, gridInqSize);
    dimID = search.foundID;
  }

  if (dimID == CDI_UNDEFID)
  {
    char dimname[CDI_MAX_NAME];
    int length = sizeof(dimname);
    cdiInqKeyString(gridID, CDI_GLOBAL, CDI_KEY_DIMNAME, dimname, &length);
    if (dimname[0] == 0) strcpy(dimname, "cell");

    int fileID = streamptr->fileID;
    dimID = checkDimName(fileID, (size_t) dimlen, dimname);
    if (dimID == CDI_UNDEFID)
    {
      bool switchNCMode = (streamptr->ncmode == 2);
      if (switchNCMode)
      {
        streamptr->ncmode = 1;
        cdf_redef(fileID);
      }

      cdf_def_dim(fileID, dimname, (size_t) dimlen, &dimID);

      if (switchNCMode)
      {
        cdf_enddef(fileID, streamptr->self);
        streamptr->ncmode = 2;
      }
    }
  }

  cdfGridList[gridIndex].gridID = gridID;
  cdfGridList[gridIndex].ncIdList[CDF_DIMID_X] = dimID;
}

static struct cdfPostDefActionList *
cdf_define_grid(stream_t *streamptr, int gridID, int gridIndex)
{
  struct cdfPostDefActionList *delayed = NULL;

  CdfGrid *ncgrid = &(streamptr->cdfInfo.cdfGridList[gridIndex]);
  if (ncgrid->ncIdList[CDF_DIMID_X] != CDI_UNDEFID) return delayed;

  int gridType = gridInqType(gridID);
  SizeType gridSize = gridInqSize(gridID);

  if (CDI_Debug) Message("gridType = %d  gridSize = %zu", gridType, gridSize);

  if (CDI_Reduce_Dim && gridSize == 1)  // no grid information
  {
    ncgrid->gridID = gridID;
    return delayed;
  }

  if (gridType == GRID_GAUSSIAN || gridType == GRID_LONLAT || gridType == GRID_PROJECTION)
  {
    int ndims = !(gridType == GRID_LONLAT && gridSize == 1 && !gridInqHasDims(gridID));
    SizeType xsize = gridInqXsize(gridID);
    SizeType ysize = gridInqYsize(gridID);

    if (xsize)
    {
      struct cdfPostDefActionList *xdelayed = cdfDefXaxis(streamptr, gridID, gridIndex, ndims, false);
      delayed = cdfPostDefActionConcat(delayed, xdelayed);
      Free(xdelayed);
    }
    if (ysize)
    {
      struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, ndims, false);
      delayed = cdfPostDefActionConcat(delayed, ydelayed);
      Free(ydelayed);
    }

    if (ndims == 1 && xsize == 0 && ysize == 0 && gridType == GRID_PROJECTION) cdf_define_generic_dim(streamptr, gridID, gridIndex);

    struct cdfPostDefActionList *mdelayed = cdf_def_mapping(streamptr, gridID);
    delayed = cdfPostDefActionConcat(delayed, mdelayed);
    Free(mdelayed);
  }
  else if (gridType == GRID_HEALPIX)
  {
    cdf_define_generic_dim(streamptr, gridID, gridIndex);

    {
      struct cdfPostDefActionList *mdelayed = cdf_def_mapping(streamptr, gridID);
      delayed = cdfPostDefActionConcat(delayed, mdelayed);
      Free(mdelayed);
    }
    if (gridSize == gridInqIndices(gridID, NULL))
    {
      struct cdfPostDefActionList *idelayed = cdf_def_grid_indices(streamptr, gridID, gridIndex);
      if (idelayed)
      {
        delayed = cdfPostDefActionConcat(delayed, idelayed);
        Free(idelayed);
      }
    }
  }
  else if (gridType == GRID_GENERIC)
  {
    SizeType xsize = gridInqXsize(gridID);
    SizeType ysize = gridInqYsize(gridID);
    if (gridSize == 1 && xsize == 0 && ysize == 0)
    {
      // no grid information
      ncgrid->gridID = gridID;
    }
    else
    {

      if (xsize > 0)
      {
        struct cdfPostDefActionList *xdelayed = cdfDefXaxis(streamptr, gridID, gridIndex, 1, false);
        delayed = cdfPostDefActionConcat(delayed, xdelayed);
        Free(xdelayed);
      }
      if (ysize > 0)
      {
        struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, 1, false);
        delayed = cdfPostDefActionConcat(delayed, ydelayed);
        Free(ydelayed);
      }

      if (xsize == 0 && ysize == 0) cdf_define_generic_dim(streamptr, gridID, gridIndex);
    }
  }
  else if (gridType == GRID_CURVILINEAR) { delayed = cdfDefCurvilinear(streamptr, gridID, gridIndex); }
  else if (gridType == GRID_UNSTRUCTURED) { delayed = cdfDefUnstructured(streamptr, gridID, gridIndex); }
  else if (gridType == GRID_GAUSSIAN_REDUCED)
  {
    cdfDefReducedGrid(streamptr, gridID, gridIndex);
    if (gridInqYsize(gridID))
    {
      struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, 1, true);
      delayed = cdfPostDefActionConcat(delayed, ydelayed);
      Free(ydelayed);
    }
  }
  else if (gridType == GRID_SPECTRAL)
  {
    cdfDefComplex(streamptr, gridID, gridIndex);
    cdfDefSP(streamptr, gridID, gridIndex);
  }
  else if (gridType == GRID_FOURIER)
  {
    cdfDefComplex(streamptr, gridID, gridIndex);
    cdfDefFC(streamptr, gridID, gridIndex);
  }
  else if (gridType == GRID_TRAJECTORY)
  {
    cdfDefTrajLon(streamptr, gridID, gridIndex);
    cdfDefTrajLat(streamptr, gridID, gridIndex);
  }
#ifndef USE_MPI
  else if (gridType == GRID_CHARXY)
  {
    int strlen = 0;
    if ((strlen = gridInqXIsc(gridID)))
      cdfDefCharacter(streamptr, gridID, gridIndex, CDI_XAXIS, strlen);
    else if (gridInqXsize(gridID))
    {
      struct cdfPostDefActionList *xdelayed = cdfDefXaxis(streamptr, gridID, gridIndex, 1, false);
      delayed = cdfPostDefActionConcat(delayed, xdelayed);
      Free(xdelayed);
    }

    if ((strlen = gridInqYIsc(gridID)))
      cdfDefCharacter(streamptr, gridID, gridIndex, CDI_YAXIS, strlen);
    else if (gridInqYsize(gridID))
    {
      struct cdfPostDefActionList *ydelayed = cdfDefYaxis(streamptr, gridID, gridIndex, 1, false);
      delayed = cdfPostDefActionConcat(delayed, ydelayed);
      Free(ydelayed);
    }
  }
#endif
  else { Error("Unsupported grid type: %s", gridNamePtr(gridType)); }
  return delayed;
}

void
cdfDefCoordinateVars(stream_t *streamptr)
{
  int vlistID = streamptr->vlistID;
  if (vlistID == CDI_UNDEFID) Error("Internal problem! vlist undefined for streamptr %p", streamptr);

  if (vlistHasTime(vlistID)) cdfDefTime(streamptr);

  int ngrids = vlistNumGrids(vlistID);
  if (2 * ngrids > MAX_GRIDS_PS) Error("Internal problem! Too many grids per stream (max=%d)\n", MAX_GRIDS_PS);

  struct cdfPostDefActionList *delayed = NULL;

  CdfGrid *ncgrid = streamptr->cdfInfo.cdfGridList;
  for (int index = 0; index < 2 * ngrids; ++index)
  {
    ncgrid[index].gridID = CDI_UNDEFID;
    for (size_t i = 0; i < CDF_SIZE_NCID; ++i) ncgrid[index].ncIdList[i] = CDI_UNDEFID;
  }

  for (int index = 0; index < ngrids; ++index)
  {
    int gridID = vlistGrid(vlistID, index);
    struct cdfPostDefActionList *griddelayed = cdf_define_grid(streamptr, gridID, index);
    delayed = cdfPostDefActionConcat(delayed, griddelayed);
    Free(griddelayed);
  }

  for (int i = 0, index = ngrids - 1; i < ngrids; ++i)
  {
    int gridID = vlistGrid(vlistID, i);
    int projID = gridInqProj(gridID);
    if (projID != CDI_UNDEFID)
    {
      struct cdfPostDefActionList *griddelayed = cdf_define_grid(streamptr, projID, ++index);
      delayed = cdfPostDefActionConcat(delayed, griddelayed);
      Free(griddelayed);
    }
  }

  int nzaxis = vlistNumZaxis(vlistID);
  for (int index = 0; index < nzaxis; ++index)
  {
    int zaxisID = vlistZaxis(vlistID, index);
    if (streamptr->cdfInfo.zaxisIdList[index] == CDI_UNDEFID)
    {
      struct cdfPostDefActionList *zaxisdelayed = cdfDefZaxis(streamptr, zaxisID);
      delayed = cdfPostDefActionConcat(delayed, zaxisdelayed);
      Free(zaxisdelayed);
    }
  }

  if (streamptr->ncmode != 2)
  {
    cdf_enddef(streamptr->fileID, streamptr->self);
    streamptr->ncmode = 2;
  }

  int nvars = vlistNvars(vlistID);
  for (int varID = 0; varID < nvars; varID++) cdfDefVar(streamptr, varID);

  cdfEndDef(streamptr);
  if (delayed)
  {
    cdfPostDefActionApply(delayed->len, delayed->actions);
    cdfPostDefActionListDelete(delayed);
  }
}

#endif

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