/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida

*/

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

#define H5_USE_16_API

#ifdef HAVE_LIBHDF5
#include "hdf5.h"
#endif

#include <cdi.h>

#include "cdo_options.h"
#include "dmemory.h"
#include "varray.h"
#include "process_int.h"
#include "cdo_cdi_wrapper.h"
#include "cdo_default_values.h"
#include <mpim_grid.h>

#define MAX_DSETS 1024

struct dset_obj_t
{
  char *name;
  char *description;
  char *units;
  char *title;
  char *time;
  int dtype;
  int nx;
  int ny;
  int nz;
  int nt;
  size_t gridsize;
  int lscale;
  int loffset;
  int hasMissvals;
  double scale;
  double offset;
  double missval;
  double *array;
};

struct datasets_t
{
  int nsets;
  int mergelevel;
  int lgeoloc;
  int lregion;
  int lprojtype;
  int lmetadata;
  dset_obj_t obj[MAX_DSETS];
};

#ifdef HAVE_LIBHDF5
static void
print_filter(hid_t dset_id, char *varname)
{
  hid_t plist;
  unsigned int flags;
  int idx;
  unsigned int cd_values;
  int nfilter;
  size_t cd_nelmts = 1;
  size_t pnamelen = 64;
  char pname[64];

  /* get filter */
  plist = H5Dget_create_plist(dset_id);
  nfilter = H5Pget_nfilters(plist);

  for (idx = 0; idx < nfilter; idx++)
    {
      H5Pget_filter(plist, idx, &flags, &cd_nelmts, &cd_values, pnamelen, pname);
      cdo_print("Dataset %s: filter %d =  %s", varname, idx + 1, pname);
    }

  H5Pclose(plist);
}

static void
get_grid_info(double c0, double re, int *nrxp, int *nryp, double *r0p, double *s0p, double *cp)
{
  const double pi = M_PI;
  double git, phi, s90;
  double r0, s0, c;
  int nrx, nry;

  git = 2. * pi * re * std::cos(pi / 6.) / c0;
  /* number of longitude pixels */
  nrx = 2 * (int) std::lround(0.5 * git);

  /* central index in longitude */
  r0 = nrx / 2 + 0.5;

  /* resolution in km */
  c = 2. * pi * re * std::cos(30. * pi / 180.) / nrx;

  phi = pi / 2.;
  s90 = re / c * std::sin(phi) / std::cos(30. * pi / 180.);

  nry = (int) std::floor(s90);
  /* central index in latitude */
  s0 = nry + 0.5;
  /* number of latitude pixels */
  nry = 2 * nry;

  *nrxp = nrx;
  *nryp = nry;
  *r0p = r0;
  *s0p = s0;
  *cp = c;
}

static double
det_lon_atovs(double r, double r0, double lts, double c, double re)
{
  const double pi = M_PI;

  double xla = (r - r0) * c / re / std::cos(lts * pi / 180.); /* longitude */
  xla = 180. * xla / pi;

  return xla;
}

static double
det_lat_atovs(double s, double s0, double lts, double c, double re)
{
  const double pi = M_PI;

  double siphi = (s - s0) * c * std::cos(lts * pi / 180.) / re;
  double phi = 180. * std::asin(siphi) / pi; /* latitude */

  return phi;
}

static int
defLonLatGrid(int nx, int ny, double c0, double lts, double re)
{
  int gridID;
  int nrx, nry, i;
  double c;
  double r0, s0;
  double r, s;
  double xla, phi;

  get_grid_info(c0, re, &nrx, &nry, &r0, &s0, &c);

  if (nx != nrx || ny != nry)
    {
      printf("nrx=%d nry=%d\n", nrx, nry);
      return -1;
    }

  Varray<double> xvals(nx), yvals(ny);
  Varray<double> xbounds(nx * 2), ybounds(ny * 2);

  for (i = 0; i < nx; ++i)
    {
      r = i + 1;
      xla = det_lon_atovs(r, r0, lts, c, re);
      xvals[i] = xla;
      xla = det_lon_atovs(r - 0.5, r0, lts, c, re);
      xbounds[2 * i] = xla;
      xla = det_lon_atovs(r + 0.5, r0, lts, c, re);
      xbounds[2 * i + 1] = xla;
      /* printf("xla[%d]=%g\n", i, xla); */
    }

  for (i = 0; i < ny; ++i)
    {
      s = (nry - i - 1) + 1;
      phi = det_lat_atovs(s, s0, lts, c, re);
      yvals[i] = phi;
      phi = det_lat_atovs(s - 0.5, s0, lts, c, re);
      ybounds[2 * i] = phi;
      phi = det_lat_atovs(s + 0.5, s0, lts, c, re);
      ybounds[2 * i + 1] = phi;
      /* printf("phi[%d]=%g\n", i, phi); */
    }

  gridID = gridCreate(GRID_LONLAT, nx * ny);
  gridDefXsize(gridID, nx);
  gridDefYsize(gridID, ny);
  gridDefXvals(gridID, xvals.data());
  gridDefYvals(gridID, yvals.data());
  /*
  gridDefXbounds(gridID, xbounds.data());
  gridDefYbounds(gridID, ybounds.data());
  */

  return gridID;
}

static int
defSinusoidalGrid(int nx, int ny, double xmin, double ymax, double dx, double dy, double p1, double p2, double p3, double p4)
{
  (void) (p1);  // CDO_UNUSED
  (void) (p2);  // CDO_UNUSED
  (void) (p3);  // CDO_UNUSED
  (void) (p4);  // CDO_UNUSED
  Varray<double> xvals(nx), yvals(ny);

  for (int i = 0; i < nx; ++i) xvals[i] = xmin + i * dx + dx / 2;
  for (int i = 0; i < ny; ++i) yvals[i] = ymax - i * dy - dy / 2;

  auto gridID = gridCreate(GRID_PROJECTION, nx * ny);

  grid_def_params_sinu(gridID);

  gridDefXsize(gridID, nx);
  gridDefYsize(gridID, ny);
  gridDefXvals(gridID, xvals.data());
  gridDefYvals(gridID, yvals.data());

  return gridID;
}

static int
defLaeaGrid(int nx, int ny, double xmin, double ymax, double dx, double dy, double a, double lon0, double lat0)
{
  Varray<double> xvals(nx), yvals(ny);

  for (int i = 0; i < nx; ++i) xvals[i] = xmin + i * dx + dx / 2;
  for (int i = 0; i < ny; ++i) yvals[i] = ymax - i * dy - dy / 2;

  auto gridID = gridCreate(GRID_PROJECTION, nx * ny);

  gridDefXsize(gridID, nx);
  gridDefYsize(gridID, ny);
  gridDefXvals(gridID, xvals.data());
  gridDefYvals(gridID, yvals.data());

  grid_def_params_laea(gridID, a, lon0, lat0);

  return gridID;
}

