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

  Author: Uwe Schulzweida

*/

/*
   This module contains the following operators:

      Select      select         Select fields
*/

#include <cdi.h>

#include "cdo_options.h"
#include "cdo_rlimit.h"
#include "process_int.h"
#include "cdo_zaxis.h"
#include "datetime.h"
#include "selectkey.h"
#include "printinfo.h"
#include "param_conversion.h"
#include "progress.h"
#include "cdi_lockedIO.h"
#include "cdo_cdi_wrapper.h"

std::vector<bool> cdo_read_timestepmask(std::string const &maskfile);

std::string
dom_to_string(CdiDate date)
{
  constexpr std::array cmons{ "", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };
  auto [year, month, day] = CdoDate(date);
  if (month < 0 || month > 12) { month = 0; }
  return std::to_string(day) + cmons[month];
}

static void
write_const_vars(CdoStreamID streamID2, VarList const &varList, Varray2D<double> &varDataList2)
{
  for (auto const &var : varList.vars)
  {
    if (varDataList2[var.ID].empty()) continue;

    for (int levelID2 = 0; levelID2 < var.numLevels; ++levelID2)
    {
      auto pdata = &varDataList2[var.ID][var.gridSize * levelID2];
      auto numMissVals = array_num_mv(var.gridSize, pdata, var.missval);
      cdo_def_field(streamID2, var.ID, levelID2);
      cdo_write_field(streamID2, pdata, numMissVals);
    }
    varDataList2[var.ID].clear();
    varDataList2[var.ID].shrink_to_fit();
  }
}

static void
eval_timestepmask(std::string const &maskfile, KVList &kvlist)
{
  auto imask = cdo_read_timestepmask(maskfile);
  auto n = static_cast<int>(imask.size());
  if (n == 0) cdo_abort("Read of timestep mask failed!");

  int nvalues = 0;
  for (int i = 0; i < n; ++i)
    if (imask[i]) nvalues++;
  if (nvalues == 0)
  {
    cdo_print("timestepmask has no values!");
    return;
  }

  KeyValues kv;
  kv.key = "timestep";
  kv.nvalues = nvalues;
  kv.values.resize(nvalues);

  for (int i = 0, j = 0; i < n; ++i)
    if (imask[i]) { kv.values[j++] = std::to_string(i + 1); }

  kvlist.push_back(kv);
}

static bool
has_selected_params(VarList const &varList, int vlistID)
{
  for (auto const &var : varList.vars)
  {
    for (int levelID = 0; levelID < var.numLevels; ++levelID)
      if (vlistInqFlag(vlistID, var.ID, levelID) == true) { return true; }
  }

  return false;
}

[[nodiscard]] static bool
has_const_vars(VarList const &varList, std::vector<int8_t> const &processVars)
{
  for (auto const &var : varList.vars)
  {
    if (processVars[var.ID] && var.isConstant) return true;
  }

  return false;
}

static void
vlist_set_flag(int vlistID, VarList const &varList)
{
  for (auto const &var : varList.vars)
  {
    for (int levelID = 0; levelID < var.numLevels; ++levelID) vlistDefFlag(vlistID, var.ID, levelID, true);
  }
}

static void
find_ps_if_hybrid_zaxis(VarList const &varList, std::vector<int8_t> &processVars)
{
  for (auto const &var : varList.vars)
  {
    if (!processVars[var.ID]) continue;
    if (zaxisInqType(var.zaxisID) != ZAXIS_HYBRID) continue;
    auto psvarid = varList_get_psvarid(varList, var.zaxisID);
    if (psvarid != -1 && !processVars[psvarid]) { processVars[psvarid] = true; }
  }
}

static double
datetime_to_double(CdiDateTime dateTime)
{
  return static_cast<double>(cdiDate_get(dateTime.date)) + static_cast<double>(cdiTime_get(dateTime.time)) / 1000000.0;
}

static void
check_validkeys(std::map<std::string, bool> const &validKeyMap)
{
  for (auto const &[key, value] : validKeyMap)
  {
    if (value == false) cdo_abort("Unsupported selection key parameter: '%s'!", key);
  }
}

static void
decode_datetime(CdiDateTime const &vDateTime, KeyInt &year, KeyInt &month, KeyInt &day, KeyInt &hour, KeyInt &minute)
{
  auto [vyear, vmonth, vday] = CdoDate(vDateTime.date);
  auto [vhour, vminute, vsecond] = CdoTime(vDateTime.time);
  year.set(vyear);
  month.set(vmonth);
  day.set(vday);
  hour.set(vhour);
  minute.set(vminute);
}

