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

  Copyright (C) 2003-2020 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/

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

#include <time.h>

#include <cdi.h>

#include "dmemory.h"
#include "process_int.h"
#include "griddes.h"
#include "commandline.h"
#include "cdo_cdi_wrapper.h"
#include "cdo_default_values.h"

#ifdef HAVE_LIBNETCDF
#include "netcdf.h"
#endif

static constexpr int nvars_ml = 4;
#ifdef HAVE_LIBNETCDF
static const char strfiletype_ml[] = "Initial file spectral";
#endif

struct VAR
{
  int gridtype;
  int zaxistype;
  int code;
  char *name;
  char *longname;
  char *units;
  int gridID;
  int zaxisID;
  size_t gridsize;
  int nlev;
  double *ptr;
};

struct ATTS
{
  int naint;
  int naflt;
  int natxt;
  char *aintname[1024];
  int *aintentry[1024];
  char *afltname[1024];
  double *afltentry[1024];
  char *atxtname[1024];
  char *atxtentry[1024];
};

static void
iniatts(ATTS *atts)
{
  atts->naint = 0;
  atts->naflt = 0;
  atts->natxt = 0;
}

static void
inivar(VAR *var, int gridtype, int zaxistype, int code, const char *name, const char *longname, const char *units)
{
  var->gridtype = gridtype;
  var->zaxistype = zaxistype;
  var->code = code;
  var->name = nullptr;
  var->longname = nullptr;
  var->units = nullptr;
  if (name) var->name = strdup(name);
  if (longname) var->longname = strdup(longname);
  if (units) var->units = strdup(units);
}

#ifdef HAVE_LIBNETCDF
static void
inivars_ml(VAR **vars)
{
  *vars = (VAR *) Malloc((nvars_ml + 1) * sizeof(VAR));

  inivar(&(*vars)[0], GRID_GAUSSIAN, ZAXIS_HYBRID, 133, "Q", "specific humidity", "kg/kg");
  inivar(&(*vars)[1], GRID_SPECTRAL, ZAXIS_HYBRID, 138, "SVO", "vorticity", "1/s");
  inivar(&(*vars)[2], GRID_SPECTRAL, ZAXIS_HYBRID, 155, "SD", "divergence", "1/s");
  inivar(&(*vars)[3], GRID_SPECTRAL, ZAXIS_HYBRID, 130, "STP", "temperature", "K");
  // Don't change the order (lsp must be the last one)!
  inivar(&(*vars)[4], GRID_SPECTRAL, ZAXIS_SURFACE, 152, "LSP", "log surface pressure", "");
}

static void
nce(int istat)
{
  // This routine provides a simple interface to NetCDF error message routine.

  if (istat != NC_NOERR) cdo_abort(nc_strerror(istat));
}
#endif

