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

#include <algorithm>

#include <cdi.h>
#include "cdo_options.h"
#include "util_string.h"
#include "cdo_vlist.h"
#include "compare.h"
#include "cdo_output.h"
#include "functs.h"
#include "array.h"

void
vlistDefineTimestepType(int vlistID, int operfunc)
{
  int stepType = -1;
  // clang-format off
  if      (operfunc == func_mean)  stepType = TSTEP_AVG;
  else if (operfunc == func_avg)   stepType = TSTEP_AVG;
  else if (operfunc == func_sum)   stepType = TSTEP_SUM;
  else if (operfunc == func_range) stepType = TSTEP_RANGE;
  else if (operfunc == func_min)   stepType = TSTEP_MIN;
  else if (operfunc == func_max)   stepType = TSTEP_MAX;
  // clang-format on

  if (stepType != -1)
    {
      const auto nvars = vlistNvars(vlistID);
      for (int varID = 0; varID < nvars; ++varID) vlistDefVarTsteptype(vlistID, varID, stepType);
    }
}

double
cdoZaxisInqLevel(int zaxisID, int levelID)
{
  const auto zaxistype = zaxisInqType(zaxisID);
  return zaxisInqLevels(zaxisID, nullptr) ? zaxisInqLevel(zaxisID, levelID) : (zaxistype == ZAXIS_SURFACE) ? 0 : levelID + 1;
}

int
cdoZaxisInqLevels(int zaxisID, double *levels)
{
  auto size = zaxisInqLevels(zaxisID, nullptr);

  if (levels)
    {
      if (size)
        zaxisInqLevels(zaxisID, levels);
      else
        {
          size = zaxisInqSize(zaxisID);
          if (size == 1 && zaxisInqType(zaxisID) == ZAXIS_SURFACE)
            levels[0] = 0;
          else
            for (int i = 0; i < size; ++i) levels[i] = i + 1;
        }
    }

  return size;
}

static void
compare_lat_reg2d(size_t ysize, int gridID1, int gridID2)
{
  if (ysize > 1)
    {
      Varray<double> yvals1(ysize), yvals2(ysize);
      const auto ny1 = gridInqYvals(gridID1, &yvals1[0]);
      const auto ny2 = gridInqYvals(gridID2, &yvals2[0]);
      if (ny1 == 0 || ny2 == 0) return;

      if (IS_EQUAL(yvals1[0], yvals2[ysize - 1]) && IS_EQUAL(yvals1[ysize - 1], yvals2[0]))
        {
          if (yvals1[0] > yvals2[0])
            cdoAbort("Latitude orientation differ! First grid: N->S; second grid: S->N");
          else
            cdoAbort("Latitude orientation differ! First grid: S->N; second grid: N->S");
        }
      else
        {
          for (size_t i = 0; i < ysize; ++i)
            if (std::fabs(yvals1[i] - yvals2[i]) > 3.e-5)
              {
                cdoWarning("Grid latitudes differ!");
                break;
              }
        }
    }
}

static void
compare_lon_reg2d(size_t xsize, int gridID1, int gridID2)
{
  if (xsize > 1)
    {
      Varray<double> xvals1(xsize), xvals2(xsize);
      const auto nx1 = gridInqXvals(gridID1, &xvals1[0]);
      const auto nx2 = gridInqXvals(gridID2, &xvals2[0]);
      if (nx1 == 0 || nx2 == 0) return;

      for (size_t i = 0; i < xsize; ++i)
        if (std::fabs(xvals1[i] - xvals2[i]) > 3.e-5)
          {
            cdoWarning("Grid longitudes differ!");
            break;
          }
    }
}

static void
compare_grid_unstructured(int gridID1, int gridID2)
{
  if (gridInqXvals(gridID1, nullptr) && gridInqXvals(gridID1, nullptr) == gridInqXvals(gridID2, nullptr)
      && gridInqYvals(gridID1, nullptr) && gridInqYvals(gridID1, nullptr) == gridInqYvals(gridID2, nullptr))
    {
      const auto gridsize = gridInqSize(gridID1);
      Varray<double> xvals1(gridsize), xvals2(gridsize), yvals1(gridsize), yvals2(gridsize);
      gridInqXvals(gridID1, xvals1.data());
      gridInqXvals(gridID2, xvals2.data());
      gridInqYvals(gridID1, yvals1.data());
      gridInqYvals(gridID2, yvals2.data());

      size_t inc = gridsize > 10000 ? gridsize / 1000 : 1;
      for (size_t i = 0; i < gridsize; i += inc)
        if (std::fabs(xvals1[i] - xvals2[i]) > 2.e-5 || std::fabs(yvals1[i] - yvals2[i]) > 2.e-5)
          {
            // printf("%d %g %g %g %g %g %g\n", i, xvals1[i], xvals2[i],
            // yvals1[i], yvals2[i], xvals1[i] - xvals2[i], yvals1[i] - yvals2[i]);
            cdoWarning("Geographic location of some grid points differ!");
            break;
          }
    }
}