static void
process_negative_steps(KeyInt &timestep, int numFiles, CdoStreamID &streamID1, int &numSteps, int &tsmax)
{
  // support for negative timestep values
  auto numStepValues = timestep.size();
  for (int i = 0; i < numStepValues; ++i)
  {
    int ptimestep = timestep.get_value_at(i);
    if (ptimestep < 0)
    {
      if (numFiles != 1) cdo_abort("Negative timesteps only supported with one input stream!");
      if (numSteps < 0 && cdo_assert_files_only())
      {
        int tsID = 0;
        while (cdo_stream_inq_timestep(streamID1, tsID)) tsID++;
        numSteps = tsID;
        if (Options::cdoVerbose) cdo_print("Found %d timesteps", numSteps);
      }
      if (numSteps > 0)
      {
        if (Options::cdoVerbose) cdo_print("timestep %d changed to %d", ptimestep, ptimestep + numSteps + 1);
        ptimestep += numSteps + 1;
        timestep.set_value_at(i, ptimestep);
      }
    }
  }

  for (int i = 0; i < numStepValues; ++i) { tsmax = std::max(tsmax, timestep.get_value_at(i)); }
}

static bool
step_is_selected(KeyInt &year, KeyInt &month, KeyInt &day, KeyInt &hour, KeyInt &minute, KeyWord &season)
{
  auto lseason = (season.empty() || season.check_season(month.get()));
  auto lyear = (year.empty() || year.check());
  auto lmonth = (month.empty() || month.check());
  auto lday = (day.empty() || day.check());
  auto lhour = (hour.empty() || hour.check());
  auto lminute = (minute.empty() || minute.check());
  return (lseason && lyear && lmonth && lday && lhour && lminute);
}

[[nodiscard]] static bool
do_var_sel(KeyInt &code, KeyWord &name, KeyWord &param)
{
  return code.size() || name.size() || param.size();
}

[[nodiscard]] static bool
do_var_sel2(KeyInt &ltype, KeyInt &zaxisnum, KeyInt &gridnum, KeyWord &zaxisname, KeyWord &gridname, KeyWord &steptype)
{
  return ltype.size() || zaxisnum.size() || gridnum.size() || zaxisname.size() || gridname.size() || steptype.size();
}

[[nodiscard]] static bool
do_layer_sel(KeyFloat &level, KeyFloat &levrange, KeyInt &levidx)
{
  return level.size() || levrange.size() || levidx.size();
}

[[nodiscard]] static bool
do_time_sel(KeyWord &date, KeyWord &startdate, KeyWord &enddate, KeyWord &season, KeyInt &timestep_of_year, KeyInt &timestep,
            KeyInt &year, KeyInt &month, KeyInt &day, KeyInt &hour, KeyInt &minute, KeyWord &dom)
{
  return date.size() || startdate.size() || enddate.size() || season.size() || timestep_of_year.size() || timestep.size()
         || year.size() || month.size() || day.size() || hour.size() || minute.size() || dom.size();
}

class Select : public Process
{
public:
  using Process::Process;
#ifndef UWES_SELECT_TEST
  inline static CdoModule module = {
    .name = "Select",
    // clang-format off
    .operators = { { "select", "parameter list", SelectHelp },
                   { "delete", "parameter list", SelectHelp } },
    // clang-format on
    .aliases = {},
    .mode = EXPOSED,     // Module mode: 0:intern 1:extern
    .number = CDI_BOTH,  // Allowed number type
    .constraints = { -1, 1, NoRestriction },
  };
#else
  inline static CdoModule module = {
    .name = "Select1",
    .operators = { { "select1", "parameter list", SelectHelp }, { "delete1", "parameter list", SelectHelp } },
    .aliases = {},
    .mode = INTERNAL,    // Module mode: 0:intern 1:extern
    .number = CDI_BOTH,  // Allowed number type
    .constraints = { 1, 1, NoRestriction },
  };
#endif
  inline static auto registration = RegisterEntry<Select>();

private:
  int SELECT{}, DELETE{};
  CdoStreamID streamID2 = CDO_STREAM_UNDEF;
  int tsID2 = 0;
  int vlistID0 = -1, vlistID2 = -1;
  bool dataIsUnchanged{};