static int
import_e5ml(const char *filename, VAR **vars)
{
  int nvars = 0;
#ifdef HAVE_LIBNETCDF
  // open file and check file type
  auto nc_file_id = cdo_cdf_openread(filename);

  char filetype[256];
  nce(nc_get_att_text(nc_file_id, NC_GLOBAL, "file_type", filetype));
  size_t attlen;
  nce(nc_inq_attlen(nc_file_id, NC_GLOBAL, "file_type", &attlen));
  filetype[attlen] = 0;

  if (strcmp(filetype, strfiletype_ml) != 0) return 0;

  inivars_ml(vars);

  // read dimensions

  int nc_dim_id;
  nce(nc_inq_dimid(nc_file_id, "lon", &nc_dim_id));
  size_t dimlen;
  nce(nc_inq_dimlen(nc_file_id, nc_dim_id, &dimlen));
  const auto nlon = (int) dimlen;

  nce(nc_inq_dimid(nc_file_id, "lat", &nc_dim_id));
  nce(nc_inq_dimlen(nc_file_id, nc_dim_id, &dimlen));
  const auto nlat = (int) dimlen;

  const auto gridIDgp = gridCreate(GRID_GAUSSIAN, nlon * nlat);
  gridDefXsize(gridIDgp, nlon);
  gridDefYsize(gridIDgp, nlat);

  nce(nc_inq_dimid(nc_file_id, "nsp", &nc_dim_id));
  nce(nc_inq_dimlen(nc_file_id, nc_dim_id, &dimlen));
  const auto nsp = (int) dimlen;

  const auto gridIDsp = gridCreate(GRID_SPECTRAL, nsp * 2);
  gridDefComplexPacking(gridIDsp, 1);

  nce(nc_inq_dimid(nc_file_id, "nlev", &nc_dim_id));
  nce(nc_inq_dimlen(nc_file_id, nc_dim_id, &dimlen));
  const auto nlev = (int) dimlen;
  const auto nlevp1 = nlev + 1;
  const auto nvct = nlevp1 * 2;

  const auto zaxisIDsfc = zaxisCreate(ZAXIS_SURFACE, 1);
  const auto zaxisIDml = zaxisCreate(ZAXIS_HYBRID, nlev);

  {
    Varray<double> levs(nlev);
    for (int i = 0; i < nlev; i++) levs[i] = i + 1;
    zaxisDefLevels(zaxisIDml, levs.data());
  }
  // read variables

  auto xvals = (double *) Malloc(nlon * sizeof(double));
  auto yvals = (double *) Malloc(nlat * sizeof(double));

  int nc_var_id;
  nce(nc_inq_varid(nc_file_id, "lon", &nc_var_id));
  nce(nc_get_var_double(nc_file_id, nc_var_id, xvals));

  nce(nc_inq_varid(nc_file_id, "lat", &nc_var_id));
  nce(nc_get_var_double(nc_file_id, nc_var_id, yvals));

  gridDefXvals(gridIDgp, xvals);
  gridDefYvals(gridIDgp, yvals);

  Free(xvals);
  Free(yvals);

  auto vct = (double *) Malloc(nvct * sizeof(double));

  nce(nc_inq_varid(nc_file_id, "vct_a", &nc_var_id));
  nce(nc_get_var_double(nc_file_id, nc_var_id, vct));

  nce(nc_inq_varid(nc_file_id, "vct_b", &nc_var_id));
  nce(nc_get_var_double(nc_file_id, nc_var_id, vct + nlevp1));

  zaxisDefVct(zaxisIDml, 2 * nlevp1, vct);
  Free(vct);

  size_t start[3], count[3];
  for (int iv = 0; iv < nvars_ml; iv++)
    {
      size_t nvals = 0;

      const auto gridtype = (*vars)[iv].gridtype;

      if (gridtype == GRID_GAUSSIAN)
        {
          (*vars)[iv].gridID = gridIDgp;
          nvals += nlon * nlat;
        }
      else
        {
          (*vars)[iv].gridID = gridIDsp;
          nvals += nsp * 2;
        }

      (*vars)[iv].zaxisID = zaxisIDml;
      (*vars)[iv].gridsize = nvals;
      (*vars)[iv].nlev = nlev;

      (*vars)[iv].ptr = (double *) Malloc(nlev * nvals * sizeof(double));

      for (int i = 0; i < nlev; i++)
        {
          if (gridtype == GRID_GAUSSIAN)
            {
              start[0] = 0;
              start[1] = i;
              start[2] = 0;
              count[0] = nlat;
              count[1] = 1;
              count[2] = nlon;
            }
          else
            {
              start[0] = 0;
              start[1] = 0;
              start[2] = i;
              count[0] = nsp;
              count[1] = 2;
              count[2] = 1;
            }

          nce(nc_inq_varid(nc_file_id, (*vars)[iv].name, &nc_var_id));
          nce(nc_get_vara_double(nc_file_id, nc_var_id, start, count, (*vars)[iv].ptr + i * nvals));
        }
    }

  // read lsp

  (*vars)[nvars_ml].gridID = gridIDsp;
  (*vars)[nvars_ml].zaxisID = zaxisIDsfc;
  (*vars)[nvars_ml].gridsize = nsp * 2;
  (*vars)[nvars_ml].nlev = 1;

  start[0] = 0;
  start[1] = 0;
  start[2] = nlev;
  count[0] = nsp;
  count[1] = 2;
  count[2] = 1;

  (*vars)[nvars_ml].ptr = (double *) Malloc(nsp * 2 * sizeof(double));

  nce(nc_inq_varid(nc_file_id, "STP", &nc_var_id));
  nce(nc_get_vara_double(nc_file_id, nc_var_id, start, count, (*vars)[nvars_ml].ptr));

  // close input file
  cdo_cdf_close(nc_file_id);

  nvars = nvars_ml + 1;

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

  return nvars;
}