void
cdoCompareGrids(int gridID1, int gridID2)
{
  // compare grids of first variable

  if (gridInqType(gridID1) == gridInqType(gridID2))
    {
      if (gridInqType(gridID1) == GRID_GAUSSIAN || gridInqType(gridID1) == GRID_LONLAT)
        {
          const auto xsize = gridInqXsize(gridID1);
          const auto ysize = gridInqYsize(gridID1);

          if (ysize == gridInqYsize(gridID2))
            compare_lat_reg2d(ysize, gridID1, gridID2);
          else
            cdoWarning("ysize of input grids differ!");

          if (xsize == gridInqXsize(gridID2))
            compare_lon_reg2d(xsize, gridID1, gridID2);
          else
            cdoWarning("xsize of input grids differ!");
        }
      else if (gridInqType(gridID1) == GRID_CURVILINEAR || gridInqType(gridID1) == GRID_UNSTRUCTURED)
        {
          compare_grid_unstructured(gridID1, gridID2);
        }
    }
  else if (gridInqSize(gridID1) > 1)
    {
      cdoWarning("Grids have different types! First grid: %s; second grid: %s", gridNamePtr(gridInqType(gridID1)),
                 gridNamePtr(gridInqType(gridID2)));
    }
}

static int
vlistCompareNames(int vlistID1, int varID1, int vlistID2, int varID2)
{
  char name1[CDI_MAX_NAME], name2[CDI_MAX_NAME];
  vlistInqVarName(vlistID1, varID1, name1);
  vlistInqVarName(vlistID2, varID2, name2);
  cstrToLowerCase(name1);
  cstrToLowerCase(name2);
  return strcmp(name1, name2);
}

static int
zaxisCheckLevels(int zaxisID1, int zaxisID2)
{
  if (zaxisID1 != zaxisID2)
    {
      const auto nlev1 = zaxisInqSize(zaxisID1);
      const auto nlev2 = zaxisInqSize(zaxisID2);
      if (nlev1 != nlev2) cdoAbort("Number of levels of the input parameters do not match!");

      Varray<double> lev1(nlev1), lev2(nlev1);
      cdoZaxisInqLevels(zaxisID1, &lev1[0]);
      cdoZaxisInqLevels(zaxisID2, &lev2[0]);

      auto ldiffer = false;
      for (int i = 0; i < nlev1; ++i)
        if (IS_NOT_EQUAL(lev1[i], lev2[i]))
          {
            ldiffer = true;
            break;
          }
      if (ldiffer)
        {
          ldiffer = false;
          for (int i = 0; i < nlev1; ++i)
            if (IS_NOT_EQUAL(lev1[i], lev2[nlev1 - 1 - i]))
              {
                ldiffer = true;
                break;
              }

          if (ldiffer)
            cdoWarning("Input parameters have different levels!");
          else
            cdoWarning("Z-axis orientation differ!");

          return 1;
        }
    }

  return 0;
}

static void
vlistCheckNames(int vlistID1, int vlistID2)
{
  int varID;
  auto nvars = vlistNvars(vlistID1);

  // std::vector<std::array<char,CDI_MAX_NAME>> names1(nvars); C++14?
  // std::vector<std::array<char,CDI_MAX_NAME>> names2(nvars);
  std::vector<std::vector<char>> names1(nvars), names2(nvars);
  for (varID = 0; varID < nvars; varID++) names1[varID].resize(CDI_MAX_NAME);
  for (varID = 0; varID < nvars; varID++) names2[varID].resize(CDI_MAX_NAME);
  for (varID = 0; varID < nvars; varID++) vlistInqVarName(vlistID1, varID, names1[varID].data());
  for (varID = 0; varID < nvars; varID++) vlistInqVarName(vlistID2, varID, names2[varID].data());

  std::sort(names1.begin(), names1.end());
  std::sort(names2.begin(), names2.end());

  for (varID = 0; varID < nvars; varID++)
    if (!cstrIsEqual(names1[varID].data(), names2[varID].data())) break;

  if (varID == nvars) cdoPrint("Use CDO option --sortname to sort the parameter by name (NetCDF only)!");
}