static int
scan_pcs_def(char *pcs_def, char proj[128], double *a, double *lon0, double *lat0)
{
  char *pcs[64];
  int npcs = 0;
  int i;
  int len;
  int nfound = 0;

  strcpy(proj, "unknown");
  *a = 1;
  *lon0 = 0;
  *lat0 = 0;

  pcs[npcs++] = &pcs_def[0];
  len = (int) strlen(pcs_def);
  for (i = 0; i < len; ++i)
    if (pcs_def[i] == ',' && npcs < 64)
      {
        pcs_def[i] = 0;
        pcs[npcs++] = &pcs_def[i + 1];
      }

  for (i = 0; i < npcs; ++i)
    {
      if (memcmp(pcs[i], "proj=", 5) == 0)
        {
          pcs[i] += 5;
          strcpy(proj, pcs[i]);
          nfound++;
        }
      else if (memcmp(pcs[i], "a=", 2) == 0)
        {
          pcs[i] += 2;
          *a = atof(pcs[i]);
          nfound++;
        }
      else if (memcmp(pcs[i], "lon_0=", 6) == 0)
        {
          pcs[i] += 6;
          *lon0 = atof(pcs[i]);
          nfound++;
        }
      else if (memcmp(pcs[i], "lat_0=", 6) == 0)
        {
          pcs[i] += 6;
          *lat0 = atof(pcs[i]);
          nfound++;
        }
    }

  return nfound;
}