static void
export_e5ml(const char *filename, VAR *vars, int nvars, int64_t vdate, int vtime, int ntr)
{
#ifdef HAVE_LIBNETCDF

  auto date_and_time_in_sec = time(nullptr);

  char timestr[30];
  timestr[0] = 0;

  if (date_and_time_in_sec != -1)
    {
      auto date_and_time = localtime(&date_and_time_in_sec);
      (void) strftime(timestr, sizeof(timestr), "%d/%m/%Y %H:%M", date_and_time);
    }

  const char *username = getenv("LOGNAME");
  if (username == nullptr)
    {
      username = getenv("USER");
      if (username == nullptr) username = "unknown";
    }

  int n2 = 2;

  int lon = 0;
  int lat = 0;
  int nsp = 0;
  int nlev = 0;
  int nlevp1 = 0;
  int nvclev = 0;
  int gridIDgp = -1, zaxisIDml = -1;
  for (int varid = 0; varid < nvars; ++varid)
    {
      const auto gridtype = vars[varid].gridtype;
      const auto zaxistype = vars[varid].zaxistype;

      if (gridtype == GRID_GAUSSIAN && lat == 0)
        {
          gridIDgp = vars[varid].gridID;
          lon = gridInqXsize(vars[varid].gridID);
          lat = gridInqYsize(vars[varid].gridID);
        }
      else if (gridtype == GRID_SPECTRAL && nsp == 0)
        {
          nsp = gridInqSize(vars[varid].gridID);
          nsp = nsp / 2;
        }

      if (zaxistype == ZAXIS_HYBRID && nlev == 0)
        {
          zaxisIDml = vars[varid].zaxisID;
          nlev = zaxisInqSize(vars[varid].zaxisID);
          nlevp1 = nlev + 1;
          nvclev = nlev + 1;
        }
    }

  if (lat == 0) cdo_abort("Gaussian grid not found!");
  if (nsp == 0) cdo_abort("Spectral data not found!");
  if (nlev == 0) cdo_abort("Hybrid level not found!");

  const size_t nlon = lon;
  const size_t nlat = lat;

  const size_t data_size = nlon + nlat + 2 * nvclev + 2 * nsp * 2 * nlev + nsp * 2 * nlevp1 + nlon * nlat * nlev;

  int writemode = NC_CLOBBER;
  if (data_size * 8 > 2147000000)
    {
#if defined(NC_64BIT_OFFSET)
      writemode = NC_CLOBBER | NC_64BIT_OFFSET;
#else
      cdoWarning("Datasize > 2GB and NC_64BIT_OFFSET not available!");
#endif
    }

  // create file
  int nc_file_id;
  nce(nc_create(filename, writemode, &nc_file_id));

  char atttext[1024];
  strcpy(atttext, "IEEE");
  size_t attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "source_type", attlen, atttext));

  strcpy(atttext, command_line());
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "history", attlen, atttext));

  strcpy(atttext, username);
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "user", attlen, atttext));

  strcpy(atttext, timestr);
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "created", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_1", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_2", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_3", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_4", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_5", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_6", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_7", attlen, atttext));

  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "label_8", attlen, atttext));

  int ivdate = vdate;
  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "fdate", NC_INT, 1, &ivdate));
  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "ftime", NC_INT, 1, &vtime));

  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "vdate", NC_INT, 1, &ivdate));
  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "vtime", NC_INT, 1, &vtime));

  // attint = 31;
  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "spherical_truncation_n", NC_INT, 1, &ntr));
  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "spherical_truncation_m", NC_INT, 1, &ntr));
  nce(nc_put_att_int(nc_file_id, NC_GLOBAL, "spherical_truncation_k", NC_INT, 1, &ntr));

  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "file_type", strlen(strfiletype_ml), strfiletype_ml));

  int lat_dimid;
  nce(nc_def_dim(nc_file_id, "lat", lat, &lat_dimid));

  int lon_dimid;
  nce(nc_def_dim(nc_file_id, "lon", lon, &lon_dimid));

  int nlev_dimid, nlevp1_dimid;
  nce(nc_def_dim(nc_file_id, "nlev", nlev, &nlev_dimid));
  nce(nc_def_dim(nc_file_id, "nlevp1", nlevp1, &nlevp1_dimid));

  int nsp_dimid;
  nce(nc_def_dim(nc_file_id, "nsp", nsp, &nsp_dimid));

  int nvclev_dimid;
  nce(nc_def_dim(nc_file_id, "nvclev", nvclev, &nvclev_dimid));

  int n2_dimid;
  nce(nc_def_dim(nc_file_id, "n2", n2, &n2_dimid));

  nce(nc_enddef(nc_file_id));

  // define gaussian grid

  auto xvals = (double *) Malloc(nlon * sizeof(double));
  auto yvals = (double *) Malloc(nlat * sizeof(double));

  gridInqXvals(gridIDgp, xvals);
  gridInqYvals(gridIDgp, yvals);

  nce(nc_redef(nc_file_id));
  int nc_var_id;
  nce(nc_def_var(nc_file_id, "lat", NC_DOUBLE, 1, &lat_dimid, &nc_var_id));
  strcpy(atttext, "Gaussian latitude");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "long_name", attlen, atttext));
  strcpy(atttext, "degrees_N");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "units", attlen, atttext));
  nce(nc_enddef(nc_file_id));
  nce(nc_put_var_double(nc_file_id, nc_var_id, yvals));

  nce(nc_redef(nc_file_id));
  nce(nc_def_var(nc_file_id, "lon", NC_DOUBLE, 1, &lon_dimid, &nc_var_id));
  strcpy(atttext, "longitude");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "long_name", attlen, atttext));
  strcpy(atttext, "degrees_E");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "units", attlen, atttext));
  nce(nc_enddef(nc_file_id));
  nce(nc_put_var_double(nc_file_id, nc_var_id, xvals));

  Free(xvals);
  Free(yvals);

  // define model level

  // nvct = nvclev*2;

  auto vct = zaxisInqVctPtr(zaxisIDml);

  nce(nc_redef(nc_file_id));
  nce(nc_def_var(nc_file_id, "vct_a", NC_DOUBLE, 1, &nvclev_dimid, &nc_var_id));
  strcpy(atttext, "vertical-coordinate parameter set A");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "long_name", attlen, atttext));
  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "units", attlen, atttext));
  nce(nc_enddef(nc_file_id));
  nce(nc_put_var_double(nc_file_id, nc_var_id, vct));

  nce(nc_redef(nc_file_id));
  nce(nc_def_var(nc_file_id, "vct_b", NC_DOUBLE, 1, &nvclev_dimid, &nc_var_id));
  strcpy(atttext, "vertical-coordinate parameter set B");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "long_name", attlen, atttext));
  strcpy(atttext, "");
  attlen = strlen(atttext);
  nce(nc_put_att_text(nc_file_id, nc_var_id, "units", attlen, atttext));
  nce(nc_enddef(nc_file_id));
  nce(nc_put_var_double(nc_file_id, nc_var_id, vct + nlevp1));

  // Free(vct);

  int lspid = -1;
  int nc_stpid = -1;

  size_t start[3], count[3];
  for (int varid = 0; varid < nvars; varid++)
    {
      size_t nvals = 0;

      const auto code = vars[varid].code;
      const auto gridtype = vars[varid].gridtype;

      const auto ilev = zaxisInqSize(vars[varid].zaxisID);

      if (ilev == 1)
        {
          if (code == 152)
            {
              lspid = varid;
              if (gridtype != GRID_SPECTRAL) cdo_abort("%s has wrong gridtype!", vars[varid].name);
            }
          continue;
        }

      if (nlev != ilev) cdo_abort("Unexpected number of level %d!", ilev);

      int dimidsp[9];
      if (gridtype == GRID_GAUSSIAN)
        {
          nvals = nlon * nlat;

          dimidsp[0] = lat_dimid;
          dimidsp[1] = nlev_dimid;
          dimidsp[2] = lon_dimid;
        }
      else if (gridtype == GRID_SPECTRAL)
        {
          nvals = nsp * 2;

          dimidsp[0] = nsp_dimid;
          dimidsp[1] = n2_dimid;

          if (strcmp(vars[varid].name, "STP") == 0 || strcmp(vars[varid].name, "T") == 0)
            dimidsp[2] = nlevp1_dimid;
          else
            dimidsp[2] = nlev_dimid;
        }
      else
        cdo_abort("Unsupported grid!");

      nce(nc_redef(nc_file_id));
      nce(nc_def_var(nc_file_id, vars[varid].name, NC_DOUBLE, 3, dimidsp, &nc_var_id));
      if (vars[varid].longname && *vars[varid].longname)
        nce(nc_put_att_text(nc_file_id, nc_var_id, "long_name", strlen(vars[varid].longname), vars[varid].longname));
      if (vars[varid].units && *vars[varid].units)
        nce(nc_put_att_text(nc_file_id, nc_var_id, "units", strlen(vars[varid].units), vars[varid].units));
      nce(nc_enddef(nc_file_id));

      if (dimidsp[2] == nlevp1_dimid) nc_stpid = nc_var_id;

      for (int i = 0; i < nlev; i++)
        {
          if (gridtype == GRID_GAUSSIAN)
            {
              start[0] = 0;
              start[1] = i;
              start[2] = 0;
              count[0] = nlat;
              count[1] = 1;
              count[2] = nlon;
            }
          else
            {
              start[0] = 0;
              start[1] = 0;
              start[2] = i;
              count[0] = nsp;
              count[1] = 2;
              count[2] = 1;
            }

          nce(nc_put_vara_double(nc_file_id, nc_var_id, start, count, vars[varid].ptr + i * nvals));
        }
    }

  if (lspid == -1) cdo_abort("LSP not found!");
  if (nc_stpid == -1) cdo_abort("STP not found!");

  // write lsp
  start[0] = 0;
  start[1] = 0;
  start[2] = nlev;
  count[0] = nsp;
  count[1] = 2;
  count[2] = 1;

  nce(nc_put_vara_double(nc_file_id, nc_stpid, start, count, vars[lspid].ptr));

  // close input file
  nce(nc_close(nc_file_id));

