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

      Exprf      expr            Evaluate expressions
      Exprf      exprf           Evaluate expressions from script file
      Exprf      aexpr           Append evaluated expressions
      Exprf      aexprf          Append evaluated expressions from script file
*/
/*
  Operatoren: +, -, *, \, ^, ==, !=, >, <, >=, <=, <=>, &&, ||, ?:
  Functions: sqrt, exp, log, log10, sin, cos, tan, asin, acos, atan
  Functions: min, max, avg, std, var
  Constansts: M_PI, M_E
*/

#include <algorithm>
#include <sys/stat.h> /* stat */
#include <cstdlib>
#include <cassert>

#include "cdi.h"
#include "julian_date.h"

#include "cdo_options.h"
#include "cdo_vlist.h"
#include "dmemory.h"
#include "process_int.h"
#include <mpim_grid.h>
#include "expr.h"
#include "cdo_zaxis.h"
#include "cdi_lockedIO.h"
#include "string_utilities.h"

void gridcell_areas(int gridID, Varray<double> &array);
int get_surface_ID(int vlistID);  // from Vertstat.cc
struct yy_buffer_state *yy_scan_string(const char *str, void *scanner);

static std::string
exprs_from_argument(const std::vector<std::string> &exprArgv)
{
  std::string exprString{};

  if (exprArgv.size() > 0)
    {
      for (size_t i = 0; i < exprArgv.size(); ++i)
        {
          if (i > 0) exprString += ",";
          exprString += exprArgv[i];
        }
      if (exprString[exprString.size()-1] != ';') exprString += ";";
    }
  else
    {
      operator_check_argc(1);
    }

  return exprString;
}

static std::string
exprs_from_file(const std::vector<std::string> &exprArgv)
{
  if (exprArgv.size() != 1) operator_check_argc(1);
  auto exprf = exprArgv[0].c_str();

  // Open expr script file for reading
  auto fp = fopen(exprf, "r");
  if (!fp) cdo_abort("Open failed on %s", exprf);

  struct stat filestat;
  if (stat(exprf, &filestat) != 0) cdo_abort("Stat failed on %s", exprf);

  const auto fsize = (size_t) filestat.st_size;

  std::string exprString{};
  exprString.reserve(fsize);
  int ichar;
  while ((ichar = fgetc(fp)) != EOF) exprString.push_back(ichar);

  if (exprString.size() == 0) cdo_abort("%s is empty!", exprf);

  fclose(fp);

  return exprString;
}

#define MAX_PARAMS 4096

static std::size_t
replace_all(std::string &inout, const std::string &what, const std::string &with)
{
    std::size_t count{};
    for (std::string::size_type pos{};
         inout.npos != (pos = inout.find(what.data(), pos, what.length()));
         pos += with.length(), ++count)
      {
        inout.replace(pos, what.length(), with.data(), with.length());
      }
    return count;
}

static std::string
exprs_expand(std::string &exprString, const VarList &varList)
{
  auto replaceTemplate = false;
  const std::string templateName = "_ALL_";

  if (exprString.find(templateName) != std::string::npos)
    {
      replaceTemplate = true;
      for (size_t varID = 0; varID < varList.size(); ++varID)
        {
          if (cdo_cmpstr(templateName.c_str(), varList[varID].name))
            {
              replaceTemplate = false;
              break;
            }
        }
    }

  if (replaceTemplate)
    {
      std::string exprStringNew{};
      const auto exprStringArgv = cstr_split_with_seperator(exprString.c_str(), ";");
      for (auto &string : exprStringArgv)
        {
          if (string.find(templateName) == std::string::npos)
            {
              exprStringNew += string + ";";
            }
          else
            {
              for (size_t varID = 0; varID < varList.size(); ++varID)
                {
                  auto tmpString = string;
                  replace_all(tmpString, templateName, varList[varID].name);
                  exprStringNew += tmpString + ";";
                }
            }
        }

      return exprStringNew;
    }

  return exprString;
}