static int
read_geolocation(hid_t loc_id, int nx, int ny, int lprojtype)
{
  int gridID = -1;
  hid_t grp_id;
  hid_t proj_id, region_id;
  hid_t proj_tid, region_tid;
  hid_t str_tid, fltarr_tid;
  hid_t ptype_id;
  hsize_t dims;
  int xsize, ysize;
  struct proj_t
  {
    char name[64] = { 0 };
    char ellipsoid[64] = { 0 };
    float parameter[10] = { 0.0 };
  };
  struct region_t
  {
    float xmin = 0.0;
    float xmax = 0.0;
    float ymin = 0.0;
    float ymax = 0.0;
    float dx = 0.0;
    float dy = 0.0;
  };

  proj_t proj;
  region_t region;
  char *projection_name = nullptr;

  if (Options::cdoVerbose) cdo_print("Read geolocation:");

  if (lprojtype)
    {
      ptype_id = H5Topen(loc_id, "ProjType");
      if (ptype_id >= 0)
        {
          projection_name = H5Tget_member_name(ptype_id, 0);
          H5Tclose(ptype_id);
        }
    }

  str_tid = H5Tcopy(H5T_C_S1);
  H5Tset_size(str_tid, 64);
  dims = 10;
  fltarr_tid = H5Tarray_create(H5T_NATIVE_FLOAT, 1, &dims, nullptr);

  proj_tid = H5Tcreate(H5T_COMPOUND, sizeof(proj_t));
  if (projection_name)
    H5Tinsert(proj_tid, projection_name, HOFFSET(proj_t, name), str_tid);
  else
    H5Tinsert(proj_tid, "Projection name", HOFFSET(proj_t, name), str_tid);
  H5Tinsert(proj_tid, "Reference ellipsoid", HOFFSET(proj_t, ellipsoid), str_tid);
  H5Tinsert(proj_tid, "Projection parameter", HOFFSET(proj_t, parameter), fltarr_tid);

  if (projection_name) Free(projection_name);

  grp_id = H5Gopen(loc_id, "Geolocation");

  proj_id = H5Dopen(grp_id, "Projection");
  if (proj_id < 0) proj_id = H5Dopen(grp_id, "projection");
  /*
  {
    hid_t tid;
    int nmem;
    int im;

    tid = H5Dget_type(proj_id);
    nmem = H5Tget_nmembers(tid);
    for ( im = 0; im < nmem; ++im )
      {
        printf("%d %s\n", im, H5Tget_member_name(tid, im));
      }
  }
  */
  if (proj_id >= 0) H5Dread(proj_id, proj_tid, H5S_ALL, H5S_ALL, H5P_DEFAULT, &proj);

  H5Dclose(proj_id);
  H5Tclose(proj_tid);
  H5Tclose(str_tid);
  H5Tclose(fltarr_tid);

  if (Options::cdoVerbose)
    cdo_print("  Projection: name=%s\n\t\t\tellipsoid=%s\n\t\t\tparameter=%g %g %g %g %g %g", proj.name, proj.ellipsoid,
              proj.parameter[0], proj.parameter[1], proj.parameter[2], proj.parameter[3], proj.parameter[4], proj.parameter[5]);

  region_tid = H5Tcreate(H5T_COMPOUND, sizeof(region_t));
  H5Tinsert(region_tid, "xmin", HOFFSET(region_t, xmin), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "xmax", HOFFSET(region_t, xmax), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "ymin", HOFFSET(region_t, ymin), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "ymax", HOFFSET(region_t, ymax), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "dx", HOFFSET(region_t, dx), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "dy", HOFFSET(region_t, dy), H5T_NATIVE_FLOAT);

  region_id = H5Dopen(grp_id, "Region");
  if (region_id < 0) region_id = H5Dopen(grp_id, "region");

  if (region_id >= 0) H5Dread(region_id, region_tid, H5S_ALL, H5S_ALL, H5P_DEFAULT, &region);

  H5Dclose(region_id);
  H5Tclose(region_tid);

  if (region.xmin > region.xmax)
    {
      double xmin = region.xmin;
      region.xmin = region.xmax;
      region.xmax = xmin;
      if (Options::cdoVerbose) cdo_print("  Swap xmin/xmax");
    }

  if (Options::cdoVerbose)
    cdo_print("  Region: xmin=%g xmax=%g ymin=%g ymax=%g dx=%g dy=%g", region.xmin, region.xmax, region.ymin, region.ymax,
              region.dx, region.dy);

  H5Gclose(grp_id);

  /* check region */
  xsize = (int) std::lround((region.xmax - region.xmin) / region.dx);
  ysize = (int) std::lround((region.ymax - region.ymin) / region.dy);

  if (Options::cdoVerbose) cdo_print("  Size: xsize=%d  ysize=%d", xsize, ysize);

  /* some CM-SAF files have incorrect entries for some metadata. */
  /* these are corrected in the following sections. */
  /* in case of questions on this, contact frank.kaspar@dwd.de */
  if (strcmp(proj.ellipsoid, "WSG-84") == 0) strcpy(proj.ellipsoid, "WGS-84");

  if ((int) region.xmin == -8887500 && (int) region.xmax == -8887500 && (int) region.ymin == 8887500 && (int) region.ymax == 8887500
      && (int) region.dx == 15000 && (int) region.dy == 15000)
    {
      region.xmax = 8887500.0;
      region.ymin = -8887500.0;
      if (Options::cdoVerbose)
        cdo_print("  Corrected region: xmin=%g xmax=%g ymin=%g ymax=%g dx=%g dy=%g", region.xmin, region.xmax, region.ymin,
                  region.ymax, region.dx, region.dy);

      xsize = (int) std::lround((region.xmax - region.xmin) / region.dx);
      ysize = (int) std::lround((region.ymax - region.ymin) / region.dy);
      if (Options::cdoVerbose) cdo_print("  Corrected size: xsize=%d  ysize=%d", xsize, ysize);
    }

  if (nx == 298 && ny == 371 && (int) region.xmin == -6709222 && (int) region.xmax == 6709222 && (int) region.ymin == -6664078
      && (int) region.ymax == 9984898 && (int) region.dx == 45000 && (int) region.dy == 45000)
    {
      region.xmin = -6705000;
      region.xmax = 6705000;
      region.ymin = -6705000;
      region.ymax = 9990000;
      cdo_print("  Corrected region: xmin=%g xmax=%g ymin=%g ymax=%g dx=%g dy=%g", region.xmin, region.xmax, region.ymin,
                region.ymax, region.dx, region.dy);

      xsize = (int) std::lround((region.xmax - region.xmin) / region.dx);
      ysize = (int) std::lround((region.ymax - region.ymin) / region.dy);
      if (Options::cdoVerbose) cdo_print("  Corrected size: xsize=%d  ysize=%d", xsize, ysize);
    }

  if (strcmp(proj.name, "sinusoidal") != 0
      && ((nx == xsize && ny == ysize && (int) region.xmin == -8887500 && (int) region.xmax == 8887500
           && (int) region.ymin == -8887500 && (int) region.ymax == 8887500 && (int) region.dx == 15000 && (int) region.dy == 15000)
          || (nx == xsize && ny == ysize && (int) region.xmin == -5827500 && (int) region.xmax == 5827500
              && (int) region.ymin == 3307500 && (int) region.ymax == 8887500 && (int) region.dx == 15000
              && (int) region.dy == 15000)
          || (nx == xsize && ny == ysize && (int) region.xmin == -5827500 && (int) region.xmax == 5827500
              && (int) region.ymin == 3307500 && (int) region.ymax == 8887500 && (int) region.dx == 45000
              && (int) region.dy == 45000)
          || (nx == xsize && ny == ysize && (int) region.xmin == -5827500 && (int) region.xmax == 5827500
              && (int) region.ymin == 3307500 && (int) region.ymax == 8887500 && (int) region.dx == 3000 && (int) region.dy == 3000)
          || (nx == 298 && ny == 371 && (int) region.xmin == -6709222 && (int) region.xmax == 6709222
              && (int) region.ymin == -6664078 && (int) region.ymax == 9984898 && (int) region.dx == 45000
              && (int) region.dy == 45000)
          || (nx == xsize && ny == ysize && (int) region.xmin == -6705000 && (int) region.xmax == 6705000
              && (int) region.ymin == -6705000 && (int) region.ymax == 9990000 && (int) region.dx == 45000
              && (int) region.dy == 45000)))
    {
      if (Options::cdoVerbose) cdo_print("Replacing incorrect projection parameters for sinusoidal products:");
      strcpy(proj.ellipsoid, "WGS-84");
      strcpy(proj.name, "sinusoidal");
      proj.parameter[0] = 0.0;
      proj.parameter[1] = 0.0;
      proj.parameter[2] = 0.0;
      proj.parameter[3] = 0.0;
      proj.parameter[4] = -99.99;
      proj.parameter[5] = -99.99;
      if (Options::cdoVerbose)
        cdo_print("proj1 = %g, proj2 = %g, proj3 = %g, proj4 = %g,", proj.parameter[0], proj.parameter[1], proj.parameter[2],
                  proj.parameter[3]);
    }

  if (nx == xsize && ny == ysize && strcmp(proj.name, "sinusoidal") == 0 && strcmp(proj.ellipsoid, "WGS-84") == 0)
    {
      gridID = defSinusoidalGrid(nx, ny, region.xmin, region.ymax, region.dx, region.dy, proj.parameter[0], proj.parameter[1],
                                 proj.parameter[2], proj.parameter[3]);
    }
  /* modification by Frank Kaspar */
  else if (nx == xsize && ny == ysize && strcmp(proj.name, "Lambert Azimuthal Equal Area") == 0
           && memcmp(proj.ellipsoid, "Sphere", 6) == 0)
    {
      double a = (proj.parameter[4] < 0) ? 6370997.0 : proj.parameter[4];
      gridID = defLaeaGrid(nx, ny, region.xmin, region.ymax, region.dx, region.dy, a, proj.parameter[2], proj.parameter[3]);
    }
  else if (memcmp(proj.name, "Cylindrical Equal Area", 22) == 0 && memcmp(proj.ellipsoid, "Sphere", 6) == 0)
    {
      double c0 = 0.001 * std::sqrt(proj.parameter[5]); /* nominal spatial resolution */
      double lts = proj.parameter[3];
      double re = proj.parameter[4] / 1000; /* Earth radius [km]*/
      if (Options::cdoVerbose) cdo_print("  c0 = %g, lts = %g, re = %g", c0, lts, re);
      gridID = defLonLatGrid(nx, ny, c0, lts, re);
    }
  else if (nx == 386 && ny == 162)
    {
      double c0 = 90; /* nominal spatial resolution */
      double lts = 30;
      double re = 6371.228; /* Earth radius [km]*/
      if (Options::cdoVerbose) cdo_print("  c0 = %g, lts = %g, re = %g", c0, lts, re);
      gridID = defLonLatGrid(nx, ny, c0, lts, re);
    }

  return gridID;
}