#else
  cdo_abort("NetCDF support not compiled in!");
#endif
}

void *
Echam5ini(void *process)
{
  cdo_initialize(process);

  const auto IMPORT_E5ML = cdo_operator_add("import_e5ml",  0,  0, nullptr);
  const auto EXPORT_E5ML = cdo_operator_add("export_e5ml",  0,  0, nullptr);

  const auto operatorID = cdo_operator_id();

  if (operatorID == EXPORT_E5ML && process_self().m_ID != 0) cdo_abort("This operator can't be linked with other operators!");

  if (operatorID == IMPORT_E5ML)
    {
      ATTS atts;
      iniatts(&atts);

      VAR *vars = nullptr;
      const auto nvars = import_e5ml(cdo_get_stream_name(0), &vars);
      if (nvars == 0) cdo_abort("Unsupported file type!");

      const auto vlistID2 = vlistCreate();
      vlistDefNtsteps(vlistID2, 0);

      for (int iv = 0; iv < nvars; iv++)
        {
          int varID = vlistDefVar(vlistID2, vars[iv].gridID, vars[iv].zaxisID, TIME_CONSTANT);
          if (vars[iv].code > 0) vlistDefVarCode(vlistID2, varID, vars[iv].code);
          if (vars[iv].name) cdiDefKeyString(vlistID2, varID, CDI_KEY_NAME, vars[iv].name);
          if (vars[iv].longname) cdiDefKeyString(vlistID2, varID, CDI_KEY_LONGNAME, vars[iv].longname);
          if (vars[iv].units) cdiDefKeyString(vlistID2, varID, CDI_KEY_UNITS, vars[iv].units);
          vlistDefVarDatatype(vlistID2, varID, CDI_DATATYPE_FLT64);
        }

      for (int iatt = 0; iatt < atts.natxt; ++iatt)
        {
          // printf("%s: %s\n", atts.atxtname[iatt], atts.atxtentry[iatt]);
          cdiDefAttTxt(vlistID2, CDI_GLOBAL, atts.atxtname[iatt], (int) strlen(atts.atxtentry[iatt]) + 1, atts.atxtentry[iatt]);
        }

      const auto taxisID = cdo_taxis_create(TAXIS_ABSOLUTE);
      vlistDefTaxis(vlistID2, taxisID);

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

      const auto streamID2 = cdo_open_write(1);

      cdo_def_vlist(streamID2, vlistID2);

      int tsID = 0;
      cdo_def_timestep(streamID2, tsID);

      for (int varID = 0; varID < nvars; varID++)
        {
          auto gridsize = vars[varID].gridsize;
          auto nlev = vars[varID].nlev;

          for (int levelID = 0; levelID < nlev; levelID++)
            {
              cdo_def_record(streamID2, varID, levelID);
              cdo_write_record(streamID2, vars[varID].ptr + levelID * gridsize, 0);
            }
        }

      cdo_stream_close(streamID2);

      vlistDestroy(vlistID2);
    }
  else if (operatorID == EXPORT_E5ML)
    {
      char name[CDI_MAX_NAME], longname[CDI_MAX_NAME], units[CDI_MAX_NAME];

      const auto streamID1 = cdo_open_read(0);

      const auto vlistID1 = cdo_stream_inq_vlist(streamID1);
      const auto taxisID = vlistInqTaxis(vlistID1);

      const auto nvars = vlistNvars(vlistID1);

      auto vars = (VAR *) Malloc(nvars * sizeof(VAR));

      int ntr = 0;
      for (int varID = 0; varID < nvars; ++varID)
        {
          auto code = vlistInqVarCode(vlistID1, varID);
          vlistInqVarName(vlistID1, varID, name);
          vlistInqVarLongname(vlistID1, varID, longname);
          vlistInqVarUnits(vlistID1, varID, units);

          if (code < 0) code = 0;
          if (strncmp(name, "var", 3) == 0)
            {
              if (code > 0)
                {
                  if (code == 133)
                    {
                      strcpy(name, "Q");
                      strcpy(longname, "specific humidity");
                      strcpy(units, "kg/kg");
                    }
                  if (code == 138)
                    {
                      strcpy(name, "SVO");
                      strcpy(longname, "vorticity");
                      strcpy(units, "1/s");
                    }
                  if (code == 155)
                    {
                      strcpy(name, "SD");
                      strcpy(longname, "divergence");
                      strcpy(units, "1/s");
                    }
                  if (code == 130)
                    {
                      strcpy(name, "STP");
                      strcpy(longname, "temperature");
                      strcpy(units, "K");
                    }
                  if (code == 152)
                    {
                      strcpy(name, "LSP");
                      strcpy(longname, "log surface pressure");
                    }
                }
            }
          else if (strncmp(name, "LSP", 3) == 0)
            code = 152;

          const auto gridID = vlistInqVarGrid(vlistID1, varID);
          const auto zaxisID = vlistInqVarZaxis(vlistID1, varID);

          const auto gridtype = gridInqType(gridID);
          auto zaxistype = zaxisInqType(zaxisID);

          if (gridtype == GRID_SPECTRAL && ntr == 0) ntr = gridInqTrunc(gridID);

          const auto gridsize = gridInqSize(gridID);
          const auto nlev = zaxisInqSize(zaxisID);

          if (zaxistype == ZAXIS_HYBRID && nlev == 1) zaxistype = ZAXIS_SURFACE;

          inivar(&vars[varID], gridtype, zaxistype, code, name, longname, units);

          vars[varID].gridID = gridID;
          vars[varID].zaxisID = zaxisID;
          vars[varID].gridsize = gridsize;
          vars[varID].nlev = nlev;

          vars[varID].ptr = (double *) Malloc(nlev * gridsize * sizeof(double));
        }

      const auto nrecs = cdo_stream_inq_timestep(streamID1, 0);
      auto vdate = taxisInqVdate(taxisID);
      auto vtime = taxisInqVtime(taxisID);

      if (vdate == 0)
        {
          vdate = 19890101;
          vtime = 120000;
        }

      for (int recID = 0; recID < nrecs; recID++)
        {
          int varID, levelID;
          cdo_inq_record(streamID1, &varID, &levelID);

          const auto gridsize = gridInqSize(vlistInqVarGrid(vlistID1, varID));
          size_t nmiss;
          cdo_read_record(streamID1, vars[varID].ptr + levelID * gridsize, &nmiss);
        }

      cdo_stream_close(streamID1);

      export_e5ml(cdo_get_stream_name(1), vars, nvars, vdate, vtime, ntr);
    }

  // vlistDestroy(vlistID2);

  cdo_finish();

  return nullptr;
}