void
vlistCompare(int vlistID1, int vlistID2, int flag)
{
  auto lchecknames = false;

  const auto nvars = vlistNvars(vlistID1);

  if (nvars != vlistNvars(vlistID2)) cdoAbort("Input streams have different number of variables per timestep!");

  if (vlistNrecs(vlistID1) != vlistNrecs(vlistID2))
    cdoAbort("Input streams have different number of %s per timestep!", nvars == 1 ? "layers" : "records");

  for (int varID = 0; varID < nvars; varID++)
    {
      if (nvars > 1)
        {
          if (flag & CMP_NAME)
            {
              if (vlistCompareNames(vlistID1, varID, vlistID2, varID) != 0)
                {
                  cdoWarning("Input streams have different parameter names!");
                  lchecknames = true;
                  flag -= CMP_NAME;
                  //    break;
                }
            }
        }

      if (flag & CMP_GRIDSIZE)
        {
          if (gridInqSize(vlistInqVarGrid(vlistID1, varID)) != gridInqSize(vlistInqVarGrid(vlistID2, varID)))
            {
              char name[CDI_MAX_NAME];
              vlistInqVarName(vlistID1, varID, name);
              cdoAbort("Grid size of the input parameter %s do not match!", name);
            }
        }

      if (flag & CMP_NLEVEL)
        {
          const auto zaxisID1 = vlistInqVarZaxis(vlistID1, varID);
          const auto zaxisID2 = vlistInqVarZaxis(vlistID2, varID);
          if (zaxisCheckLevels(zaxisID1, zaxisID2) != 0) break;
        }
    }

  if (flag & CMP_GRID)
    {
      const auto gridID1 = vlistInqVarGrid(vlistID1, 0);
      const auto gridID2 = vlistInqVarGrid(vlistID2, 0);
      cdoCompareGrids(gridID1, gridID2);
    }

  if (lchecknames) vlistCheckNames(vlistID1, vlistID2);
}

int
vlistCompareX(int vlistID1, int vlistID2, int flag)
{
  const auto nvars = vlistNvars(vlistID1);
  const auto nvars2 = vlistNvars(vlistID2);
  const auto nlevels2 = zaxisInqSize(vlistInqVarZaxis(vlistID2, 0));

  if (nvars2 != 1) cdoAbort("Internal problem, vlistCompareX() called with unexpected vlistID2 argument!");

  for (int varID = 0; varID < nvars; varID++)
    {
      if (flag & CMP_GRIDSIZE)
        {
          if (gridInqSize(vlistInqVarGrid(vlistID1, varID)) != gridInqSize(vlistInqVarGrid(vlistID2, 0)))
            cdoAbort("Grid size of the input parameters do not match!");
        }

      if (flag & CMP_NLEVEL)
        {
          if ((zaxisInqSize(vlistInqVarZaxis(vlistID1, varID)) != nlevels2) && nlevels2 > 1)
            cdoAbort("Number of levels of the input parameters do not match!");
        }
    }

  if (flag & CMP_GRID)
    {
      const auto gridID1 = vlistInqVarGrid(vlistID1, 0);
      const auto gridID2 = vlistInqVarGrid(vlistID2, 0);
      cdoCompareGrids(gridID1, gridID2);
    }

  return nlevels2;
}