static int
read_region(hid_t loc_id, int nx, int ny)
{
  int gridID = -1;
  struct region_t
  {
    double area_extent[4];
    int xsize;
    int ysize;
    float xscale;
    float yscale;
    float lat_0;
    float lon_0;
    float lat_ts;
    char id[128];
    char name[128];
    char pcs_id[128];
    char pcs_def[128];
  };
  region_t region;
  char proj[128];
  double a, lon0, lat0;

  if (Options::cdoVerbose) cdo_print("Read region:");

  /*
   * Create a data type for region
   */
  hid_t region_tid = H5Tcreate(H5T_COMPOUND, sizeof(region_t));
  hsize_t dims = 4;
  hid_t fltarr_tid = H5Tarray_create(H5T_NATIVE_DOUBLE, 1, &dims, nullptr);
  hid_t str64_tid = H5Tcopy(H5T_C_S1);
  H5Tset_size(str64_tid, 128);
  hid_t str128_tid = H5Tcopy(H5T_C_S1);
  H5Tset_size(str128_tid, 128);

  H5Tinsert(region_tid, "area_extent", HOFFSET(region_t, area_extent), fltarr_tid);
  H5Tinsert(region_tid, "xsize", HOFFSET(region_t, xsize), H5T_NATIVE_INT);
  H5Tinsert(region_tid, "ysize", HOFFSET(region_t, ysize), H5T_NATIVE_INT);
  H5Tinsert(region_tid, "xscale", HOFFSET(region_t, xscale), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "yscale", HOFFSET(region_t, yscale), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "lat_0", HOFFSET(region_t, lat_0), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "lon_0", HOFFSET(region_t, lon_0), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "lat_ts", HOFFSET(region_t, lat_ts), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "id", HOFFSET(region_t, id), str64_tid);
  H5Tinsert(region_tid, "name", HOFFSET(region_t, name), str64_tid);
  H5Tinsert(region_tid, "pcs_id", HOFFSET(region_t, pcs_id), str64_tid);
  H5Tinsert(region_tid, "pcs_def", HOFFSET(region_t, pcs_def), str128_tid);

  hid_t grp_id = H5Gopen(loc_id, "/");

  hid_t region_id = H5Dopen(grp_id, "region");
  /*
  {
    hid_t tid;
    int nmem;
    int im;

    tid = H5Dget_type(proj_id);
    nmem = H5Tget_nmembers(tid);
    for ( im = 0; im < nmem; ++im )
      {
        printf("%d %s\n", im, H5Tget_member_name(tid, im));
      }
  }
  */
  herr_t status = H5Dread(region_id, region_tid, H5S_ALL, H5S_ALL, H5P_DEFAULT, &region);
  (void) (status);  // CDO_UNUSED

  if (Options::cdoVerbose)
    {
      printf("area_extent[0] = %g\n", region.area_extent[0]);
      printf("area_extent[1] = %g\n", region.area_extent[1]);
      printf("area_extent[2] = %g\n", region.area_extent[2]);
      printf("area_extent[3] = %g\n", region.area_extent[3]);
      printf("xsize = %d\n", region.xsize);
      printf("ysize = %d\n", region.ysize);
      printf("xscale = %g\n", region.xscale);
      printf("yscale = %g\n", region.yscale);
      printf("lat_0 = %g\n", region.lat_0);
      printf("lon_0 = %g\n", region.lon_0);
      printf("lat_ts = %g\n", region.lat_ts);
      printf("id = %s\n", region.id);
      printf("name = %s\n", region.name);
      printf("pcs_id = %s\n", region.pcs_id);
      printf("pcs_def = %s\n", region.pcs_def);
    }

  H5Dclose(region_id);
  H5Tclose(region_tid);
  H5Tclose(str64_tid);
  H5Tclose(str128_tid);
  H5Tclose(fltarr_tid);

  H5Gclose(grp_id);

  /* check region */

  int nfound = scan_pcs_def(region.pcs_def, proj, &a, &lon0, &lat0);

  if (Options::cdoVerbose)
    {
      printf("proj = %s\n", proj);
      printf("a    = %g\n", a);
      printf("lon0 = %g\n", lon0);
      printf("lat0 = %g\n", lat0);
    }

  double xmin = region.area_extent[0];
  double ymin = region.area_extent[1];
  double xmax = region.area_extent[2];
  double ymax = region.area_extent[3];

  double dx = (xmax - xmin) / nx;
  double dy = (ymax - ymin) / ny;
  /*
  xsize = (int)lround((region.xmax-region.xmin)/region.dx);
  ysize = (int)lround((region.ymax-region.ymin)/region.dy);

  if ( Options::cdoVerbose ) cdo_print("  Size: xsize=%d  ysize=%d", xsize, ysize);
  */

  if (nfound == 4 && nx == region.xsize && ny == region.ysize && strcmp(proj, "laea") == 0)
    {
      gridID = defLaeaGrid(nx, ny, xmin, ymax, dx, dy, a, lon0, lat0);
    }

  return gridID;
}