static void
params_init(std::vector<ParamEntry> &params, const VarList &varList, int vlistID)
{
  char longname[CDI_MAX_NAME], units[CDI_MAX_NAME], stdname[CDI_MAX_NAME];

  const auto nvars = vlistNvars(vlistID);
  for (int varID = 0; varID < nvars; ++varID)
    {
      longname[0] = 0;
      units[0] = 0;
      stdname[0] = 0;

      vlistInqVarLongname(vlistID, varID, longname);
      vlistInqVarUnits(vlistID, varID, units);
      int length = CDI_MAX_NAME;
      (void) cdiInqKeyString(vlistID, varID, CDI_KEY_STDNAME, stdname, &length);

      params[varID].type = ParamType::VAR;
      params[varID].isValid = true;
      params[varID].hasMV = true;
      params[varID].gridID = varList[varID].gridID;
      params[varID].zaxisID = varList[varID].zaxisID;
      params[varID].datatype = varList[varID].datatype;
      params[varID].steptype = varList[varID].timetype;
      params[varID].nlat = gridInqYsize(varList[varID].gridID);
      params[varID].ngp = varList[varID].gridsize;
      params[varID].nlev = varList[varID].nlevels;
      params[varID].missval = varList[varID].missval;
      params[varID].name = strdup(varList[varID].name);
      if (longname[0]) params[varID].longname = strdup(longname);
      if (units[0]) params[varID].units = strdup(units);
      if (stdname[0]) params[varID].stdname = strdup(stdname);
    }
}

static void
params_delete(const std::vector<ParamEntry> &params)
{
  for (int varID = 0; varID < MAX_PARAMS; ++varID)
    {
      if (params[varID].data) Free(params[varID].data);
      if (params[varID].name) Free(params[varID].name);
      if (params[varID].longname) Free(params[varID].longname);
      if (params[varID].stdname) Free(params[varID].stdname);
      if (params[varID].units) Free(params[varID].units);
    }
}

static void
params_add_coord(parseParamType &parseArg, int coord, int cdiID, size_t size, const char *units, const char *longname)
{
  const auto ncoords = parseArg.ncoords;
  if (ncoords >= parseArg.maxCoords) cdo_abort("Too many coordinates (limit=%d)", parseArg.maxCoords);

  parseArg.coords[ncoords].needed = false;
  parseArg.coords[ncoords].coord = coord;
  parseArg.coords[ncoords].cdiID = cdiID;
  parseArg.coords[ncoords].size = size;
  if (units)
    {
      parseArg.coords[ncoords].units.resize(strlen(units) + 1);
      strcpy(parseArg.coords[ncoords].units.data(), units);
    }
  if (longname)
    {
      parseArg.coords[ncoords].longname.resize(strlen(longname) + 1);
      strcpy(parseArg.coords[ncoords].longname.data(), longname);
    }

  parseArg.ncoords++;
}

int
params_get_coord_ID(const parseParamType &parseArg, int coord, int cdiID)
{
  const auto ncoords = parseArg.ncoords;
  for (int coordID = 0; coordID < ncoords; ++coordID)
    {
      if (parseArg.coords[coordID].coord == coord && parseArg.coords[coordID].cdiID == cdiID) return coordID;
    }

  cdo_abort("%s: coordinate %c not found!", __func__, coord);

  return -1;
}

static void
params_add_coordinates(int vlistID, parseParamType &parseArg)
{
  char longname[CDI_MAX_NAME], units[CDI_MAX_NAME];

  const auto ngrids = vlistNgrids(vlistID);
  for (int index = 0; index < ngrids; ++index)
    {
      const auto gridID = vlistGrid(vlistID, index);
      const auto size = gridInqSize(gridID);
      int length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_XAXIS, CDI_KEY_UNITS, units, &length);
      params_add_coord(parseArg, 'x', gridID, size, units, "longitude");
      length = CDI_MAX_NAME;
      cdiInqKeyString(gridID, CDI_YAXIS, CDI_KEY_UNITS, units, &length);
      params_add_coord(parseArg, 'y', gridID, size, units, "latitude");

      params_add_coord(parseArg, 'a', gridID, size, "m^2", "grid cell area");
      params_add_coord(parseArg, 'w', gridID, size, nullptr, "grid cell area weights");
    }

  const auto nzaxis = vlistNzaxis(vlistID);
  for (int index = 0; index < nzaxis; ++index)
    {
      const auto zaxisID = vlistZaxis(vlistID, index);
      const auto size = zaxisInqSize(zaxisID);
      int length = CDI_MAX_NAME;
      cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_UNITS, units, &length);
      length = CDI_MAX_NAME;
      cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_LONGNAME, longname, &length);
      params_add_coord(parseArg, 'z', zaxisID, size, units, longname);
      params_add_coord(parseArg, 'i', zaxisID, size, units, "level index");
      params_add_coord(parseArg, 'd', zaxisID, size, units, "delta z");
    }
}