  KVList kvlist{};
  int operatorID{};

public:
  void
  init() override
  {
    SELECT = module.get_id("select");
    DELETE = module.get_id("delete");

    dataIsUnchanged = data_is_unchanged();

    auto numFiles = cdo_stream_cnt() - 1;
    cdo::set_numfiles(numFiles + 8);  // needed only if apply is used on input files

    operatorID = cdo_operator_id();

    operator_input_arg(cdo_operator_enter(operatorID));

    auto numArgs = cdo_operator_argc();
    auto const &argList = cdo_get_oper_argv();

    if (numArgs == 0) cdo_abort("Parameter missing!");

    kvlist.name = cdo_module_name();
    if (kvlist.parse_arguments(argList) != 0) cdo_abort("Parse error!");
    if (Options::cdoVerbose) kvlist.print();

    {
      auto kv = kvlist.find("starttime");
      if (kv && kv->nvalues > 0) { kv->key = "startdate"; }
    }

    {
      auto kv = kvlist.find("endtime");
      if (kv && kv->nvalues > 0) { kv->key = "enddate"; }
    }

    {
      auto kv = kvlist.search("timestepmask");
      if (kv && kv->nvalues > 0)
      {
        if (kvlist.search("timestep")) cdo_abort("Parameter timestep and timestepmask can't be combined!");
        eval_timestepmask(kv->values[0], kvlist);
      }
    }
  }