static void
read_dataset(hid_t loc_id, const char *name, void *opdata)
{
  hid_t dataspace;
  hsize_t dims_out[9]; /* dataset dimensions           */
  herr_t status;       /* Generic return value		*/
  hid_t attr, atype, atype_mem;
  int iattr;
  float fattr;
  double dattr;
  char attname[CDI_MAX_NAME];
  H5T_class_t atype_class;
  size_t atype_size;
  int rank;
  int nx = 0, ny = 0, nz = 0, nt = 0;
  size_t gridsize, offset;
  double *array;
  double addoffset = 0, scalefactor = 1, missval = cdiInqMissval();
  int laddoffset = 0, lscalefactor = 0, hasMissvals = 0;
  int nset;
  int ftype = 0;
  int len;
  int dtype = CDI_DATATYPE_FLT32;
  char attstring[4096]; /* Buffer to read string attribute back */
  char varname[CDI_MAX_NAME];
  size_t nmiss;
  int num_attrs;

  attstring[0] = 0;
  strcpy(varname, name);

  hid_t dset_id = H5Dopen(loc_id, varname);

  hid_t type_id = H5Dget_type(dset_id); /* get datatype*/

  H5T_class_t type_class = H5Tget_class(type_id);
  if (type_class < 0)
    {
      cdo_abort(" Invalid datatype for %s", varname);
    }
  /*
  else {
    if(type_class == H5T_INTEGER)
      puts("   Datatype is 'H5T_NATIVE_INTEGER'.\n");
    if(type_class == H5T_FLOAT)
      puts("   Datatype is 'H5T_NATIVE_FLOAT'.\n");
    if(type_class == H5T_STRING)
      puts("   Datatype is 'H5T_NATIVE_STRING'.\n");
    if(type_class == H5T_BITFIELD)
      puts("   Datatype is 'H5T_NATIVE_BITFIELD'.\n");
    if(type_class == H5T_OPAQUE)
      puts("   Datatype is 'H5T_NATIVE_OPAQUE'.\n");
    if(type_class == H5T_COMPOUND)
      puts("   Datatype is 'H5T_NATIVE_COMPOUND'.\n");
  }
  */
  hid_t native_type = H5Tget_native_type(type_id, H5T_DIR_ASCEND);
  if (H5Tequal(native_type, H5T_NATIVE_SCHAR) > 0)
    {
      ftype = 0;
      dtype = CDI_DATATYPE_INT8;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_UCHAR) > 0)
    {
      ftype = 0;
      dtype = CDI_DATATYPE_UINT8;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_SHORT) > 0)
    {
      ftype = 0;
      dtype = CDI_DATATYPE_INT16;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_USHORT) > 0)
    {
      ftype = 0;
      dtype = CDI_DATATYPE_UINT16;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_INT) > 0)
    {
      ftype = 0;
      dtype = CDI_DATATYPE_INT32;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_UINT) > 0)
    {
      ftype = 0;
      dtype = CDI_DATATYPE_UINT32;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_FLOAT) > 0)
    {
      ftype = 1;
      dtype = CDI_DATATYPE_FLT32;
    }
  else if (H5Tequal(native_type, H5T_NATIVE_DOUBLE) > 0)
    {
      ftype = 1;
      dtype = CDI_DATATYPE_FLT64;
    }
  else
    {
      cdo_warning("Dataset %s skipped, unsupported native datatype!", varname);
      goto RETURN;
    }
  H5Tclose(native_type);

  dataspace = H5Dget_space(dset_id); /* dataspace handle */
  rank = H5Sget_simple_extent_ndims(dataspace);
  status = H5Sget_simple_extent_dims(dataspace, dims_out, nullptr);

  if (rank == 2)
    {
      nx = dims_out[1];
      ny = dims_out[0];
      nz = 1;
      nt = 1;
    }
  else if (rank == 3)
    {
      nx = dims_out[2];
      ny = dims_out[1];
      nz = 1;
      nt = dims_out[0];
    }
  else
    {
      cdo_warning("Dataset %s skipped, unsupported rank (=%d)!", varname, rank);
      goto RETURN;
    }

  len = (int) strlen(varname);
  if (len > 0 && ((datasets_t *) opdata)->mergelevel)
    if (isdigit(varname[len - 1]) && memcmp(varname, "Data", 4) != 0)
      {
        if (nt > 1) cdo_abort("Combination of nlevel > 1 and ntime > 1 not implemented!");

        nz = atoi(&varname[len - 1]);
        varname[len - 1] = 0;
      }

  gridsize = nx * ny;

  if (nz == 1)
    nset = ((datasets_t *) opdata)->nsets;
  else
    {
      for (nset = 0; nset < ((datasets_t *) opdata)->nsets; ++nset)
        {
          if (strcmp(varname, ((datasets_t *) opdata)->obj[nset].name) == 0) break;
        }

      if (nset >= ((datasets_t *) opdata)->nsets) cdo_abort("3D var %s not found!", varname);
    }

  if (nset < MAX_DSETS)
    {
      if (Options::cdoVerbose) print_filter(dset_id, varname);

      num_attrs = H5Aget_num_attrs(dset_id);
      for (int i = 0; i < num_attrs; i++)
        {
          attr = H5Aopen_idx(dset_id, i);
          atype = H5Aget_type(attr);
          H5Aget_name(attr, sizeof(attname), attname);

          if (strcmp(attname, "CLASS") == 0 || strcmp(attname, "IMAGE_VERSION") == 0 || strcmp(attname, "PALETTE") == 0) continue;

          atype_mem = H5Tget_native_type(atype, H5T_DIR_ASCEND);
          atype_size = H5Tget_size(atype);
          atype_class = H5Tget_class(atype);

          len = strlen(attname);
          for (int k = 0; k < len; ++k) attname[k] = tolower(attname[k]);

          if (strcmp(attname, "intercept") == 0 || strcmp(attname, "offset") == 0)
            {
              if (atype_class == H5T_FLOAT)
                {
                  if (atype_size == 4)
                    {
                      status = H5Aread(attr, H5T_NATIVE_FLOAT, &fattr);
                      if (status >= 0)
                        {
                          addoffset = fattr;
                          laddoffset = 1;
                        }
                    }
                  else
                    {
                      status = H5Aread(attr, H5T_NATIVE_DOUBLE, &dattr);
                      if (status >= 0)
                        {
                          addoffset = dattr;
                          laddoffset = 1;
                        }
                    }

                  if (laddoffset == 0) cdo_warning("Reading of float attribute %s failed!", attname);
                }
              else if (atype_class == H5T_INTEGER)
                {
                  status = H5Aread(attr, H5T_NATIVE_INT, &iattr);
                  if (status >= 0)
                    {
                      addoffset = iattr;
                      laddoffset = 1;
                    }
                  else
                    cdo_warning("Reading of integer attribute %s failed!", attname);
                }
              else
                cdo_warning("Attribute %s has unsupported data type!", attname);
            }
          else if (strcmp(attname, "gain") == 0 || strcmp(attname, "scaling_factor") == 0)
            {
              if (atype_class == H5T_FLOAT)
                {
                  if (atype_size == 4)
                    {
                      status = H5Aread(attr, H5T_NATIVE_FLOAT, &fattr);
                      if (status >= 0)
                        {
                          scalefactor = fattr;
                          lscalefactor = 1;
                        }
                    }
                  else
                    {
                      status = H5Aread(attr, H5T_NATIVE_DOUBLE, &dattr);
                      if (status >= 0)
                        {
                          scalefactor = dattr;
                          lscalefactor = 1;
                        }
                    }

                  if (lscalefactor == 0) cdo_warning("Reading of float attribute %s failed!", attname);
                }
              else if (atype_class == H5T_INTEGER)
                {
                  status = H5Aread(attr, H5T_NATIVE_INT, &iattr);
                  if (status >= 0)
                    {
                      scalefactor = iattr;
                      lscalefactor = 1;
                    }
                  else
                    cdo_warning("Reading of integer attribute %s failed!", attname);
                }
              else
                cdo_warning("Attribute %s has unsupported data type!", attname);
            }
          else if (strncmp(attname, "no_data", 7) == 0 || strncmp(attname, "nodata", 6) == 0)
            {
              if (atype_class == H5T_FLOAT)
                {
                  if (atype_size == 4)
                    {
                      status = H5Aread(attr, H5T_NATIVE_FLOAT, &fattr);
                      if (status >= 0)
                        {
                          missval = fattr;
                          hasMissvals = 1;
                        }
                    }
                  else
                    {
                      status = H5Aread(attr, H5T_NATIVE_DOUBLE, &dattr);
                      if (status >= 0)
                        {
                          missval = dattr;
                          hasMissvals = 1;
                        }
                    }

                  if (hasMissvals == 0) cdo_warning("Reading of float attribute %s failed!", attname);
                }
              else if (atype_class == H5T_INTEGER)
                {
                  status = H5Aread(attr, H5T_NATIVE_INT, &iattr);
                  if (status >= 0)
                    {
                      missval = iattr;
                      hasMissvals = 1;
                    }
                  else
                    cdo_warning("Reading of integer attribute %s failed!", attname);
                }
              else
                cdo_warning("Attribute %s has unsupported data type!", attname);
            }
          else if (strcmp(attname, "description") == 0)
            {
              H5Aread(attr, atype_mem, attstring);
              if (((datasets_t *) opdata)->obj[nset].description) Free(((datasets_t *) opdata)->obj[nset].description);
              ((datasets_t *) opdata)->obj[nset].description = strdup(attstring);
            }
          else if (strcmp(attname, "title") == 0)
            {
              H5Aread(attr, atype_mem, attstring);
              if (((datasets_t *) opdata)->obj[nset].title) Free(((datasets_t *) opdata)->obj[nset].title);
              ((datasets_t *) opdata)->obj[nset].title = strdup(attstring);
            }
          else if (strcmp(attname, "time") == 0)
            {
              H5Aread(attr, atype_mem, attstring);
              if (((datasets_t *) opdata)->obj[nset].time) Free(((datasets_t *) opdata)->obj[nset].time);
              ((datasets_t *) opdata)->obj[nset].time = strdup(attstring);
            }
          else if (strcmp(attname, "unit") == 0)
            {
              H5Aread(attr, atype_mem, attstring);
              ((datasets_t *) opdata)->obj[nset].units = strdup(attstring);
            }

          H5Tclose(atype_mem);
          H5Aclose(attr);
          H5Tclose(atype);
        }

      offset = gridsize * (nz - 1);
      array = ((datasets_t *) opdata)->obj[nset].array;
      array = (double *) Realloc(array, gridsize * nz * nt * sizeof(double));
      ((datasets_t *) opdata)->obj[nset].array = array;
      array = array + offset;

      if (ftype)
        {
          if (dtype == CDI_DATATYPE_FLT32)
            {
              std::vector<float> farray(gridsize * nt);
              status = H5Dread(dset_id, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL, H5P_DEFAULT, farray.data());
              if (status < 0) cdo_abort("Reading of NATIVE_FLOAT variable %s failed!", varname);
              for (size_t i = 0; i < gridsize * nt; ++i) array[i] = farray[i];
            }
          else
            {
              status = H5Dread(dset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, array);
              if (status < 0) cdo_abort("Reading of NATIVE_DOUBLE variable %s failed!", varname);
            }
        }
      else
        {
          Varray<int> iarray(gridsize * nt);
          status = H5Dread(dset_id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, iarray.data());
          if (status < 0) cdo_abort("Reading of NATIVE_INT variable %s failed!", varname);
          for (size_t i = 0; i < gridsize * nt; ++i) array[i] = iarray[i];
        }

      ((datasets_t *) opdata)->obj[nset].name = strdup(varname);
      ((datasets_t *) opdata)->obj[nset].nx = nx;
      ((datasets_t *) opdata)->obj[nset].ny = ny;
      ((datasets_t *) opdata)->obj[nset].nz = nz;
      ((datasets_t *) opdata)->obj[nset].nt = nt;
      ((datasets_t *) opdata)->obj[nset].gridsize = gridsize;

      if (nz > 1)
        {
          if (((datasets_t *) opdata)->obj[nset].dtype != dtype) cdo_warning("Data type changes over levels!");

          if (laddoffset && !DBL_IS_EQUAL(((datasets_t *) opdata)->obj[nset].offset, addoffset))
            cdo_warning("Offset changes over levels!");

          if (lscalefactor && !DBL_IS_EQUAL(((datasets_t *) opdata)->obj[nset].scale, scalefactor))
            cdo_warning("Scalefactor changes over levels!");

          if (hasMissvals && !DBL_IS_EQUAL(((datasets_t *) opdata)->obj[nset].missval, missval))
            cdo_warning("Missing value changes over levels!");
        }

      if (nz == 1) ((datasets_t *) opdata)->nsets++;

      std::vector<bool> mask(gridsize * nt, false);

      nmiss = 0;

      const auto mm = varray_min_max(gridsize * nt, array);

      if (Options::cdoVerbose)
        cdo_print("Dataset %s: missval = %g  addoffset = %g  scalefactor = %g", varname, missval, addoffset, scalefactor);

      if (Options::cdoVerbose)
        cdo_print("Dataset %s: dtype = %d  minval = %g  maxval = %g  missval = %g", varname, dtype, mm.min, mm.max, missval);

      if (dtype == CDI_DATATYPE_UINT8)
        {
          if (mm.min >= 0 && mm.max <= 127) dtype = CDI_DATATYPE_INT8;
        }
      else if (dtype == CDI_DATATYPE_UINT16)
        {
          if (mm.min >= 0 && mm.max <= 32767) dtype = CDI_DATATYPE_INT16;
        }

      laddoffset = IS_NOT_EQUAL(addoffset, 0);
      lscalefactor = IS_NOT_EQUAL(scalefactor, 1);

      if (laddoffset || lscalefactor)
        {
          for (size_t i = 0; i < gridsize * nt; i++)
            if (!DBL_IS_EQUAL(array[i], missval))
              {
                mask[i] = false;

                if (lscalefactor) array[i] *= scalefactor;
                if (laddoffset) array[i] += addoffset;
              }
            else
              {
                nmiss++;
                mask[i] = true;
              }
        }

      double minval = 1e35;
      double maxval = -1e35;
      for (size_t i = 0; i < gridsize * nt; i++)
        if (mask[i] == false)
          {
            minval = std::min(minval, array[i]);
            maxval = std::max(maxval, array[i]);
          }

      if (Options::cdoVerbose)
        cdo_print("Dataset %s: dtype = %d  minval = %g  maxval = %g  missval = %g", varname, dtype, minval, maxval, missval);

      if (nmiss)
        {
          if (!(missval < minval || missval > maxval))
            {
              if (DBL_IS_EQUAL(missval, 255.) && dtype == CDI_DATATYPE_UINT8)
                {
                  missval = -255;
                  dtype = CDI_DATATYPE_INT16;
                  cdo_print("Dataset %s: changed missval to %g and datatype to INT16!", varname, missval);

                  for (size_t i = 0; i < gridsize * nt; i++)
                    if (mask[i]) array[i] = missval;
                }
              else
                cdo_warning(" Missing value is inside the range of valid values!\n"
                            "\tDataset %s,  Missval: %g,  Range: %g - %g",
                            varname, missval, minval, maxval);
            }
        }

      ((datasets_t *) opdata)->obj[nset].dtype = dtype;
      ((datasets_t *) opdata)->obj[nset].loffset = laddoffset;
      ((datasets_t *) opdata)->obj[nset].lscale = lscalefactor;
      ((datasets_t *) opdata)->obj[nset].hasMissvals = hasMissvals;
      ((datasets_t *) opdata)->obj[nset].offset = addoffset;
      ((datasets_t *) opdata)->obj[nset].scale = scalefactor;
      ((datasets_t *) opdata)->obj[nset].missval = missval;
    }
  else
    {
      cdo_warning("Too many datasets (MAX = %d)!", MAX_DSETS);
      goto RETURN;
    }

  H5Sclose(dataspace);