void
vlistMap(int vlistID1, int vlistID2, int flag, int mapflag, std::map<int, int> &mapOfVarIDs)
{
  int varID1, varID2;
  const auto nvars1 = vlistNvars(vlistID1);
  const auto nvars2 = vlistNvars(vlistID2);

  std::vector<std::vector<char>> names1(nvars1), names2(nvars2);
  for (varID1 = 0; varID1 < nvars1; varID1++) names1[varID1].resize(CDI_MAX_NAME);
  for (varID2 = 0; varID2 < nvars2; varID2++) names2[varID2].resize(CDI_MAX_NAME);
  for (varID1 = 0; varID1 < nvars1; varID1++) vlistInqVarName(vlistID1, varID1, names1[varID1].data());
  for (varID2 = 0; varID2 < nvars2; varID2++) vlistInqVarName(vlistID2, varID2, names2[varID2].data());

  if (mapflag == 2)
    {
      for (varID2 = 0; varID2 < nvars2; varID2++)
        {
          for (varID1 = 0; varID1 < nvars1; varID1++)
            {
              if (cstrIsEqual(names1[varID1].data(), names2[varID2].data())) break;
            }
          if (varID1 == nvars1)
            {
              cdoAbort("Variable %s not found!", names2[varID2].data());
            }
          else
            {
              mapOfVarIDs[varID1] = varID2;
            }
        }
    }
  else
    {
      for (varID1 = 0; varID1 < nvars1; varID1++)
        {
          for (varID2 = 0; varID2 < nvars2; varID2++)
            {
              if (cstrIsEqual(names1[varID1].data(), names2[varID2].data())) break;
            }
          if (varID2 == nvars2)
            {
              if (mapflag == 3) continue;
              cdoAbort("Variable %s not found!", names1[varID1].data());
            }
          else
            {
              mapOfVarIDs[varID1] = varID2;
            }
        }
    }

  if (mapOfVarIDs.empty()) cdoAbort("No variable found!");

  if (Options::cdoVerbose)
    for (varID1 = 0; varID1 < nvars1; varID1++)
      {
        const auto &it = mapOfVarIDs.find(varID1);
        if (it != mapOfVarIDs.end())
          cdoPrint("Variable %d:%s mapped to %d:%s", varID1, names1[varID1].data(), it->second, names2[it->second].data());
      }

  if (mapOfVarIDs.size() > 1)
    {
      varID2 = mapOfVarIDs.begin()->second;
      for (auto it = ++mapOfVarIDs.begin(); it != mapOfVarIDs.end(); ++it)
        {
          if (it->second < varID2)
            cdoAbort("Variable names must be sorted, use CDO option --sortname to sort the parameter by name (NetCDF only)!");

          varID2 = it->second;
        }
    }

  for (auto it = mapOfVarIDs.begin(); it != mapOfVarIDs.end(); ++it)
    {
      varID1 = it->first;
      varID2 = it->second;

      if (flag & CMP_GRIDSIZE)
        {
          if (gridInqSize(vlistInqVarGrid(vlistID1, varID1)) != gridInqSize(vlistInqVarGrid(vlistID2, varID2)))
            cdoAbort("Grid size of the input parameters do not match!");
        }

      if (flag & CMP_NLEVEL)
        {
          const auto zaxisID1 = vlistInqVarZaxis(vlistID1, varID1);
          const auto zaxisID2 = vlistInqVarZaxis(vlistID2, varID2);
          if (zaxisCheckLevels(zaxisID1, zaxisID2) != 0) break;
        }

      if (flag & CMP_GRID && varID1 == mapOfVarIDs.begin()->first)
        {
          const auto gridID1 = vlistInqVarGrid(vlistID1, varID1);
          const auto gridID2 = vlistInqVarGrid(vlistID2, varID2);
          cdoCompareGrids(gridID1, gridID2);
        }
    }
}

bool
vlistIsSzipped(int vlistID)
{
  const auto nvars = vlistNvars(vlistID);
  for (int varID = 0; varID < nvars; varID++)
    {
      const auto comptype = vlistInqVarCompType(vlistID, varID);
      if (comptype == CDI_COMPRESS_SZIP) return true;
    }

  return false;
}

int
vlistInqNWPV(int vlistID, int varID)
{
  const auto datatype = vlistInqVarDatatype(vlistID, varID);
  // number of words per value; real:1  complex:2
  const auto nwpv = (datatype == CDI_DATATYPE_CPX32 || datatype == CDI_DATATYPE_CPX64) ? 2 : 1;

  return nwpv;
}