  void
  run() override
  {
    int numVars2 = 0;
    bool hasConstVars = true;
    std::array<char, 32> paramstr{};
    int lastYear = -999999999;
    Field field;
    double startDateFloat = -99999999999.0;
    double endDateFloat = -99999999999.0;
    int taxisID2 = CDI_UNDEFID;
    int numStepsOut = 0;
    bool doTimeSel = false;
    std::vector<int8_t> processVars;
    Varray2D<double> varDataList2;

    std::map<std::string, bool> validKeyMap;
    for (auto const &x : kvlist) { validKeyMap[x.key] = false; }

    KeyInt timestepOfYear(kvlist, validKeyMap, "timestep_of_year", "Timestep of year");
    KeyInt timestep(kvlist, validKeyMap, "timestep", "Timestep");
    KeyInt year(kvlist, validKeyMap, "year", "Year");
    KeyInt month(kvlist, validKeyMap, "month", "Month");
    KeyInt day(kvlist, validKeyMap, "day", "Day");
    KeyInt hour(kvlist, validKeyMap, "hour", "Hour");
    KeyInt minute(kvlist, validKeyMap, "minute", "Minute");
    KeyInt code(kvlist, validKeyMap, "code", "Code number");
    KeyInt levidx(kvlist, validKeyMap, "levidx", "Level index");
    KeyInt ltype(kvlist, validKeyMap, "ltype", "Level type");
    KeyInt zaxisnum(kvlist, validKeyMap, "zaxisnum", "Zaxis number");
    KeyInt gridnum(kvlist, validKeyMap, "gridnum", "Grid number");
    KeyFloat level(kvlist, validKeyMap, "level", "Level");
    KeyFloat levrange(kvlist, validKeyMap, "levrange", "Level range");
    KeyWord name(kvlist, validKeyMap, "name", "Variable name");
    KeyWord param(kvlist, validKeyMap, "param", "Parameter");
    KeyWord zaxisname(kvlist, validKeyMap, "zaxisname", "Zaxis name");
    KeyWord gridname(kvlist, validKeyMap, "gridname", "Grid name");
    KeyWord steptype(kvlist, validKeyMap, "steptype", "Time step type");
    KeyWord startDate(kvlist, validKeyMap, "startdate", "Start time");
    KeyWord endDate(kvlist, validKeyMap, "enddate", "End time");
    KeyWord season(kvlist, validKeyMap, "season", "Season");
    KeyWord date(kvlist, validKeyMap, "date", "Date");
    KeyWord timestepMask(kvlist, validKeyMap, "timestepmask", "Timestep mask");
    KeyWord dayOfMonth(kvlist, validKeyMap, "dom", "Day of month");

    check_validkeys(validKeyMap);

    if (levrange.size() > 0 && levrange.size() != 2) cdo_abort("Key levrange needs two values!");
    if (timestepMask.size() > 1) cdo_abort("Key timestepmask has too many values!");

    auto numFiles = cdo_stream_cnt() - 1;

    DateTimeList timeList;

    cdo::Progress progress(get_id());

    int tsmax = -1;

    int step = 0;
    for (int fileIdx = 0; fileIdx < numFiles; ++fileIdx)
    {
      if (Options::cdoVerbose) cdo_print("Process file: %s", cdo_get_stream_name(fileIdx));

      auto streamID1 = cdo_open_read(fileIdx);
      auto vlistID1 = cdo_stream_inq_vlist(streamID1);
      auto taxisID1 = vlistInqTaxis(vlistID1);

      VarList varList1(vlistID1);
      auto numSteps = varList1.numSteps();

      VarList varList2;

      auto copyConstVars = false;

      if (fileIdx == 0 || (fileIdx > 0 && tsID2 == 0))
      {
        auto xresult = true;

        vlistClearFlag(vlistID1);
        auto numVars = varList1.numVars();
        processVars.resize(numVars);

        if (operatorID == DELETE)
        {
          xresult = false;
          vlist_set_flag(vlistID1, varList1);
        }

        auto findVariable = do_var_sel(code, name, param);
        auto doVarSel = findVariable || do_var_sel2(ltype, zaxisnum, gridnum, zaxisname, gridname, steptype);
        auto doLayerSel = do_layer_sel(level, levrange, levidx);
        doTimeSel
            = do_time_sel(date, startDate, endDate, season, timestepOfYear, timestep, year, month, day, hour, minute, dayOfMonth);

        for (int varID = 0; varID < numVars; ++varID)
        {
          auto const &var = varList1.vars[varID];
          code.set(var.code);
          name.set(var.name);
          // stdname.set(var.stdname);
          cdiParamToString(var.param, paramstr.data(), paramstr.size());
          param.set(paramstr.data());

          ltype.set(zaxis_to_ltype(var.zaxisID));

          zaxisnum.set(vlistZaxisIndex(vlistID1, var.zaxisID) + 1);
          zaxisname.set(zaxisNamePtr(zaxisInqType(var.zaxisID)));
          gridnum.set(vlistGridIndex(vlistID1, var.gridID) + 1);
          gridname.set(gridNamePtr(gridInqType(var.gridID)));

          steptype.set(var.isConstant ? "constant" : "varying");

          auto found_code = code.check();
          auto found_name = name.check();
          auto found_param = param.check();
          auto found_grid = gridnum.check();
          auto found_gname = gridname.check();
          auto found_ltype = ltype.check();
          auto found_zaxis = zaxisnum.check();
          auto found_zname = zaxisname.check();
          auto found_stype = steptype.check();

          if (steptype.size() && !found_stype)
          {
            steptype.set(cdo::get_steptype_name(var.stepType));
            found_stype = steptype.check();
          }

          bool lstep = (steptype.empty() || found_stype);
          bool lvar = (found_code || found_name || found_param);
          bool lgrid = (gridnum.size() || gridname.size()) ? (found_grid || found_gname) : true;
          bool lvert = (ltype.size() || zaxisnum.size() || zaxisname.size()) ? (found_ltype || found_zaxis || found_zname) : true;

          processVars[varID] = (lvar && lgrid && lvert && lstep);
          auto &processVar = processVars[varID];
          if (!processVar && !lvar && !findVariable)
          {
            processVar = found_grid || found_gname || found_stype || found_ltype || found_zaxis || found_zname;
            if (!processVar && !doVarSel && (levidx.size() || level.size() || levrange.size()))
            {
              for (int levelID = 0; levelID < var.numLevels; ++levelID)
              {
                levidx.set(levelID + 1);
                level.set(cdo_zaxis_inq_level(var.zaxisID, levelID));
                if (!processVar && levidx.check_index(var.numLevels)) { processVar = true; }
                if (!processVar && level.check()) { processVar = true; }
                if (!processVar && levrange.check_range(level.get())) { processVar = true; }
              }
            }
          }
        }

        find_ps_if_hybrid_zaxis(varList1, processVars);

        for (int varID = 0; varID < numVars; ++varID)
        {
          if (processVars[varID])
          {
            auto const &var = varList1.vars[varID];
            for (int levelID = 0; levelID < var.numLevels; ++levelID)
            {
              levidx.set(levelID + 1);
              level.set(cdo_zaxis_inq_level(var.zaxisID, levelID));

              if (var.numLevels == 1 && is_equal(level.get(), 0.0))
              {
                (void) level.check();
                vlistDefFlag(vlistID1, varID, levelID, xresult);
              }
              else
              {
                if (levidx.size())
                {
                  if (levidx.check_index(var.numLevels)) vlistDefFlag(vlistID1, varID, levelID, xresult);
                }
                else if (level.size())
                {
                  if (level.check()) vlistDefFlag(vlistID1, varID, levelID, xresult);
                }
                else if (levrange.size())
                {
                  if (levrange.check_range(level.get())) vlistDefFlag(vlistID1, varID, levelID, xresult);
                }
                else
                {
                  vlistDefFlag(vlistID1, varID, levelID, xresult);
                }
              }
            }
          }
        }

        code.check_flag();
        levidx.check_flag();
        ltype.check_flag();
        zaxisnum.check_flag();
        gridnum.check_flag();
        level.check_flag();
        name.check_flag();
        param.check_flag();
        zaxisname.check_flag();
        gridname.check_flag();
        steptype.check_flag();
        levrange.check_range_flag();

        if (has_selected_params(varList1, vlistID1))
        {
          if (doVarSel && doTimeSel) { copyConstVars = has_const_vars(varList1, processVars); }
        }
        else if (!doVarSel && !doLayerSel && doTimeSel)
        {
          copyConstVars = true;
          for (auto const &var : varList1.vars)
          {
            processVars[var.ID] = true;
            for (int levelID = 0; levelID < var.numLevels; ++levelID) vlistDefFlag(vlistID1, var.ID, levelID, true);
          }
        }
        else
        {
          cdo_abort("No variable selected!");
        }

        // if (Options::cdoVerbose) vlistPrint(vlistID1);

        vlistID0 = vlistDuplicate(vlistID1);
        for (auto const &var : varList1.vars)
        {
          for (int levelID = 0; levelID < var.numLevels; ++levelID)
            vlistDefFlag(vlistID0, var.ID, levelID, vlistInqFlag(vlistID1, var.ID, levelID));
        }

        // if (Options::cdoVerbose) vlistPrint(vlistID0);

        vlistID2 = vlistCreate();
        cdo_vlist_copy_flag(vlistID2, vlistID0);

        varList2 = VarList(vlistID2);

        // if (Options::cdoVerbose) vlistPrint(vlistID2);

        taxisID2 = taxisDuplicate(taxisID1);
        numSteps = varList1.numSteps();
        numVars2 = varList2.numVars();
        if (numSteps == 1 && numFiles == 1 && varList2.numVaryingVars() == 0) { numSteps = 0; }

        numStepsOut = (numFiles == 1 && doTimeSel == false) ? numSteps : -1;
        if (operatorID == SELECT && timestep.size() > 0) { numStepsOut = timestep.size(); }
        if (numStepsOut == 0 && numFiles > 1)
        {
          hasConstVars = false;
          for (int varID = 0; varID < numVars2; ++varID) vlistDefVarTimetype(vlistID2, varID, TIME_VARYING);
        }

        // support for negative timestep values
        process_negative_steps(timestep, numFiles, streamID1, numSteps, tsmax);

        startDate.set(startDate.get_value_at(0));
        endDate.set(endDate.get_value_at(0));
        if (startDate.size()) { startDateFloat = datestr_to_double(startDate.get(), 0); }
        if (endDate.size()) { endDateFloat = datestr_to_double(endDate.get(), 1); }
      }
      else
      {
        varList_compare(VarList(vlistID0), varList1);
      }

      if (numVars2 == 0)
      {
        cdo_warning("No variable selected!");
        return;
      }

      if (copyConstVars) varDataList2.resize(numVars2);

      auto stopReading = false;
      int tsID1 = 0;
      while (true)
      {
        auto numFields = cdo_stream_inq_timestep(streamID1, tsID1);
        if (numFields == 0) break;

        auto fstatus = (numSteps > 1) ? fileIdx + (tsID1 + 1.0) / numSteps : fileIdx + 1.0;
        progress.update(fstatus / numFiles);

        step++;
        timestep.set(step);
        auto copyTimestep = true;

        if (doTimeSel)
        {
          copyTimestep = false;

          if (operatorID == SELECT && timestep.size() > 0 && step > tsmax)
          {
            stopReading = true;
            break;
          }

          timeList.taxis_inq_timestep(taxisID1, 0);
          auto vDateTime = timeList.vDateTime(0);
          decode_datetime(vDateTime, year, month, day, hour, minute);

          if (year.get() != lastYear)
          {
            timestepOfYear.set(0);
            lastYear = year.get();
          }

          timestepOfYear.set(timestepOfYear.get() + 1);

          if (timestep.check()) { copyTimestep = true; }
          if (timestepOfYear.check()) { copyTimestep = true; }

          if (!copyTimestep && date.empty() && timestep.empty() && timestepOfYear.empty() && dayOfMonth.empty())
          {
            if (step_is_selected(year, month, day, hour, minute, season)) { copyTimestep = true; }
          }

          auto dateTimeFloat = datetime_to_double(vDateTime);
          if (endDate.size())
          {
            copyTimestep = (dateTimeFloat <= endDateFloat);
            if (dateTimeFloat > endDateFloat)
            {
              endDate.set_flag_at(0);
              if (operatorID == SELECT)
              {
                stopReading = true;
                break;
              }
            }
          }

          if (startDate.size())
          {
            copyTimestep = (dateTimeFloat >= startDateFloat);
            if (dateTimeFloat >= startDateFloat) startDate.set_flag_at(0);
          }

          if (date.size())
          {
            date.set(datetime_to_string(vDateTime));
            if (date.check_date()) { copyTimestep = true; }
          }

          if (dayOfMonth.size())
          {
            dayOfMonth.set(dom_to_string(vDateTime.date));
            if (dayOfMonth.check_date()) { copyTimestep = true; }
          }

          if (operatorID == DELETE) { copyTimestep = !copyTimestep; }

          if (copyTimestep && fileIdx == 0 && tsID1 == 0) { copyConstVars = false; }
        }

        if (copyTimestep)
        {
          cdo_taxis_copy_timestep(taxisID2, taxisID1);
          if (streamID2 == CDO_STREAM_UNDEF)
          {
            auto isLastTimestep = ((numFiles == 1) && (numStepsOut > 1) && (numStepsOut == (tsID1 + 1)));
            if (isLastTimestep && tsID2 == 0) { numStepsOut = 1; }
            vlistDefNtsteps(vlistID2, numStepsOut);
            streamID2 = cdo_open_write(numFiles);
            vlistDefTaxis(vlistID2, taxisID2);
            cdo_def_vlist(streamID2, vlistID2);
          }

          cdo_def_timestep(streamID2, tsID2);

          if (copyConstVars && tsID2 == 0) write_const_vars(streamID2, varList2, varDataList2);

          for (int fieldID = 0; fieldID < numFields; ++fieldID)
          {
            auto [varID, levelID] = cdo_inq_field(streamID1);
            if (vlistInqFlag(vlistID0, varID, levelID) == true)
            {
              auto const &var = varList1.vars[varID];
              if (hasConstVars && tsID2 > 0 && tsID1 == 0 && var.isConstant) continue;

              auto varID2 = vlistFindVar(vlistID2, varID);
              auto levelID2 = vlistFindLevel(vlistID2, varID, levelID);

              cdo_def_field(streamID2, varID2, levelID2);
              if (dataIsUnchanged) { cdo_copy_field(streamID1, streamID2); }
              else
              {
                field.init(var);
                cdo_read_field(streamID1, field);
                cdo_write_field(streamID2, field);
              }
            }
          }

          if (copyConstVars && tsID2 == 0) write_const_vars(streamID2, varList2, varDataList2);

          tsID2++;
        }
        else if (copyConstVars && fileIdx == 0 && tsID1 == 0)
        {
          for (int fieldID = 0; fieldID < numFields; ++fieldID)
          {
            auto [varID, levelID] = cdo_inq_field(streamID1);
            if (vlistInqFlag(vlistID0, varID, levelID) == true)
            {
              auto varID2 = vlistFindVar(vlistID2, varID);
              auto const &var = varList2.vars[varID2];
              if (var.isConstant)
              {
                auto levelID2 = vlistFindLevel(vlistID2, varID, levelID);
                if (levelID == 0) varDataList2[varID2].resize(var.gridSize * var.numLevels);
                size_t numMissVals;
                cdo_read_field(streamID1, &varDataList2[varID2][var.gridSize * levelID2], &numMissVals);
              }
            }
          }
        }

        tsID1++;
      }

      cdo_stream_close(streamID1);

      if (stopReading) break;
    }

    timestepOfYear.check_flag();
    timestep.check_flag();
    year.check_flag();
    month.check_flag();
    day.check_flag();
    hour.check_flag();
    minute.check_flag();
    startDate.check_flag();
    // enddate.check_flag();
    season.check_flag();
    date.check_flag();
    dayOfMonth.check_flag();
  }

  void
  close() override
  {
    if (streamID2 != CDO_STREAM_UNDEF) cdo_stream_close(streamID2);

    vlistDestroy(vlistID0);
    vlistDestroy(vlistID2);

    if (tsID2 == 0) cdo_abort("No timesteps selected!");
  }
};