RETURN:

  H5Dclose(dset_id);
  H5Tclose(type_id);
}

static herr_t
obj_info(hid_t loc_id, const char *name, void *opdata)
{
  H5G_stat_t statbuf;
  H5Gget_objinfo(loc_id, name, false, &statbuf);

  H5G_obj_t obj_type = statbuf.type;

  switch (obj_type)
    {
    case H5G_GROUP:
      if (Options::cdoVerbose) cdo_print(" Object with name %s is a group", name);
      if (strcmp(name, "Data") == 0)
        {
          ((datasets_t *) opdata)->mergelevel = true;
          H5Giterate(loc_id, name, nullptr, obj_info, opdata);
        }
      else if (strcmp(name, "Geolocation") == 0)
        {
          ((datasets_t *) opdata)->lgeoloc = true;
        }
      else if (strcmp(name, "Metadata") == 0)
        {
          ((datasets_t *) opdata)->lmetadata = true;
        }
      break;
    case H5G_DATASET:
      if (Options::cdoVerbose) cdo_print(" Object with name %s is a dataset", name);
      if (strstr(name, "PALETTE"))
        {
          if (Options::cdoVerbose) cdo_print("   Skip dataset: %s", name);
        }
      /*else if ( strstr(name, "egion") ) */
      else if (strcmp(name, "region") == 0)
        {
          ((datasets_t *) opdata)->lregion = true;
        }
      else
        {
          if (Options::cdoVerbose) cdo_print("   Read dataset: %s", name);
          read_dataset(loc_id, name, opdata);
        }
      break;
    case H5G_TYPE:
      if (Options::cdoVerbose) cdo_print(" Object with name %s is a named datatype", name);
      if (strcmp(name, "ProjType") == 0)
        {
          ((datasets_t *) opdata)->lprojtype = true;
        }
      break;
    default: cdo_abort(" Unable to identify an object %s", name); break;
    }

  return 0;
}