size_t
vlist_check_gridsize(int vlistID)
{
  auto lerror = false;
  const auto ngp = gridInqSize(vlistGrid(vlistID, 0));

  // check gridsize
  const auto ngrids = vlistNgrids(vlistID);
  for (int index = 0; index < ngrids; ++index)
    {
      const auto gridID = vlistGrid(vlistID, index);
      if (ngp != gridInqSize(gridID))
        {
          lerror = true;
          break;
        }
    }

  if (lerror)
    {
      cdoPrint("This operator requires all variables on the same horizontal grid.");
      cdoPrint("Horizontal grids found:");
      for (int index = 0; index < ngrids; ++index)
        {
          const auto gridID = vlistGrid(vlistID, index);
          cdoPrint("  grid=%d  type=%s  points=%zu", index + 1, gridNamePtr(gridInqType(gridID)), gridInqSize(gridID));
        }
      cdoAbort("The input stream contains variables on different horizontal grids!");
    }

  return ngp;
}

void
vlist_read_vct(int vlistID, int *rzaxisIDh, int *rnvct, int *rnhlev, int *rnhlevf, int *rnhlevh, Varray<double> &vct)
{
  int zaxisIDh = -1;
  int nhlev = 0, nhlevf = 0, nhlevh = 0;
  int nvct = 0;

  auto lhavevct = false;
  auto nzaxis = vlistNzaxis(vlistID);
  for (int iz = 0; iz < nzaxis; ++iz)
    {
      // bool mono_level = false;
      auto mono_level = true;
      auto zaxisID = vlistZaxis(vlistID, iz);
      auto nlevel = zaxisInqSize(zaxisID);
      auto zaxistype = zaxisInqType(zaxisID);

      if (Options::cdoVerbose)
        cdoPrint("ZAXIS_HYBRID=%d ZAXIS_HYBRID_HALF=%d nlevel=%d mono_level=%d", zaxisInqType(zaxisID) == ZAXIS_HYBRID,
                 zaxisInqType(zaxisID) == ZAXIS_HYBRID_HALF, nlevel, mono_level);

      if ((zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_HYBRID_HALF) && nlevel > 1 && !mono_level)
        {
          Varray<double> level(nlevel);
          cdoZaxisInqLevels(zaxisID, &level[0]);
          int l;
          for (l = 0; l < nlevel; l++)
            {
              if ((l + 1) != (int) (level[l] + 0.5)) break;
            }
          if (l == nlevel) mono_level = true;
        }

      if ((zaxistype == ZAXIS_HYBRID || zaxistype == ZAXIS_HYBRID_HALF) && nlevel > 1 && mono_level)
        {
          nvct = zaxisInqVctSize(zaxisID);
          if (nlevel == (nvct / 2 - 1))
            {
              if (!lhavevct)
                {
                  lhavevct = true;
                  zaxisIDh = zaxisID;
                  nhlev = nlevel;
                  nhlevf = nhlev;
                  nhlevh = nhlevf + 1;

                  vct.resize(nvct);
                  zaxisInqVct(zaxisID, vct.data());
                  if (Options::cdoVerbose)
                    cdoPrint("Detected half-level model definition : nlevel == (nvct/2 - 1) (nlevel: %d, nvct: %d, nhlevf: %d, "
                             "nhlevh: %d) ",
                             nlevel, nvct, nhlevf, nhlevh);
                }
            }
          else if (nlevel == (nvct / 2))
            {
              if (!lhavevct)
                {
                  lhavevct = true;
                  zaxisIDh = zaxisID;
                  nhlev = nlevel;
                  nhlevf = nhlev - 1;
                  nhlevh = nhlev;

                  vct.resize(nvct);
                  zaxisInqVct(zaxisID, vct.data());
                  if (Options::cdoVerbose)
                    cdoPrint(
                        "Detected full-level model definition : nlevel == (nvct/2) (nlevel: %d, nvct: %d, nhlevf: %d, nhlevh: %d) ",
                        nlevel, nvct, nhlevf, nhlevh);
                }
            }
          else if (nlevel == (nvct - 4 - 1))
            {
              if (!lhavevct)
                {
                  Varray<double> rvct(nvct);
                  zaxisInqVct(zaxisID, rvct.data());

                  int voff = 4;
                  if ((int) (rvct[0] + 0.5) == 100000 && rvct[voff] < rvct[voff + 1])
                    {
                      lhavevct = true;
                      zaxisIDh = zaxisID;
                      nhlev = nlevel;
                      nhlevf = nhlev;
                      nhlevh = nhlev + 1;

                      int vctsize = 2 * nhlevh;
                      vct.resize(vctsize);

                      // calculate VCT for LM

                      for (int i = 0; i < vctsize / 2; i++)
                        {
                          if (rvct[voff + i] >= rvct[voff] && rvct[voff + i] <= rvct[3])
                            {
                              vct[i] = rvct[0] * rvct[voff + i];
                              vct[vctsize / 2 + i] = 0;
                            }
                          else
                            {
                              vct[i] = (rvct[0] * rvct[3] * (1 - rvct[voff + i])) / (1 - rvct[3]);
                              vct[vctsize / 2 + i] = (rvct[voff + i] - rvct[3]) / (1 - rvct[3]);
                            }
                        }

                      if (Options::cdoVerbose)
                        {
                          for (int i = 0; i < vctsize / 2; i++)
                            fprintf(stdout, "%5d %25.17f %25.17f\n", i, vct[i], vct[vctsize / 2 + i]);
                        }
                    }
                }
            }
        }
    }

  *rzaxisIDh = zaxisIDh;
  *rnvct = nvct;
  *rnhlev = nhlev;
  *rnhlevf = nhlevf;
  *rnhlevh = nhlevh;
}