static int
params_add_ts(parseParamType &parseArg)
{
  auto &params = parseArg.params;

  const auto varID = parseArg.nparams;
  if (varID >= parseArg.maxparams) cdo_abort("Too many parameter (limit=%d)", parseArg.maxparams);

  params[varID].name = strdup("_timestep_info");
  params[varID].gridID = parseArg.pointID;
  params[varID].zaxisID = parseArg.surfaceID;
  params[varID].steptype = TIME_VARYING;
  params[varID].ngp = CoordIndex::LEN;
  params[varID].nlev = 1;

  parseArg.nparams++;
  parseArg.cnparams++;

  return varID;
}

static void
parse_param_init(parseParamType &parseArg, int vlistID, int pointID, int zonalID, int surfaceID)
{
  const auto nvars = vlistNvars(vlistID);
  const auto ngrids = vlistNgrids(vlistID);
  const auto nzaxis = vlistNzaxis(vlistID);
  const auto maxCoords = ngrids * 4 + nzaxis * 3;

  parseArg.maxparams = MAX_PARAMS;
  parseArg.params.resize(MAX_PARAMS);
  parseArg.nparams = nvars;
  parseArg.cnparams = nvars;
  parseArg.nvars1 = nvars;
  parseArg.init = true;
  parseArg.debug = (Options::cdoVerbose != 0);
  parseArg.pointID = pointID;
  parseArg.zonalID = zonalID;
  parseArg.surfaceID = surfaceID;
  parseArg.needed.resize(nvars);
  parseArg.coords.resize(maxCoords);
  parseArg.maxCoords = maxCoords;
  parseArg.ncoords = 0;
}

static int
genZonalID(int vlistID)
{
  int zonalID = -1;

  const auto ngrids = vlistNgrids(vlistID);
  for (int index = 0; index < ngrids; ++index)
    {
      const auto gridID = vlistGrid(vlistID, index);
      const auto gridtype = gridInqType(gridID);
      if (gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || gridtype == GRID_GENERIC)
        if (gridInqXsize(gridID) > 1 && gridInqYsize(gridID) >= 1)
          {
            zonalID = gridToZonal(gridID);
            break;
          }
    }

  return zonalID;
}

static void
set_date_and_time(ParamEntry &varts, int calendar, int tsID, const CdiDateTime &vDateTime0, const CdiDateTime &vDateTime)
{
  double jdelta = 0.0;

  if (tsID)
    {
      const auto julianDate0 = julianDate_encode(calendar, vDateTime0);
      const auto julianDate = julianDate_encode(calendar, vDateTime);
      jdelta = julianDate_to_seconds(julianDate_sub(julianDate, julianDate0));
    }

  varts.data[CoordIndex::TIMESTEP] = tsID + 1;
  varts.data[CoordIndex::DATE] = cdiDate_get(vDateTime.date);
  varts.data[CoordIndex::TIME] = cdiTime_get(vDateTime.time);
  varts.data[CoordIndex::DELTAT] = jdelta;

  int year, mon, day;
  int hour, minute, second, ms;
  cdiDate_decode(vDateTime.date, &year, &mon, &day);
  cdiTime_decode(vDateTime.time, &hour, &minute, &second, &ms);

  varts.data[CoordIndex::DAY] = day;
  varts.data[CoordIndex::MONTH] = mon;
  varts.data[CoordIndex::YEAR] = year;
  varts.data[CoordIndex::SECOND] = second;
  varts.data[CoordIndex::MINUTE] = minute;
  varts.data[CoordIndex::HOUR] = hour;
}