static void
get_global_att(hid_t file_id, const char *obj_path, int vlistID)
{
  hid_t attr, atype, atype_mem, obj_id, grp_id = -1;
  char attname[CDI_MAX_NAME];
  H5T_class_t type_class;
  int attint;
  double attflt;
  int i, pos;
  int num_attrs;
  char attstring[4096]; /* Buffer to read string attribute back */

  attstring[0] = 0;

  obj_id = H5Gopen(file_id, obj_path);

  num_attrs = H5Aget_num_attrs(obj_id);

  for (i = 0; i < num_attrs; i++)
    {
      attr = H5Aopen_idx(obj_id, i);
      atype = H5Aget_type(attr);
      H5Aget_name(attr, sizeof(attname), attname);

      /* remove illegal characters */
      for (pos = 0; pos < (int) strlen(attname); ++pos)
        if (attname[pos] == '&') attname[pos] = '_';

      atype_mem = H5Tget_native_type(atype, H5T_DIR_ASCEND);
      type_class = H5Tget_class(atype);
      if (type_class == H5T_STRING)
        {
          H5Aread(attr, atype_mem, attstring);
          cdiDefAttTxt(vlistID, CDI_GLOBAL, attname, (int) strlen(attstring), attstring);
        }
      else if (type_class == H5T_INTEGER)
        {
          H5Aread(attr, H5T_NATIVE_INT, &attint);
          cdiDefAttInt(vlistID, CDI_GLOBAL, attname, CDI_DATATYPE_INT32, 1, &attint);
        }
      else if (type_class == H5T_FLOAT)
        {
          H5Aread(attr, H5T_NATIVE_DOUBLE, &attflt);
          cdiDefAttFlt(vlistID, CDI_GLOBAL, attname, CDI_DATATYPE_FLT64, 1, &attflt);
        }
      H5Tclose(atype_mem);
      H5Aclose(attr);
      H5Tclose(atype);
    }

  if (grp_id >= 0) H5Gclose(grp_id);
}

static int
get_vdate(int vlistID)
{
  int64_t vdate = 0;
  int natts;
  int i, len, type;
  char name[CDI_MAX_NAME];
  char attstr[CDI_MAX_NAME];

  cdiInqNatts(vlistID, CDI_GLOBAL, &natts);

  for (i = 0; i < natts; ++i)
    {
      cdiInqAtt(vlistID, CDI_GLOBAL, i, name, &type, &len);
      if (type == CDI_DATATYPE_TXT)
        {
          if (strcmp(name, "DateAndTime") == 0 || strcmp(name, "Date_Time") == 0)
            {
              cdiInqAttTxt(vlistID, CDI_GLOBAL, name, CDI_MAX_NAME, attstr);
              if (len > 8) len = 8;
              attstr[len] = 0;
              vdate = atoi(attstr);
              if (vdate < 999999) vdate = vdate * 100 + 1;
            }
        }
    }

  return vdate;
}

static void
dsets_init(datasets_t *dsets)
{
  dsets->nsets = 0;
  dsets->mergelevel = 0;
  dsets->lgeoloc = 0;
  dsets->lregion = 0;
  dsets->lprojtype = 0;
  dsets->lmetadata = 0;

  for (int i = 0; i < MAX_DSETS; ++i)
    {
      dsets->obj[i].nx = 0;
      dsets->obj[i].ny = 0;
      dsets->obj[i].nz = 0;
      dsets->obj[i].name = nullptr;
      dsets->obj[i].description = nullptr;
      dsets->obj[i].units = nullptr;
      dsets->obj[i].title = nullptr;
      dsets->obj[i].time = nullptr;
      dsets->obj[i].dtype = CdoDefault::DataType;
      dsets->obj[i].lscale = 0;
      dsets->obj[i].loffset = 0;
      dsets->obj[i].hasMissvals = 0;
      dsets->obj[i].missval = cdiInqMissval();
      dsets->obj[i].array = nullptr;
    }
}
#endif