void
vlist_change_hybrid_zaxis(int vlistID1, int vlistID2, int zaxisID1, int zaxisID2)
{
  int nvct0 = 0;
  Varray<double> vct;

  const auto nzaxis = vlistNzaxis(vlistID1);
  for (int i = 0; i < nzaxis; ++i)
    {
      const auto zaxisID = vlistZaxis(vlistID1, i);
      const auto nlevel = zaxisInqSize(zaxisID);

      if (zaxisID == zaxisID1 && nlevel > 1)
        {
          const auto nvct = zaxisInqVctSize(zaxisID);
          if (!vct.size())
            {
              nvct0 = nvct;
              vct.resize(nvct);
              zaxisInqVct(zaxisID, vct.data());

              vlistChangeZaxisIndex(vlistID2, i, zaxisID2);
            }
          else
            {
              if (nvct0 == nvct && memcmp(vct.data(), zaxisInqVctPtr(zaxisID), nvct * sizeof(double)) == 0)
                vlistChangeZaxisIndex(vlistID2, i, zaxisID2);
            }
        }
    }
}

int
vlist_get_psvarid(int vlistID, int zaxisID)
{
  char psname[CDI_MAX_NAME];
  int length = CDI_MAX_NAME;
  cdiInqKeyString(zaxisID, CDI_GLOBAL, CDI_KEY_PSNAME, psname, &length);

  if (psname[0])
    {
      char name[CDI_MAX_NAME];
      const auto nvars = vlistNvars(vlistID);
      for (int varID = 0; varID < nvars; ++varID)
        {
          vlistInqVarName(vlistID, varID, name);
          if (cstrIsEqual(name, psname)) return varID;
        }
      if (Options::cdoVerbose) cdoWarning("Surface pressure variable not found - %s", psname);
    }

  return -1;
}

int
vlistGetFirstSpectralGrid(int vlistID)
{
  // find first spectral grid
  const auto ngrids = vlistNgrids(vlistID);
  for (int index = 0; index < ngrids; index++)
    {
      const auto gridID = vlistGrid(vlistID, index);
      if (gridInqType(gridID) == GRID_SPECTRAL) return gridID;
    }

  return -1;
}

int
vlistGetFirstGaussianGrid(int vlistID)
{
  // find first gaussian grid
  const auto ngrids = vlistNgrids(vlistID);
  for (int index = 0; index < ngrids; index++)
    {
      const auto gridID = vlistGrid(vlistID, index);
      if (gridInqType(gridID) == GRID_GAUSSIAN) return gridID;
    }

  return -1;
}

int
vlistGetFirstFourierGrid(int vlistID)
{
  // find first fourier grid
  const auto ngrids = vlistNgrids(vlistID);
  for (int index = 0; index < ngrids; index++)
    {
      const auto gridID = vlistGrid(vlistID, index);
      if (gridInqType(gridID) == GRID_FOURIER) return gridID;
    }

  return -1;
}

void
cdo_check_missval(double missval, const char *varname)
{
  if (DBL_IS_EQUAL(0, missval) || DBL_IS_EQUAL(1, missval))
    {
      static bool lwarn = true;
      if (lwarn)
        {
          lwarn = false;
          cdoWarning("Variable %s has a missing value of %g, this can lead to incorrect results with this operator!", varname, missval);
        }
    }
}