static void
addOperators(void)
{
  // clang-format off
  cdo_operator_add("expr",   1, 1, "expressions");
  cdo_operator_add("exprf",  1, 0, "expr script filename");
  cdo_operator_add("aexpr",  0, 1, "expressions");
  cdo_operator_add("aexprf", 0, 0, "expr script filename");
  // clang-format on
}

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

  void *scanner = nullptr;
  yylex_init(&scanner);

  parseParamType parseArg = {};
  yyset_extra(&parseArg, scanner);

  addOperators();

  const auto operatorID = cdo_operator_id();
  const bool replacesVariables = cdo_operator_f1(operatorID);
  const bool readsCommandLine = cdo_operator_f2(operatorID);

  operator_input_arg(cdo_operator_enter(operatorID));

  const auto &exprArgv = cdo_get_oper_argv();

  auto exprString = readsCommandLine ? exprs_from_argument(exprArgv) : exprs_from_file(exprArgv);

  const auto streamID1 = cdo_open_read(0);
  const auto vlistID1 = cdo_stream_inq_vlist(streamID1);

  VarList varList1;
  varListInit(varList1, vlistID1);

  exprString = exprs_expand(exprString, varList1);
  if (Options::cdoVerbose) cdo_print(exprString);

  const auto nvars1 = vlistNvars(vlistID1);

  const auto pointID = gridCreate(GRID_GENERIC, 1);
  const auto zonalID = genZonalID(vlistID1);
  const auto surfaceID = get_surface_ID(vlistID1);

  parse_param_init(parseArg, vlistID1, pointID, zonalID, surfaceID);

  auto &params = parseArg.params;
  params_init(params, varList1, vlistID1);

  // Set all input variables to 'needed' if replacing is switched off
  for (int varID = 0; varID < nvars1; ++varID) parseArg.needed[varID] = !replacesVariables;

  // init function rand()
  std::srand(Options::Random_Seed);

  const auto vartsID = params_add_ts(parseArg);
  parseArg.tsID = vartsID;
  params_add_coordinates(vlistID1, parseArg);

  CDO_parser_errorno = 0;
  yy_scan_string(exprString.c_str(), scanner);
  yyparse(parseArg, scanner);
  if (CDO_parser_errorno != 0) cdo_abort("Syntax error!");

  parseArg.init = false;

  if (Options::cdoVerbose)
    for (int varID = 0; varID < nvars1; ++varID)
      if (parseArg.needed[varID]) cdo_print("Needed var: %d %s", varID, params[varID].name);

  if (Options::cdoVerbose)
    for (int varID = 0; varID < parseArg.nparams; ++varID)
      cdo_print("var: %d %s ngp=%zu nlev=%zu coord=%c", varID, params[varID].name, params[varID].ngp, params[varID].nlev,
                (params[varID].coord == 0) ? ' ' : params[varID].coord);

  std::vector<int> varIDmap(parseArg.nparams);

  const auto vlistID2 = vlistCreate();
  vlistDefNtsteps(vlistID2, vlistNtsteps(vlistID1));
  vlistClearFlag(vlistID1);
  if (!replacesVariables)
    {
      int pidx = 0;
      for (int varID = 0; varID < nvars1; ++varID)
        {
          params[varID].select = false;
          if (!params[varID].remove)
            {
              varIDmap[pidx++] = varID;
              const auto nlevels = varList1[varID].nlevels;
              // printf("Replace %d nlevs %d\n", varID, nlevels);
              for (int levID = 0; levID < nlevels; levID++) vlistDefFlag(vlistID1, varID, levID, true);
            }
        }
    }
  cdo_vlist_copy_flag(vlistID2, vlistID1);  // Copy global attributes

  //  printf("parseArg.nparams %d\n", parseArg.nparams);
  for (int pidx = 0; pidx < parseArg.nparams; pidx++)
    {
      if (pidx < nvars1 && !params[pidx].select) continue;
      if (pidx >= nvars1)
        {
          if (params[pidx].type == ParamType::CONST) continue;
          if (params[pidx].name[0] == '_') continue;
          if (params[pidx].remove) continue;
          if (params[pidx].coord) continue;
        }

      // printf("gridID %d zaxisID %d\n",  params[pidx].gridID, params[pidx].zaxisID);
      const auto varID = vlistDefVar(vlistID2, params[pidx].gridID, params[pidx].zaxisID, params[pidx].steptype);
      cdiDefKeyString(vlistID2, varID, CDI_KEY_NAME, params[pidx].name);
      // printf("add: %d %s %d levs %d\n", pidx,  params[pidx].name, varID, zaxisInqSize(params[pidx].zaxisID));
      if (params[pidx].hasMV) vlistDefVarMissval(vlistID2, varID, params[pidx].missval);
      if (params[pidx].units) cdiDefKeyString(vlistID2, varID, CDI_KEY_UNITS, params[pidx].units);
      if (params[pidx].longname) cdiDefKeyString(vlistID2, varID, CDI_KEY_LONGNAME, params[pidx].longname);
      if (params[pidx].stdname) cdiDefKeyString(vlistID2, varID, CDI_KEY_STDNAME, params[pidx].stdname);
      const auto len = strlen(params[pidx].name);
      if (len > 3 && memcmp(params[pidx].name, "var", 3) == 0)
        {
          if (isdigit(params[pidx].name[3]))
            {
              const auto code = atoi(params[pidx].name + 3);
              vlistDefVarCode(vlistID2, varID, code);
            }
        }
      varIDmap[varID] = pidx;
    }

  if (Options::cdoVerbose)
    {
      for (int varID = 0; varID < nvars1; ++varID)
        if (parseArg.needed[varID]) printf("needed: %d %s\n", varID, parseArg.params[varID].name);
      cdo_print("vlistNvars(vlistID1)=%d, vlistNvars(vlistID2)=%d", vlistNvars(vlistID1), vlistNvars(vlistID2));
    }

  const auto nvars2 = vlistNvars(vlistID2);
  if (nvars2 == 0) cdo_abort("No output variable found!");

  for (int varID = 0; varID < nvars1; ++varID)
    {
      if (parseArg.needed[varID])
        {
          const auto nItems = std::max((size_t) 4, params[varID].ngp * params[varID].nlev);
          params[varID].data = (double *) Malloc(nItems * sizeof(double));
        }
    }

  for (int varID = parseArg.nvars1; varID < parseArg.nparams; ++varID)
    {
      const auto nItems = std::max((size_t) 4, params[varID].ngp * params[varID].nlev);
      params[varID].data = (double *) Malloc(nItems * sizeof(double));
    }

  for (int i = 0; i < parseArg.ncoords; ++i)
    {
      if (parseArg.coords[i].needed)
        {
          auto &cdata = parseArg.coords[i].data;
          const auto csize = parseArg.coords[i].size;
          const auto cdiID = parseArg.coords[i].cdiID;
          const auto coord = parseArg.coords[i].coord;
          if (coord == 'x' || coord == 'y' || coord == 'a' || coord == 'w')
            {
              auto gridID = cdiID;
              const auto ngp = csize;
              cdata.resize(ngp);
              if (coord == 'x' || coord == 'y')
                {
                  gridID = generate_full_point_grid(gridID);

                  if (!gridHasCoordinates(gridID)) cdo_abort("Cell center coordinates missing!");

                  if (coord == 'x') gridInqXvals(gridID, cdata.data());
                  if (coord == 'y') gridInqYvals(gridID, cdata.data());

                  if (gridID != parseArg.coords[i].cdiID) gridDestroy(gridID);
                }
              else if (coord == 'a')
                {
                  gridcell_areas(gridID, cdata);
                }
              else if (coord == 'w')
                {
                  cdata[0] = 1;
                  if (ngp > 1)
                    {
                      const auto wstatus = gridcell_weights(gridID, cdata);
                      if (wstatus) cdo_warning("Grid cell bounds not available, using constant grid cell area weights!");
                    }
                }
            }
          else if (coord == 'z' || coord == 'i' || coord == 'd')
            {
              const auto zaxisID = cdiID;
              const auto nlev = csize;
              cdata.resize(nlev);
              if (coord == 'z')
                {
                  cdo_zaxis_inq_levels(zaxisID, cdata.data());
                }
              else if (coord == 'i')
                {
                  for (size_t k = 0; k < nlev; ++k) cdata[k] = k + 1;
                  cdo_zaxis_inq_levels(zaxisID, cdata.data());
                }
              else if (coord == 'd')
                {
                  varray_fill(nlev, cdata, 1.0);
                  if (zaxisInqLbounds(zaxisID, nullptr) && zaxisInqUbounds(zaxisID, nullptr))
                    {
                      std::vector<double> lbounds(nlev), ubounds(nlev);
                      zaxisInqLbounds(zaxisID, lbounds.data());
                      zaxisInqUbounds(zaxisID, ubounds.data());
                      for (size_t k = 0; k < nlev; ++k) cdata[k] = ubounds[k] - lbounds[k];
                    }
                }
            }
          else
            cdo_abort("Computation of coordinate %c not implemented!", coord);
        }
    }

  for (int varID = parseArg.nvars1; varID < parseArg.nparams; ++varID)
    {
      const auto coord = params[varID].coord;
      if (coord)
        {
          if (coord == 'x' || coord == 'y' || coord == 'a' || coord == 'w')
            {
              const auto coordID = params_get_coord_ID(parseArg, coord, params[varID].gridID);
              const auto gridID = parseArg.coords[coordID].cdiID;
              const auto ngp = parseArg.coords[coordID].size;
              auto &cdata = parseArg.coords[coordID].data;
              assert(gridID == params[varID].gridID);
              assert(!cdata.empty());

              array_copy(ngp, cdata.data(), params[varID].data);
            }
          else if (coord == 'z' || coord == 'i' || coord == 'd')
            {
              const auto coordID = params_get_coord_ID(parseArg, coord, params[varID].zaxisID);
              const auto zaxisID = parseArg.coords[coordID].cdiID;
              const auto nlev = parseArg.coords[coordID].size;
              auto &cdata = parseArg.coords[coordID].data;
              assert(zaxisID == params[varID].zaxisID);
              assert(!cdata.empty());

              array_copy(nlev, cdata.data(), params[varID].data);
            }
          else
            cdo_abort("Computation of coordinate %c not implemented!", coord);
        }
    }

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

  const auto taxisID1 = vlistInqTaxis(vlistID1);
  const auto taxisID2 = taxisDuplicate(taxisID1);
  vlistDefTaxis(vlistID2, taxisID2);

  const auto streamID2 = cdo_open_write(1);
  cdo_def_vlist(streamID2, vlistID2);

  CdiDateTime vDateTime0 = { };
  const auto calendar = taxisInqCalendar(taxisID1);

  int tsID = 0;
  while (true)
    {
      const auto nrecs = cdo_stream_inq_timestep(streamID1, tsID);
      if (nrecs == 0) break;

      const auto vDateTime = taxisInqVdatetime(taxisID1);

      set_date_and_time(params[vartsID], calendar, tsID, vDateTime0, vDateTime);

      vDateTime0 = vDateTime;

      cdo_taxis_copy_timestep(taxisID2, taxisID1);

      cdo_def_timestep(streamID2, tsID);

      // for (int varID = 0; varID < nvars1; ++varID) printf(">>> %s %d\n", params[varID].name, params[varID].isValid);
      for (int varID = 0; varID < nvars1; ++varID) params[varID].isValid = true;
      for (int varID = 0; varID < nvars1; ++varID)
        if (tsID == 0 || params[varID].steptype != TIME_CONSTANT) params[varID].nmiss = 0;

      for (int recID = 0; recID < nrecs; ++recID)
        {
          int varID, levelID;
          cdo_inq_record(streamID1, &varID, &levelID);
          if (parseArg.needed[varID])
            {
              const auto offset = params[varID].ngp * levelID;
              auto vardata = params[varID].data + offset;
              size_t nmiss;
              cdo_read_record(streamID1, vardata, &nmiss);
              params[varID].nmiss += nmiss;

              if (nmiss > 0) cdo_check_missval(params[varID].missval, params[varID].name);
            }
        }

      for (int varID = 0; varID < nvars2; ++varID)
        {
          const auto pidx = varIDmap[varID];
          if (pidx < nvars1) continue;

          params[pidx].nmiss = 0;
          varray_fill(params[pidx].ngp * params[pidx].nlev, params[pidx].data, 0.0);
        }

      parseArg.cnparams = vartsID + 1;
      yy_scan_string(exprString.c_str(), scanner);
      yyparse(parseArg, scanner);

      for (int varID = 0; varID < nvars2; ++varID)
        {
          const auto pidx = varIDmap[varID];

          if (tsID > 0 && params[pidx].steptype == TIME_CONSTANT) continue;

          const auto missval = vlistInqVarMissval(vlistID2, varID);

          const auto ngp = params[pidx].ngp;
          const auto nlev = (int) params[pidx].nlev;
          for (int levelID = 0; levelID < nlev; ++levelID)
            {
              const auto offset = ngp * levelID;
              double *vardata = params[pidx].data + offset;
              const auto nmiss = array_num_mv(ngp, vardata, missval);
              cdo_def_record(streamID2, varID, levelID);
              cdo_write_record(streamID2, vardata, nmiss);
            }
        }

      tsID++;
    }

  cdo_stream_close(streamID2);
  cdo_stream_close(streamID1);

  vlistDestroy(vlistID2);

  yylex_destroy(scanner);

  params_delete(params);

  gridDestroy(pointID);
  if (zonalID != -1) gridDestroy(zonalID);

  cdo_finish();

  return nullptr;
}