void *
Importcmsaf(void *process)
{
#ifdef HAVE_LIBHDF5
  int gridID = -1, zaxisID;
  int i, offset;
  size_t nmiss;
  int ivar;
  int varID, levelID, tsID;
  double missval;
  herr_t status;  // Generic return value
  datasets_t dsets;
#endif

  cdo_initialize(process);

  operator_check_argc(0);

  if (CdoDefault::FileType == CDI_UNDEFID) CdoDefault::FileType = CDI_FILETYPE_NC;

#ifdef HAVE_LIBHDF5
  dsets_init(&dsets);

  // Open an existing file.
  hid_t file_id = H5Fopen(cdo_get_stream_name(0), H5F_ACC_RDONLY, H5P_DEFAULT);
  if (file_id < 0) cdo_abort("H5Fopen failed on %s", cdo_get_stream_name(0));

  // cmsaf_type = get_cmsaf_type(file_id);

  H5Giterate(file_id, "/", nullptr, obj_info, (void *) &dsets);

  if (dsets.nsets == 0) cdo_abort("No dataset found!");

  int gridsize = dsets.obj[0].gridsize;
  int nx = dsets.obj[0].nx;
  int ny = dsets.obj[0].ny;
  int nz = dsets.obj[0].nz;
  int nt = dsets.obj[0].nt;

  for (ivar = 0; ivar < dsets.nsets; ++ivar)
    if (dsets.obj[ivar].nt > 1)
      {
        nt = dsets.obj[ivar].nt;
        break;
      }

  Varray<int> vtimes;
  if (nt > 1)
    {
      vtimes.resize(nt);

      for (i = 0; i < nt; ++i) vtimes[i] = i * 10000 + 45 * 100;

      if (dsets.obj[ivar].time)
        {
          char *pline = dsets.obj[ivar].time;
          for (i = 0; i < nt; ++i)
            {
              int itime = ((int) strtol(pline, &pline, 10)) * 100;
              if (itime < 0 || itime > 240000)
                {
                  cdo_warning("Wrong time string!");
                  break;
                }
              vtimes[i] = itime;
            }
        }
    }

  if (Options::cdoVerbose)
    for (ivar = 0; ivar < dsets.nsets; ++ivar)
      cdo_print(" Var %d %-20s %dx%d nlev = %d nts = %d", ivar, dsets.obj[ivar].name, nx, ny, nz, dsets.obj[ivar].nt);

  for (ivar = 1; ivar < dsets.nsets; ++ivar)
    {
      if (nx != dsets.obj[0].nx || ny != dsets.obj[0].ny) cdo_abort("Gridsize must not change!");
      if (nz != dsets.obj[0].nz) cdo_abort("Number of levels must not change!");
    }

  if (dsets.lgeoloc)
    {
      gridID = read_geolocation(file_id, nx, ny, dsets.lprojtype);
    }
  else if (dsets.lregion)
    {
      gridID = read_region(file_id, nx, ny);
    }

  if (gridID == -1)
    {
      gridID = gridCreate(GRID_GENERIC, gridsize);
      gridDefXsize(gridID, nx);
      gridDefYsize(gridID, ny);
    }

  if (nz == 1)
    zaxisID = zaxisCreate(ZAXIS_SURFACE, 1);
  else
    {
      Varray<double> levels(nz);
      for (i = 0; i < nz; ++i) levels[i] = i + 1;
      zaxisID = zaxisCreate(ZAXIS_GENERIC, nz);
      zaxisDefLevels(zaxisID, levels.data());
    }

  const auto vlistID = vlistCreate();

  const auto taxisID = cdo_taxis_create(nt > 1 ? TAXIS_RELATIVE : TAXIS_ABSOLUTE);
  taxisDefCalendar(taxisID, CALENDAR_STANDARD);
  vlistDefTaxis(vlistID, taxisID);

  for (ivar = 0; ivar < dsets.nsets; ++ivar)
    {
      varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARYING);
      cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, dsets.obj[ivar].name);
      if (dsets.obj[ivar].description) cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, dsets.obj[ivar].description);
      if (dsets.obj[ivar].units) cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, dsets.obj[ivar].units);
      if (dsets.obj[ivar].title) cdiDefAttTxt(vlistID, varID, "title", (int) strlen(dsets.obj[ivar].title), dsets.obj[ivar].title);

      vlistDefVarDatatype(vlistID, varID, dsets.obj[ivar].dtype);
      if (dsets.obj[ivar].hasMissvals) vlistDefVarMissval(vlistID, varID, dsets.obj[ivar].missval);
      if (dsets.obj[ivar].lscale) vlistDefVarScalefactor(vlistID, varID, dsets.obj[ivar].scale);
      if (dsets.obj[ivar].loffset) vlistDefVarAddoffset(vlistID, varID, dsets.obj[ivar].offset);
    }

  get_global_att(file_id, "/", vlistID);
  if (dsets.lmetadata) get_global_att(file_id, "Metadata", vlistID);

  int vdate = get_vdate(vlistID);
  if (vdate == 0) vdate = 10101;

  const auto streamID = cdo_open_write(1);

  cdo_def_vlist(streamID, vlistID);

  for (tsID = 0; tsID < nt; ++tsID)
    {
      taxisDefVdate(taxisID, vdate);
      const int vtime = (vtimes.empty()) ? 0 : vtimes[tsID];
      taxisDefVtime(taxisID, vtime);
      cdo_def_timestep(streamID, tsID);

      for (ivar = 0; ivar < dsets.nsets; ++ivar)
        {
          varID = ivar;

          if (tsID > 0 && dsets.obj[ivar].nt == 1) continue;

          gridsize = dsets.obj[ivar].gridsize;
          missval = dsets.obj[ivar].missval;

          for (levelID = 0; levelID < nz; ++levelID)
            {
              offset = gridsize * levelID;
              if (nz == 1) offset = gridsize * tsID;
              double *array = dsets.obj[ivar].array + offset;

              const auto mm = varray_min_max_mv(gridsize, array, missval);
              nmiss = gridsize - mm.n;

              if (Options::cdoVerbose)
                cdo_print(" Write var %d,  level %d, nmiss %zu, missval %g, minval %g, maxval %g", varID, levelID, nmiss, missval,
                          mm.min, mm.max);

              cdo_def_record(streamID, varID, levelID);
              cdo_write_record(streamID, array, nmiss);
            }
        }
    }

  // Close file
  status = H5Fclose(file_id);
  (void) (status);  // CDO_UNUSED

  process_def_var_num(vlistNvars(vlistID));

  cdo_stream_close(streamID);

  vlistDestroy(vlistID);
  gridDestroy(gridID);
  zaxisDestroy(zaxisID);
  taxisDestroy(taxisID);

  for (ivar = 0; ivar < dsets.nsets; ++ivar) Free(dsets.obj[ivar].array);

  cdo_finish();
#else
  cdo_abort("HDF5 support not compiled in!");
#endif

  return nullptr;
}
