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

  Copyright (C) 2003-2021 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

#ifdef _OPENMP
#include <omp.h>
#endif

#include <iostream>
#include <algorithm>
#include <vector>

#ifdef HAVE_EXECINFO_H
#include <execinfo.h>
#endif

#ifdef HAVE_FEENABLEEXCEPT
#ifndef __USE_GNU
#define __USE_GNU  // gives us feenableexcept()
#endif
#endif
#include <cfenv>
#include <sys/stat.h>
#include <unistd.h> /* sysconf, gethostname */
#include <cstring>
#include <csignal>

#include <cdi.h>

#include "cdo_getopt.h"
#include "cdo_rlimit.h"
#include "cdo_task.h"
#include <mpim_grid.h>
#include <griddes.h>
#include "cdo_default_values.h"
#include "cdo_cdi_wrapper.h"
#include "param_conversion.h"
#include "progress.h"

#ifdef HAVE_LIBPTHREAD
#include "pthread_debug.h"
#endif

#include "module_list.h"
#include "module_info.h"
#include "percentiles.h"
#include "util_wildcards.h"
#include "util_string.h"
#include "process_int.h"
#include "cdo_options.h"
#include "timer.h"
#include "commandline.h"
#include "mpmo_color.h"
#include "cdo_output.h"
#include "cdo_features.h"
#include "cdo_zaxis.h"
#include "compare.h"
#include "dmemory.h"
#include "table.h"
#include "datetime.h"
#include "remap_grid_cell_search.h"
#include "cdo_pthread.h"
#include "institution.h"
#include "cdo_apply.h"

#ifndef VERSION
#define VERSION "0.0.1"
#endif

#define MAX_NUM_VARNAMES 256

static ProcessManager g_processManager;

void
cdo_exit()
{
  g_processManager.kill_processes();
  exit(EXIT_FAILURE);
}

static int Debug = 0;
static int DebugLevel = 0;
static int Version = 0;
static int Help = 0;
static int CDO_numThreads = 0;
static int timer_total;
static int CDO_netcdf_hdr_pad = 0;
static int CDO_Rusage = 0;
static bool applyDryRun = false;

extern "C" void streamGrbDefDataScanningMode(int scanmode);

void set_point_search_method(const std::string &methodstr);

static void
cdo_stackframe()
{
#if defined HAVE_EXECINFO_H && defined HAVE_BACKTRACE
  void *callstack[32];
  const auto frames = backtrace(callstack, 32);
  char **messages = backtrace_symbols(callstack, frames);

  fprintf(stderr, "[bt] Execution path:\n");
  if (messages)
    {
      for (int i = 0; i < frames; ++i) fprintf(stderr, "[bt] %s\n", messages[i]);
      free(messages);
    }
#endif
}

#ifdef HAVE_FEENABLEEXCEPT
static int
cdo_feenableexcept(const int excepts)
{
  return feenableexcept(excepts);
}
#else
static int
cdo_feenableexcept(const int excepts)
{
  static fenv_t fenv;
  int old_excepts = -1;  // previous masks
  if (fegetenv(&fenv)) return -1;

#if defined(HAVE_FENV_T___CONTROL) && defined(HAVE_FENV_T___MXCSR)
  const unsigned new_excepts = ((unsigned) excepts) & FE_ALL_EXCEPT;
  old_excepts = (int) (fenv.__control & FE_ALL_EXCEPT);

  // unmask
  fenv.__control &= ~new_excepts;
  fenv.__mxcsr &= ~(new_excepts << 7);
#endif

  return (fesetenv(&fenv) ? -1 : old_excepts);
}
#endif

static void
cdoSignalHandler(const int signo)
{
  if (signo == SIGFPE)
    {
      cdo_stackframe();
      cdo_abort("floating-point exception!");
    }
}

static void
cdoSetDigits(const char *const arg)
{
  char *ptr1 = 0;
  if (arg != 0 && (int) strlen(arg) > 0 && arg[0] != ',') Options::CDO_flt_digits = (int) strtol(arg, &ptr1, 10);

  if (Options::CDO_flt_digits < 1 || Options::CDO_flt_digits > 20)
    cdo_abort("Unreasonable value for float significant digits: %d", Options::CDO_flt_digits);

  if (ptr1 && *ptr1 == ',')
    {
      char *ptr2 = 0;
      Options::CDO_dbl_digits = (int) strtol(ptr1 + 1, &ptr2, 10);
      if (ptr2 == ptr1 + 1 || Options::CDO_dbl_digits < 1 || Options::CDO_dbl_digits > 20)
        cdo_abort("Unreasonable value for double significant digits: %d", Options::CDO_dbl_digits);
    }
}

static void
cdo_version()
{
  const int filetypes[] = { CDI_FILETYPE_SRV, CDI_FILETYPE_EXT, CDI_FILETYPE_IEG, CDI_FILETYPE_GRB,  CDI_FILETYPE_GRB2,
                            CDI_FILETYPE_NC,  CDI_FILETYPE_NC2, CDI_FILETYPE_NC4, CDI_FILETYPE_NC4C, CDI_FILETYPE_NC5 };
  const char *typenames[] = { "srv", "ext", "ieg", "grb1", "grb2", "nc1", "nc2", "nc4", "nc4c", "nc5" };

  fprintf(stderr, "%s\n", cdo::Version);
#ifdef SYSTEM_TYPE
  fprintf(stderr, "System: %s\n", SYSTEM_TYPE);
#endif
#ifdef CXX_COMPILER
  fprintf(stderr, "CXX Compiler: %s\n", CXX_COMPILER);
#ifdef CXX_VERSION
  fprintf(stderr, "CXX version : %s\n", CXX_VERSION);
#endif
#endif
#ifdef C_COMPILER
  fprintf(stderr, "C Compiler: %s\n", C_COMPILER);
#ifdef C_VERSION
  fprintf(stderr, "C version : %s\n", C_VERSION);
#endif
#endif
#ifdef F77_COMPILER
  fprintf(stderr, "F77 Compiler: %s\n", F77_COMPILER);
#ifdef F77_VERSION
  fprintf(stderr, "F77 version : %s\n", F77_VERSION);
#endif
#endif

  cdo_print_features();
  cdo_print_libraries();

  fprintf(stderr, "Filetypes: ");
  set_text_color(stderr, BRIGHT, GREEN);
  for (size_t i = 0; i < sizeof(filetypes) / sizeof(int); ++i)
    if (cdiHaveFiletype(filetypes[i])) fprintf(stderr, "%s ", typenames[i]);
  reset_text_color(stderr);
  fprintf(stderr, "\n");

  cdiPrintVersion();
  fprintf(stderr, "\n");
}

static void
cdo_variableInputs()
{
  set_text_color(stderr, BRIGHT, BLUE);
  fprintf(stderr, "#==============================================================================#\n");
  reset_text_color(stderr);
  fprintf(stderr, "    For operators with variable number of inputs:\n");
  fprintf(stderr, "    Brackets can be used for grouping input to the right operator.\n");
  reset_text_color(stderr);
  fprintf(stderr, "    example:\n");
  fprintf(stderr, "       -add -select,x=0 [ file1 -add -topo -file2 ] -merge [ file3 file4 ] out\n");
  set_text_color(stderr, BRIGHT, BLUE);
  fprintf(stderr, "#==============================================================================#\n");
  reset_text_color(stderr);
}

static void
cdo_usage()
{
  const char *name;

  fprintf(stderr, "usage : cdo  [Options]  Operator1  [-Operator2  [-OperatorN]]\n");
  fprintf(stderr, "\n");
  fprintf(stderr, "  Options:\n");
  set_text_color(stderr, BLUE);
  fprintf(stderr, "    -a             Generate an absolute time axis\n");
  fprintf(stderr, "    -A             Dry run that shows processed cdo call\n");
  fprintf(stderr, "    --attribs      Lists all operators with choosen features or the attributes of given operator(s)\n"
                  "                   operator name or a combination of [arbitrary/filesOnly/onlyFirst/noOutput/obase]\n");
  fprintf(stderr, "    -b <nbits>     Set the number of bits for the output precision\n");
  fprintf(stderr, "                   (I8/I16/I32/F32/F64 for nc1/nc2/nc4/nc4c/nc5; U8/U16/U32 for nc4/nc4c/nc5;"
                  " F32/F64 for grb2/srv/ext/ieg; P1 - P24 for grb1/grb2)\n");
  fprintf(stderr, "                   Add L or B to set the byteorder to Little or Big endian\n");
  fprintf(stderr, "    --cmor         CMOR conform NetCDF output\n");
  fprintf(stderr, "    -C, --color    Set behaviour of colorized output messages <auto,no,all>\n");
  fprintf(stderr, "    --double       Using double precision for data in memory.\n");
  fprintf(stderr, "    --eccodes      Use ecCodes to decode/encode GRIB1 messages\n");
  fprintf(stderr, "    --enableexcept <except>\n");
  fprintf(stderr, "                   Set individual floating-point traps "
                  "(DIVBYZERO, INEXACT, INVALID, OVERFLOW, UNDERFLOW, ALL_EXCEPT)\n");
  fprintf(stderr, "    -f, --format <format>\n");
  fprintf(stderr, "                   Format of the output file. (grb1/grb2/nc1/nc2/nc4/nc4c/nc5/srv/ext/ieg)\n");
  fprintf(stderr, "    -g <grid>      Set default grid name or file. Available grids: \n");
  fprintf(stderr,
          "                   F<XXX>, t<RES>, tl<RES>, global_<DXY>, r<NX>x<NY>, g<NX>x<NY>, gme<NI>, lon=<LON>/lat=<LAT>\n");
  fprintf(stderr, "    -h, --help     Help information for the operators\n");
  fprintf(stderr, "    --ignore_time_bounds  Ignores time bounds for time range statistics\n");
  fprintf(stderr, "    --no_history   Do not append to NetCDF \"history\" global attribute\n");
  fprintf(stderr, "    --netcdf_hdr_pad, --hdr_pad, --header_pad <nbr>\n");
  fprintf(stderr, "                   Pad NetCDF output header with nbr bytes\n");
  fprintf(stderr, "    -k <chunktype> NetCDF4 chunk type: auto, grid or lines\n");
  fprintf(stderr, "    -L             Lock IO (sequential access)\n");
  // fprintf(stderr, "    -M             Switch to indicate that the I/O streams have missing values\n");
  fprintf(stderr, "    -m <missval>   Set the missing value of non NetCDF files (default: %g)\n", cdiInqMissval());
  fprintf(stderr, "    -O             Overwrite existing output file, if checked\n");
  fprintf(stderr, "    --operators    List of all operators\n");
  fprintf(stderr, "    --pedantic     Warnings count as errors\n");
#ifdef _OPENMP
  fprintf(stderr, "    -P <nthreads>  Set number of OpenMP threads\n");
#endif
  fprintf(stderr, "    --percentile <method>\n");
  fprintf(stderr, "                   Percentile method: nrank, nist, rtype8, numpy, numpy_lower, numpy_higher, numpy_nearest\n");
  fprintf(stderr, "    --precision <float_digits[,double_digits]>\n");
  fprintf(stderr, "                   Precision to use in displaying floating-point data (default: 7,15)\n");
  if (ITSME) fprintf(stderr, "    --pointsearchmethod [full/kdtree/nanoflann/spherepart/latbins]\n");
  fprintf(stderr, "    --reduce_dim   Reduce NetCDF dimensions\n");
  fprintf(stderr, "    --no_remap_weights Switch off generation of remap weights\n");
  if (ITSME) fprintf(stderr, "    --remap_weights [0/1] Generate remap weights (default: 1)\n");
  fprintf(stderr, "    -R, --regular  Convert GRIB1 data from global reduced to regular Gaussian grid (cgribex only)\n");
  fprintf(stderr, "    -r             Generate a relative time axis\n");
  fprintf(stderr, "    -S             Create an extra output stream for the module TIMSTAT. This stream\n");
  fprintf(stderr, "                   contains the number of non missing values for each output period.\n");
  fprintf(stderr, "    -s, --silent   Silent mode\n");
  fprintf(stderr, "    --seed <seed>  Seed for a new sequence of pseudo-random numbers.\n");
  fprintf(stderr, "    --single       Using single precision for data in memory.\n");
  fprintf(stderr, "    --sortname     Alphanumeric sorting of NetCDF parameter names\n");
  fprintf(stderr, "    -t <codetab>   Set GRIB1 default parameter code table name or file (cgribex only)\n");
  fprintf(stderr, "                   Predefined tables: ");
  for (int id = 0; id < tableInqNumber(); id++)
    if ((name = tableInqNamePtr(id))) fprintf(stderr, " %s", name);
  fprintf(stderr, "\n");

  fprintf(stderr, "    --timestat_date <srcdate>\n");
  fprintf(stderr, "                   Target timestamp (temporal statistics): "
                  "first, middle, midhigh or last source timestep.\n");
  fprintf(stderr, "    -V, --version  Print the version number\n");
  fprintf(stderr, "    -v, --verbose  Print extra details for some operators\n");
  fprintf(stderr, "    -w             Disable warning messages\n");
  fprintf(stderr, "    --worker <num> Number of worker to decode/decompress GRIB records\n");
  fprintf(stderr, "    -z szip        SZIP compression of GRIB1 records\n");
  fprintf(stderr, "       aec         AEC compression of GRIB2 records\n");
  fprintf(stderr, "       jpeg        JPEG compression of GRIB2 records\n");
  fprintf(stderr, "        zip[_1-9]  Deflate compression of NetCDF4 variables\n");
#ifdef HIRLAM_EXTENSIONS
  fprintf(stderr, "    --Dkext <debLev>   Setting debugLevel for extensions\n");
  fprintf(stderr, "    --outputGribDataScanningMode <mode>   Setting grib scanning mode for data in output file <0, 64, 96>; "
                  "Default is 64\n");
#endif  // HIRLAM_EXTENSIONS
  reset_text_color(stderr);
  fprintf(stderr, "\n");

  fprintf(stderr, "  Operators:\n");
  fprintf(stderr, "    Use option --operators for a list of all operators.\n");

  fprintf(stderr, "\n");
  fprintf(stderr, "  CDO version %s, Copyright (C) 2003-2021 Uwe Schulzweida\n", VERSION);
  fprintf(stderr, "  This is free software and comes with ABSOLUTELY NO WARRANTY\n");
  fprintf(stderr, "  Report bugs to <https://mpimet.mpg.de/cdo>\n");
}

static void
cdo_init_is_tty()
{
  struct stat statbuf;
  fstat(0, &statbuf);
  if (S_ISCHR(statbuf.st_mode)) stdin_is_tty = true;
  fstat(1, &statbuf);
  if (S_ISCHR(statbuf.st_mode))
    {
      stdout_is_tty = true;
      progress::stdout_is_tty = true;
    }
  fstat(2, &statbuf);
  if (S_ISCHR(statbuf.st_mode)) stderr_is_tty = true;
}

static void
cdo_print_help(const char **help)
{
  if (!help)
    fprintf(stderr, "No help available for this operator!\n");
  else
    {
      size_t help_size = 0;
      while (help[help_size]) help_size++;
      for (size_t i = 0; i < help_size; i++)
        {
          const auto doPrint = !(help[i][0] == '\0' && help[i + 1][0] == ' ');
          if (doPrint)
            {
              if (color_enabled())
                {
                  if (cdo_cmpstr(help[i], "NAME") || cdo_cmpstr(help[i], "SYNOPSIS") || cdo_cmpstr(help[i], "DESCRIPTION")
                      || cdo_cmpstr(help[i], "OPERATORS") || cdo_cmpstr(help[i], "NAMELIST") || cdo_cmpstr(help[i], "PARAMETER")
                      || cdo_cmpstr(help[i], "ENVIRONMENT") || cdo_cmpstr(help[i], "NOTE") || cdo_cmpstr(help[i], "EXAMPLES"))
                    {
                      set_text_color(stdout, BRIGHT);
                      fprintf(stdout, "%s", help[i]);
                      reset_text_color(stdout);
                      fprintf(stdout, "\n");
                    }
                  else
                    fprintf(stdout, "%s\n", help[i]);
                }
              else
                {
                  fprintf(stdout, "%s\n", help[i]);
                }
            }
        }
    }
}

#undef IsBigendian
#define IsBigendian() (u_byteorder.c[sizeof(long) - 1])

static void
setDefaultDataType(const char *datatypestr)
{
  static const union
  {
    unsigned long l;
    unsigned char c[sizeof(long)];
  } u_byteorder = { 1 };
  enum
  {
    D_UINT,
    D_INT,
    D_FLT,
    D_CPX
  };
  int dtype = -1;

  const auto datatype = tolower(*datatypestr);
  // clang-format off
  if      (datatype == 'i') { dtype = D_INT;  datatypestr++; }
  else if (datatype == 'u') { dtype = D_UINT; datatypestr++; }
  else if (datatype == 'f') { dtype = D_FLT;  datatypestr++; }
  else if (datatype == 'c') { dtype = D_CPX;  datatypestr++; }
  else if (datatype == 'p') {                 datatypestr++; }
  // clang-format on

  if (isdigit((int) *datatypestr))
    {
      const auto nbits = atoi(datatypestr);
      datatypestr += 1;
      if (nbits >= 10) datatypestr += 1;

      if (dtype == -1)
        {
          if (nbits > 0 && nbits < 32)
            CdoDefault::DataType = nbits;
          else if (nbits == 32)
            CdoDefault::DataType = (CdoDefault::FileType == CDI_FILETYPE_GRB) ? CDI_DATATYPE_PACK32 : CDI_DATATYPE_FLT32;
          else if (nbits == 64)
            CdoDefault::DataType = CDI_DATATYPE_FLT64;
          else
            {
              cdo_warning("Unsupported number of bits %d!", nbits);
              cdo_warning("Use I8/I16/I32/F32/F64 for nc1/nc2/nc4/nc4c/nc5; U8/U16/U32 for nc4/nc4c/nc5; F32/F64 for "
                          "grb2/srv/ext/ieg; P1 - P24 for grb1/grb2.");
              cdo_abort("Unsupported number of bits!");
            }
        }
      else
        {
          // clang-format off
          if (dtype == D_INT)
            {
              if      (nbits ==  8) CdoDefault::DataType = CDI_DATATYPE_INT8;
              else if (nbits == 16) CdoDefault::DataType = CDI_DATATYPE_INT16;
              else if (nbits == 32) CdoDefault::DataType = CDI_DATATYPE_INT32;
              else cdo_abort("Unsupported number of bits = %d for datatype INT!", nbits);
            }
          else if (dtype == D_UINT)
            {
              if      (nbits ==  8) CdoDefault::DataType = CDI_DATATYPE_UINT8;
              else if (nbits == 16) CdoDefault::DataType = CDI_DATATYPE_UINT16;
              else if (nbits == 32) CdoDefault::DataType = CDI_DATATYPE_UINT32;
              else cdo_abort("Unsupported number of bits = %d for datatype UINT!", nbits);
            }
          else if (dtype == D_FLT)
            {
              if      (nbits == 32) CdoDefault::DataType = CDI_DATATYPE_FLT32;
              else if (nbits == 64) CdoDefault::DataType = CDI_DATATYPE_FLT64;
              else cdo_abort("Unsupported number of bits = %d for datatype FLT!", nbits);
            }
          else if (dtype == D_CPX)
            {
              if      (nbits == 32) CdoDefault::DataType = CDI_DATATYPE_CPX32;
              else if (nbits == 64) CdoDefault::DataType = CDI_DATATYPE_CPX64;
              else cdo_abort("Unsupported number of bits = %d for datatype CPX!", nbits);
            }
          // clang-format on
        }
    }

  if (*datatypestr != 0)
    {
      if (*datatypestr == 'l' || *datatypestr == 'L')
        {
          if (IsBigendian()) CdoDefault::Byteorder = CDI_LITTLEENDIAN;
          datatypestr++;
        }
      else if (*datatypestr == 'b' || *datatypestr == 'B')
        {
          if (!IsBigendian()) CdoDefault::Byteorder = CDI_BIGENDIAN;
          datatypestr++;
        }
      else
        {
          cdo_abort("Unsupported character in number of bytes: >%s< !", datatypestr);
        }
    }
}

static void
setDefaultFileType(const char *filetypestr)
{
  if (filetypestr)
    {
      size_t len = 0;

      // clang-format off
      if      (cdo_cmpstrLenRhs(filetypestr, "grb2", len)) CdoDefault::FileType = CDI_FILETYPE_GRB2;
      else if (cdo_cmpstrLenRhs(filetypestr, "grb1", len)) CdoDefault::FileType = CDI_FILETYPE_GRB;
      else if (cdo_cmpstrLenRhs(filetypestr, "grb",  len)) CdoDefault::FileType = CDI_FILETYPE_GRB;
      else if (cdo_cmpstrLenRhs(filetypestr, "nc2",  len)) CdoDefault::FileType = CDI_FILETYPE_NC2;
      else if (cdo_cmpstrLenRhs(filetypestr, "nc4c", len)) CdoDefault::FileType = CDI_FILETYPE_NC4C;
      else if (cdo_cmpstrLenRhs(filetypestr, "nc4",  len)) CdoDefault::FileType = CDI_FILETYPE_NC4;
      else if (cdo_cmpstrLenRhs(filetypestr, "nc5",  len)) CdoDefault::FileType = CDI_FILETYPE_NC5;
      else if (cdo_cmpstrLenRhs(filetypestr, "nc1",  len)) CdoDefault::FileType = CDI_FILETYPE_NC;
      else if (cdo_cmpstrLenRhs(filetypestr, "nc",   len)) CdoDefault::FileType = CDI_FILETYPE_NC2;
      else if (cdo_cmpstrLenRhs(filetypestr, "srv",  len)) CdoDefault::FileType = CDI_FILETYPE_SRV;
      else if (cdo_cmpstrLenRhs(filetypestr, "ext",  len)) CdoDefault::FileType = CDI_FILETYPE_EXT;
      else if (cdo_cmpstrLenRhs(filetypestr, "ieg",  len)) CdoDefault::FileType = CDI_FILETYPE_IEG;
      else
        {
          cdo_warning("Unsupported filetype %s!", filetypestr);
          cdo_warning("Available filetypes: grb1/grb2/nc1/nc2/nc4/nc4c/nc5/srv/ext/ieg");
          cdo_abort("Unsupported filetype %s!", filetypestr);
        }
      // clang-format on

      const char *ftstr = filetypestr + len;

      if (CdoDefault::FileType != CDI_UNDEFID && *ftstr != 0)
        {
          if (*ftstr == '_')
            {
              setDefaultDataType(++ftstr);
            }
          else
            {
              cdo_warning("Unexpected character >%c< in file type >%s<!", *ftstr, filetypestr);
              cdo_warning("Use format[_nbits] with:");
              cdo_warning("    format = grb1, grb2, nc1, nc2, nc4, nc4c, nc5, srv, ext or ieg");
              cdo_warning("    nbits  = 32/64 for grb2/nc1/nc2/nc4/nc4c/nc5/srv/ext/ieg; 1 - 24 for grb1/grb2");
              cdo_abort("Unexpected character in file type option!");
            }
        }
    }
}

#include <inttypes.h>

static auto
alignof_address(void *ptr) -> int
{
  const auto n = reinterpret_cast<int64_t> (ptr);
  return (int)(n & (-n));
}

static auto
alignof_malloc_data(const std::vector<int> &tsize) -> int
{
  int align = 1 << 30;
  const auto n = tsize.size();

  std::vector<double *> ptr(n);

  for (size_t i = 0; i < n; ++i)
    {
      ptr[i] = (double *) malloc(tsize[i] * sizeof(double));
      align = std::min(align, alignof_address(ptr[i]));
    }
  for (auto &p : ptr) free(p);

  return align;
}

static auto
alignof_vector_data(const std::vector<int> &tsize) -> int
{
  int align = 1 << 30;
  const auto n = tsize.size();

  std::vector<std::vector<double>> ptr(n);

  for (size_t i = 0; i < n; ++i)
    {
      ptr[i].resize(tsize[i]);
      align = std::min(align, alignof_address(ptr[i].data()));
    }

  return align;
}

static void
defineCompress(const char *arg)
{
  const size_t len = strlen(arg);

  if (strncmp(arg, "szip", len) == 0)
    {
      Options::cdoCompType = CDI_COMPRESS_SZIP;
      Options::cdoCompLevel = 0;
    }
  else if (strncmp(arg, "aec", len) == 0 || strncmp(arg, "ccsds", len) == 0)
    {
      Options::cdoCompType = CDI_COMPRESS_AEC;
      Options::cdoCompLevel = 0;
    }
  else if (strncmp(arg, "jpeg", len) == 0)
    {
      Options::cdoCompType = CDI_COMPRESS_JPEG;
      Options::cdoCompLevel = 0;
    }
  else if (strncmp(arg, "zip", 3) == 0)
    {
      Options::cdoCompType = CDI_COMPRESS_ZIP;
      Options::cdoCompLevel = (len == 5 && arg[3] == '_' && isdigit(arg[4])) ? atoi(&arg[4]) : 1;
    }
  else
    {
      cdo_abort("Compression type '%s' unsupported!", arg);
    }
}

static void
defineChunktype(const std::string &arg)
{
  // clang-format off
  if      ("auto"  == arg) Options::cdoChunkType = CDI_CHUNK_AUTO;
  else if ("grid"  == arg) Options::cdoChunkType = CDI_CHUNK_GRID;
  else if ("lines" == arg) Options::cdoChunkType = CDI_CHUNK_LINES;
  else cdo_abort("Chunk type '%s' unsupported!", arg.c_str());
  // clang-format on
}

std::vector<std::string>
defineVarnames(const char *const arg)
{

  std::string strArgs = std::string(arg);
  std::vector<std::string> newVarnames;

  const char delim = ',';
  size_t previous = 0;
  size_t current = strArgs.find(delim);

  while (current != std::string::npos)
    {
      newVarnames.push_back(strArgs.substr(previous, current - previous));
      previous = current + 1;
      current = strArgs.find(delim, previous);
    }
  newVarnames.push_back(strArgs.substr(previous, current - previous));

  return newVarnames;
}

static void
get_env_vars()
{
  cdo::Username = getenv("LOGNAME");
  if (cdo::Username == nullptr)
    {
      cdo::Username = getenv("USER");
      if (cdo::Username == nullptr) cdo::Username = "unknown";
    }

  auto envstr = getenv("CDO_DOWNLOAD_PATH");
  if (envstr && *envstr)
    {
      cdo::DownloadPath = envstr;
      if (Options::cdoVerbose) fprintf(stderr, "CDO_DOWNLOAD_PATH = %s\n", cdo::DownloadPath);
    }

  envstr = getenv("CDO_ICON_GRIDS");
  if (envstr && *envstr)
    {
      cdo::IconGrids = envstr;
      if (Options::cdoVerbose) fprintf(stderr, "CDO_ICON_GRIDS = %s\n", cdo::IconGrids);
    }

  envstr = getenv("CDO_DISABLE_HISTORY");
  if (envstr)
    {
      if (parameter_to_bool(envstr) == true)
        {
          Options::CDO_Reset_History = true;
          if (Options::cdoVerbose) fprintf(stderr, "CDO_DISABLE_HISTORY = %s\n", envstr);
        }
    }

  envstr = getenv("CDO_RESET_HISTORY");
  if (envstr)
    {
      if (parameter_to_bool(envstr) == true)
        {
          Options::CDO_Reset_History = true;
          if (Options::cdoVerbose) fprintf(stderr, "CDO_RESET_HISTORY = %s\n", envstr);
        }
    }

  envstr = getenv("CDO_HISTORY_INFO");
  if (envstr)
    {
      Options::CDO_Append_History = parameter_to_bool(envstr);
      if (Options::cdoVerbose) fprintf(stderr, "CDO_HISTORY_INFO = %s\n", envstr);
    }

  cdo::File_Suffix[0] = 0;
  envstr = getenv("CDO_FILE_SUFFIX");
  if (envstr)
    {
      if (envstr[0])
        {
          strncat(cdo::File_Suffix, envstr, sizeof(cdo::File_Suffix) - 1);
          if (Options::cdoVerbose) fprintf(stderr, "CDO_FILE_SUFFIX = %s\n", envstr);
        }
    }

  envstr = getenv("CDO_DISABLE_FILESUFFIX");
  if (envstr)
    {
      if (parameter_to_bool(envstr) == true)
        {
          strcat(cdo::File_Suffix, "nullptr");
          if (Options::cdoVerbose) fprintf(stderr, "CDO_DISABLE_FILESUFFIX = %s\n", envstr);
        }
    }

  envstr = getenv("CDO_DIAG");
  if (envstr)
    {
      if (parameter_to_bool(envstr) == true)
        {
          Options::cdoDiag = true;
          if (Options::cdoVerbose) fprintf(stderr, "CDO_DIAG = %s\n", envstr);
        }
    }

  envstr = getenv("CDO_USE_FFTW");
  if (envstr)
    {
      Options::Use_FFTW = parameter_to_bool(envstr);
      if (Options::cdoVerbose) fprintf(stderr, "Options::Use_FFTW = %s\n", envstr);
    }

  envstr = getenv("CDO_VERSION_INFO");
  if (envstr)
    {
      Options::VersionInfo = parameter_to_bool(envstr);
      if (Options::cdoVerbose) fprintf(stderr, "Options::VersionInfo = %s\n", envstr);
    }
}

static void
print_system_info()
{
  fprintf(stderr, "\n");
  fprintf(stderr, "CDO_Color           = %d\n", mpmo_get_color_mode());
  fprintf(stderr, "Options::CDO_Reset_History   = %d\n", Options::CDO_Reset_History);
  fprintf(stderr, "CDO_File_Suffix     = %s\n", cdo::File_Suffix);
  fprintf(stderr, "CdoDefault::FileType  = %d\n", CdoDefault::FileType);
  fprintf(stderr, "CdoDefault::DataType  = %d\n", CdoDefault::DataType);
  fprintf(stderr, "CdoDefault::Byteorder = %d\n", CdoDefault::Byteorder);
  fprintf(stderr, "CdoDefault::TableID   = %d\n", CdoDefault::TableID);
  fprintf(stderr, "\n");

  const char *envstr;
  envstr = getenv("HOSTTYPE");
  if (envstr) fprintf(stderr, "HOSTTYPE            = %s\n", envstr);
  envstr = getenv("VENDOR");
  if (envstr) fprintf(stderr, "VENDOR              = %s\n", envstr);
  envstr = getenv("OSTYPE");
  if (envstr) fprintf(stderr, "OSTYPE              = %s\n", envstr);
  envstr = getenv("MACHTYPE");
  if (envstr) fprintf(stderr, "MACHTYPE            = %s\n", envstr);
  fprintf(stderr, "\n");

#if defined(_ARCH_PWR6)
  fprintf(stderr, "Predefined: _ARCH_PWR6\n");
#elif defined(_ARCH_PWR7)
  fprintf(stderr, "Predefined: _ARCH_PWR7\n");
#endif

#if defined(__AVX2__)
  fprintf(stderr, "Predefined: __AVX2__\n");
#elif defined(__AVX__)
  fprintf(stderr, "Predefined: __AVX__\n");
#elif defined(__SSE4_2__)
  fprintf(stderr, "Predefined: __SSE4_2__\n");
#elif defined(__SSE4_1__)
  fprintf(stderr, "Predefined: __SSE4_1__\n");
#elif defined(__SSE3__)
  fprintf(stderr, "Predefined: __SSE3__\n");
#elif defined(__SSE2__)
  fprintf(stderr, "Predefined: __SSE2__\n");
#endif
  fprintf(stderr, "\n");

  fprintf(stderr, "sizeof(size_t)      = %zu\n", sizeof(size_t));
  {
    const std::vector<int> numElements = {1, 3, 5, 9, 17, 33, 69, 121, 251, 510, 1025, 1024*1024};
    fprintf(stderr, "alignof malloc data = %d\n", alignof_malloc_data(numElements));
    fprintf(stderr, "alignof malloc big  = %d\n", alignof_malloc_data({8*1024*1024, 16*1024*1024, 32*1024*1024}));
    fprintf(stderr, "alignof vector data = %d\n", alignof_vector_data(numElements));
    fprintf(stderr, "alignof vector big  = %d\n", alignof_vector_data({8*1024*1024, 16*1024*1024, 32*1024*1024}));
  }
  fprintf(stderr, "\n");

#ifdef HAVE_MMAP
  fprintf(stderr, "HAVE_MMAP\n");
#endif
#ifdef HAVE_MEMORY_H
  fprintf(stderr, "HAVE_MEMORY_H\n");
#endif
  fprintf(stderr, "\n");

#ifdef _OPENACC
  fprintf(stderr, "OPENACC VERSION     = %d\n", _OPENACC);
#endif
  // OPENMP3:   201107
  // OPENMP4:   201307 gcc 4.9
  // OPENMP45:  201511
#ifdef _OPENMP
  fprintf(stderr, "OPENMP VERSION      = %d\n", _OPENMP);
#endif
  fprintf(stderr, "__cplusplus         = %ld\n", (long) __cplusplus);
#ifdef __GNUC__
  fprintf(stderr, "GNUC VERSION        = %d\n", __GNUC__);
#endif
#ifdef __GNUC_MINOR__
  fprintf(stderr, "GNUC MINOR          = %d\n", __GNUC_MINOR__);
#endif
#ifdef __ICC
  fprintf(stderr, "ICC VERSION         = %d\n", __ICC);
#endif
#ifdef __STDC__
  fprintf(stderr, "STD ANSI C          = %d\n", __STDC__);
#endif
#ifdef __STD_VERSION__
  fprintf(stderr, "STD VERSION         = %ld\n", __STD_VERSION__);
#endif
#ifdef __STDC_VERSION__
  fprintf(stderr, "STDC VERSION        = %ld\n", __STDC_VERSION__);
#endif
#ifdef __STD_HOSTED__
  fprintf(stderr, "STD HOSTED          = %d\n", __STD_HOSTED__);
#endif
#ifdef FLT_EVAL_METHOD
  fprintf(stderr, "FLT_EVAL_METHOD     = %d\n", FLT_EVAL_METHOD);
#endif
#ifdef FP_FAST_FMA
  fprintf(stderr, "FP_FAST_FMA         = defined\n");
#endif
#ifdef __FAST_MATH__
  fprintf(stderr, "__FAST_MATH__       = defined\n");
#endif
  fprintf(stderr, "\n");

#ifdef _SC_VERSION
  fprintf(stderr, "POSIX.1 VERSION     = %ld\n", sysconf(_SC_VERSION));
#endif
#ifdef _SC_ARG_MAX
  fprintf(stderr, "POSIX.1 ARG_MAX     = %ld\n", sysconf(_SC_ARG_MAX));
#endif
#ifdef _SC_CHILD_MAX
  fprintf(stderr, "POSIX.1 CHILD_MAX   = %ld\n", sysconf(_SC_CHILD_MAX));
#endif
#ifdef _SC_STREAM_MAX
  fprintf(stderr, "POSIX.1 STREAM_MAX  = %ld\n", sysconf(_SC_STREAM_MAX));
#endif
#ifdef _SC_OPEN_MAX
  fprintf(stderr, "POSIX.1 OPEN_MAX    = %ld\n", sysconf(_SC_OPEN_MAX));
#endif
#ifdef _SC_PAGESIZE
  fprintf(stderr, "POSIX.1 PAGESIZE    = %ld\n", sysconf(_SC_PAGESIZE));
#endif

  fprintf(stderr, "\n");

  cdo::print_rlimits();

  fprintf(stderr, "\n");
}

static void
cdo_set_options()
{
  if (Debug)
    {
      fprintf(stderr, "CMOR_Mode           = %d\n", Options::CMOR_Mode);
      fprintf(stderr, "CDO_netcdf_hdr_pad  = %d\n", CDO_netcdf_hdr_pad);
      fprintf(stderr, "\n");
    }

  if (Options::CMOR_Mode) cdiDefGlobal("CMOR_MODE", Options::CMOR_Mode);
  if (Options::CDO_Reduce_Dim) cdiDefGlobal("REDUCE_DIM", Options::CDO_Reduce_Dim);
  if (CDO_netcdf_hdr_pad > 0) cdiDefGlobal("NETCDF_HDR_PAD", CDO_netcdf_hdr_pad);
}

static long
cstrToNumBytes(const char *intstring)
{
  long intval = -1;

  if (intstring)
    {
      long fact = 1;
      const auto len = (int) strlen(intstring);
      for (int loop = 0; loop < len; loop++)
        {
          if (!isdigit((int) intstring[loop]))
            {
              switch (tolower((int) intstring[loop]))
                {
                case 'k': fact = 1024; break;
                case 'm': fact = 1048576; break;
                case 'g': fact = 1073741824; break;
                default: fact = 0; break;
                }
              break;
            }
        }

      if (fact) intval = fact * atol(intstring);
    }

  return intval;
}

void
evaluate_color_options(const std::string &arg)
{
  // clang-format off
  if      ("all"  == arg) mpmo_color_set(All);
  else if ("auto" == arg) mpmo_color_set(Auto);
  else if ("no"   == arg) mpmo_color_set(No);
  else cdo_abort("Color option <%s> unknown. Known options: auto, all, no", Yellow(arg.c_str()));
  // clang-format on
}

int
evaluate_except_options(const std::string &arg)
{
  int except = -1;
  // clang-format off
  if      (arg == "DIVBYZERO")  except = FE_DIVBYZERO;
  else if (arg == "INEXACT")    except = FE_INEXACT;
  else if (arg == "INVALID")    except = FE_INVALID;
  else if (arg == "OVERFLOW")   except = FE_OVERFLOW;
  else if (arg == "UNDERFLOW")  except = FE_UNDERFLOW;
  else if (arg == "ALL_EXCEPT") except = FE_ALL_EXCEPT;
  // clang-format on
  return except;
}

enum class ParserResult
{
  FAIL_EXIT,
  OK,
  OK_EXIT
};

static ParserResult
parse_options_long(int argc, char *argv[])
{
  ModListOptions modListOpt;
  int loperators_no = 0;
  int lprintOperators = 0;
  int attribRequest = 0;
  int lprintVariableInputDesc = 0;
  int lpedantic = 0;
  // These variables must be initialized with each while loop run!
  int lasync_worker;
  int lcellsearchmethod;
  int lcolor;
  int lconfig;
  int ldebLevel;
  int leccodes;
  int lenableexcept;
  int lgridsearchradius;
  int lignore_time_bounds;
  int lnetcdf_hdr_pad;
  int lno_remap_weights;
  int lpercentile;
  int lpointsearchmethod;
  int lprecision;
  int lremap_weights;
  int lscmode;
  int lseed;
  int lsortname;
  int lsortparam;
  int ltimestat_date;
  int luse_fftw;
  int luse_time_bounds;

  // clang-format off
  const struct cdo_option opt_long[] =
    {
      { "worker"                     , required_argument , &lasync_worker               , 1             },
      { "precision"                  , required_argument , &lprecision                  , 1             },
      { "percentile"                 , required_argument , &lpercentile                 , 1             },
      { "netcdf_hdr_pad"             , required_argument , &lnetcdf_hdr_pad             , 1             },
      { "header_pad"                 , required_argument , &lnetcdf_hdr_pad             , 1             },
      { "hdr_pad"                    , required_argument , &lnetcdf_hdr_pad             , 1             },
      { "use_fftw"                   , required_argument , &luse_fftw                   , 1             },
      { "cellsearchmethod"           , required_argument , &lcellsearchmethod           , 1             },
      { "config"                     , required_argument , &lconfig                     , 1             },
      { "pointsearchmethod"          , required_argument , &lpointsearchmethod          , 1             },
      { "gridsearchradius"           , required_argument , &lgridsearchradius           , 1             },
      { "remap_weights"              , required_argument , &lremap_weights              , 1             },
      { "enableexcept"               , required_argument , &lenableexcept               , 1             },
      { "timestat_date"              , required_argument , &ltimestat_date              , 1             },
      { "no_remap_weights"           , no_argument       , &lno_remap_weights           , 1             },
      { "ignore_time_bounds"         , no_argument       , &lignore_time_bounds         , 1             },
      { "use_time_bounds"            , no_argument       , &luse_time_bounds            , 1             },
      { "cmor"                       , no_argument       , &Options::CMOR_Mode          , 1             },
      { "reduce_dim"                 , no_argument       , &Options::CDO_Reduce_Dim     , 1             },
      { "float"                      , no_argument       , (int*)&Options::CDO_Memtype  , (int)MemType::Float },
      { "single"                     , no_argument       , (int*)&Options::CDO_Memtype  , (int)MemType::Float },
      { "double"                     , no_argument       , (int*)&Options::CDO_Memtype  , (int)MemType::Double },
      { "rusage"                     , no_argument       , &CDO_Rusage                  , 1             },
      { "attribs"                    , required_argument       , &attribRequest               , 1             },
      { "operators"                  , no_argument       , &lprintOperators             , 1             },
      { "operators_no_output"        , no_argument       , &loperators_no               , 1             },
      { "pedantic"                   , no_argument       , &lpedantic                   , 1             },
      { "no_warnings"                , no_argument       , nullptr                      , 'w'           },
      { "color"                      , required_argument , &lcolor                      , 'C'           },
      { "eccodes"                    , no_argument       , &leccodes                    , 1             },
      { "format"                     , required_argument , nullptr                      , 'f'           },
      { "help"                       , no_argument       , nullptr                      , 'h'           },
      { "history"                    , no_argument       , &Options::CDO_Append_History , 0             },
      { "no_history"                 , no_argument       , &Options::CDO_Append_History , 0             },
      { "regular"                    , no_argument       , nullptr                      , 'R'           },
      { "seed"                       , required_argument , &lseed                       , 1             },
      { "silent"                     , no_argument       , nullptr                      , 's'           },
      { "sort"                       , no_argument       , nullptr                      , 'Q'           },
      { "sortname"                   , no_argument       , &lsortname                   , 1             },
      { "sortparam"                  , no_argument       , &lsortparam                  , 1             },
      { "argumentGroups"             , no_argument       , &lprintVariableInputDesc     , 1             },
      { "variableInput"              , no_argument       , &lprintVariableInputDesc     , 1             },
      { "table"                      , required_argument , nullptr                      , 't'           },
      { "verbose"                    , no_argument       , nullptr                      , 'v'           },
      { "version"                    , no_argument       , nullptr                      , 'V'           },
      { "Dkext"                      , required_argument , &ldebLevel                   , 1             },
      { "outputGribDataScanningMode" , required_argument , &lscmode                     , 1             },
      { "seperateDebugFromLog"       , required_argument , nullptr                      , 2             },
      { nullptr                      , 0                 , nullptr                      , 0             }
    } ;
  // clang-format on

  CDO_opterr = 1;

  while (1)
    {
      // IMPORTANT: BY EVERY OPTION that takes arguments you MUST set its trigger variable to ZERO;
      // otherwise the parameters of other options get wrongly assigned.
      attribRequest = 0;
      lasync_worker = 0;
      lcellsearchmethod = 0;
      lcolor = 0;
      lconfig = 0;
      ldebLevel = 0;
      leccodes = 0;
      lenableexcept = 0;
      lgridsearchradius = 0;
      lnetcdf_hdr_pad = 0;
      lno_remap_weights = 0;
      lpercentile = 0;
      lpointsearchmethod = 0;
      lprecision = 0;
      lremap_weights = 0;
      lscmode = 0;
      lseed = 0;
      lsortname = 0;
      lsortparam = 0;
      ltimestat_date = 0;
      luse_fftw = 0;
      lignore_time_bounds = 0;
      luse_time_bounds = 0;

      int c = cdo_getopt_long(argc, argv, "f:b:e:P:g:i:k:l:m:n:t:D:z:aC:AcdhLMOpQRrsSTuVvWwXZ", opt_long, nullptr);
      if (c == -1) break;

      switch (c)
        {
        case '?':
          // cdo_usage();
          // fprintf(stderr, "Illegal option!\n");
          return ParserResult::FAIL_EXIT;
        // break;
        case ':':
          // cdo_usage();
          // fprintf(stderr, "Option requires an argument!\n");
          return ParserResult::FAIL_EXIT;
        // break;
        case 0:
          if (loperators_no)
            {
              std::string request = "noOutput";
              if (!modListOpt.parse_request(request))
                {
                  exit(EXIT_FAILURE);
                }
            }
          else if (attribRequest || lprintOperators)
            {
              std::string request = CDO_optarg ? CDO_optarg : "";
              if (!modListOpt.parse_request(request))
                {
                  exit(EXIT_FAILURE);
                }
            }
          else if (lnetcdf_hdr_pad)
            {
              const int netcdf_hdr_pad = cstrToNumBytes(CDO_optarg);
              if (netcdf_hdr_pad >= 0) CDO_netcdf_hdr_pad = netcdf_hdr_pad;
            }
          else if (lprecision)
            {
              cdoSetDigits(CDO_optarg);
            }
          else if (lpercentile)
            {
              percentile_set_method(CDO_optarg);
            }
          else if (lasync_worker)
            {
              Options::numStreamWorker = parameter_to_int(CDO_optarg);
            }
          else if (leccodes)
            {
              cdiDefGlobal("ECCODES_GRIB1", true);
            }
          else if (lenableexcept)
            {
              const auto except = evaluate_except_options(CDO_optarg);
              if (except < 0) cdo_abort("option --%s: unsupported argument: %s", "enableexcept", CDO_optarg);
              cdo_feenableexcept(except);
              if (signal(SIGFPE, cdoSignalHandler) == SIG_ERR) cdo_warning("can't catch SIGFPE!");
            }
          else if (ltimestat_date)
            {
              set_timestat_date(CDO_optarg);
            }
          else if (lignore_time_bounds)
            {
              extern bool CDO_Ignore_Time_Bounds;
              CDO_Ignore_Time_Bounds = true;
            }
          else if (luse_time_bounds)
            {
              extern bool CDO_Use_Time_Bounds;
              CDO_Use_Time_Bounds = true;
            }
          else if (luse_fftw)
            {
              const int intarg = parameter_to_bool(CDO_optarg);
              Options::Use_FFTW = intarg;
            }
          else if (lcellsearchmethod)
            {
              set_cell_search_method(CDO_optarg);
            }
          else if (lconfig)
            {
              exit(cdo_print_config(CDO_optarg));
            }
          else if (lpointsearchmethod)
            {
              set_point_search_method(CDO_optarg);
            }
          else if (lgridsearchradius)
            {
              extern double pointSearchRadius;
              const auto fval = radius_str_to_deg(CDO_optarg);
              if (fval < 0 || fval > 180) cdo_abort("%s=%g out of bounds (0-180 deg)!", "gridsearchradius", fval);
              pointSearchRadius = fval;
            }
          else if (lno_remap_weights)
            {
              Options::REMAP_genweights = 0;
            }
          else if (lremap_weights)
            {
              const auto intarg = parameter_to_int(CDO_optarg);
              if (intarg != 0 && intarg != 1) cdo_abort("Unsupported value for option --remap_weights %d [0/1]", intarg);
              Options::REMAP_genweights = intarg;
            }
          else if (lseed)
            {
              const int intarg = parameter_to_int(CDO_optarg);
              if (intarg < 0) cdo_abort("Unsupported value for option --seed %d [>=0]", intarg);
              Options::Random_Seed = intarg;
            }
          else if (lsortname)
            {
              cdiDefGlobal("SORTNAME", true);
            }
          else if (lsortparam)
            {
              cdiDefGlobal("SORTPARAM", true);
            }
          else if (lcolor)
            {
              evaluate_color_options(CDO_optarg);
            }
#ifdef HIRLAM_EXTENSIONS
          else if (ldebLevel)
            {
              const auto extDebugVal = parameter_to_int(CDO_optarg);
              if (extDebugVal > 0)
                {
                  extern int cdiDebugExt;
                  cdoDebugExt = extDebugVal;
                  cdiDebugExt = extDebugVal;
                }
            }
          else if (lscmode)
            {
              const auto scanningModeValue = parameter_to_int(CDO_optarg);
              if (cdoDebugExt) printf("scanningModeValue=%d\n", scanningModeValue);

              if ((scanningModeValue == 0) || (scanningModeValue == 64) || (scanningModeValue == 96))
                {
                  streamGrbDefDataScanningMode(scanningModeValue);  // -1: not used; allowed modes: <0,
                                                                    // 64, 96>; Default is 64
                }
              else
                {
                  cdo_abort("Warning: %d not in allowed modes: <0, 64, 96>; Using default: 64\n", scanningModeValue);
                  streamGrbDefDataScanningMode(64);
                }
            }
#endif
          break;
        case 'A': applyDryRun = true; break;
        case 'a': CdoDefault::TaxisType = TAXIS_ABSOLUTE; break;
        case 'b': setDefaultDataType(CDO_optarg); break;
        case 'C': evaluate_color_options(CDO_optarg); break;
        case 'c': Options::CheckDatarange = true; break;
        case 'd':
          Debug = 1;
          DebugLevel = 1;
          break;
        case 'D':
          Debug = 1;
          {
            char *token = std::strtok((char *) CDO_optarg, ",");
            while (token != NULL)
              {
                const auto res = std::atoi(token);
                if (res != 0) DebugLevel = DebugLevel | (int) std::pow(2, res) / 2;
                token = std::strtok(NULL, ",");
              }
          }
          break;
        case 'f': setDefaultFileType(CDO_optarg); break;
        case 'g': cdo_set_grids(CDO_optarg); break;
        case 'h': Help = 1; break;
        case 'i': define_institution(CDO_optarg); break;
        case 'k': defineChunktype(CDO_optarg); break;
        case 'L': Threading::cdoLockIO = true; break;
        case 'l': define_zaxis(CDO_optarg); break;
        case 'm': cdiDefMissval(atof(CDO_optarg)); break;
        case 'M': cdiDefGlobal("HAVE_MISSVAL", true); break;
        case 'n': Options::cdoVarnames = defineVarnames(CDO_optarg); break;
        case 'O': Options::cdoOverwriteMode = true; break;
        case 'P': CDO_numThreads = parameter_to_int(CDO_optarg); break;
        case 'p':
          Options::CDO_Parallel_Read = true;
          Options::CDO_task = true;
          break;
        case 'Q': cdiDefGlobal("SORTNAME", true); break;
        case 'R':
          Options::cdoRegulargrid = true;
          cdiDefGlobal("REGULARGRID", true);
          break;
        case 'r': CdoDefault::TaxisType = TAXIS_RELATIVE; break;
        case 'S': Options::cdoDiag = true; break;
        case 's':
          Options::silentMode = true;
          MpMO::enable_silent_mode(Options::silentMode);
          progress::silentMode = true;
          break;
        case 'T': Options::Timer = true; break;
        case 't': CdoDefault::TableID = define_table(CDO_optarg); break;
        case 'u': Options::cdoInteractive = true; break;
        case 'V': Version = 1; break;
        case 'v':
          Options::cdoVerbose = true;
          MpMO::enable_verbose(true);
          gridEnableVerbose(Options::cdoVerbose);
          break;
        case 'W':  // obsolete since 1.9.9
        case 'w':  // disable warning messages
          MpMO::enable_warnings(false);
          extern int _Verbose;  // CDI Warnings
          _Verbose = 0;
          break;
        case 'X': Options::cdoParIO = true; break;  // multi threaded I/O
        case 'Z': Options::cdoCompress = true; break;
        case 'z': defineCompress(CDO_optarg); break;
        case 2: break;
        }
    }

  if (lpedantic)
    {
      MpMO::enable_pedantic(true);
    }

  if (modListOpt.mod_info_requested())
    {
      operator_print_list(modListOpt);
      return ParserResult::OK_EXIT;
    }

  if (lprintVariableInputDesc)
    {
      cdo_variableInputs();
      return ParserResult::OK_EXIT;
    }

  return ParserResult::OK;
}

static void
cdo_rusage(void)
{
#if defined(HAVE_SYS_RESOURCE_H) && defined(RUSAGE_SELF)
  struct rusage ru;
  const auto status = getrusage(RUSAGE_SELF, &ru);
  if (status == 0)
    {
      const double ut = ru.ru_utime.tv_sec + 0.000001 * ru.ru_utime.tv_usec;
      const double st = ru.ru_stime.tv_sec + 0.000001 * ru.ru_stime.tv_usec;

      fprintf(stderr, "  User time:     %.3f seconds\n", ut);
      fprintf(stderr, "  System time:   %.3f seconds\n", st);
      fprintf(stderr, "  Total time:    %.3f seconds\n", ut + st);
      fprintf(stderr, "  Memory usage:  %.2f MBytes\n", ru.ru_maxrss / (1024. * 1024.));
      fprintf(stderr, "  Page reclaims: %5ld page%s\n", ru.ru_minflt, ADD_PLURAL(ru.ru_minflt));
      fprintf(stderr, "  Page faults:   %5ld page%s\n", ru.ru_majflt, ADD_PLURAL(ru.ru_majflt));
      fprintf(stderr, "  Swaps:         %5ld\n", ru.ru_nswap);
      fprintf(stderr, "  Disk read:     %5ld block%s\n", ru.ru_inblock, ADD_PLURAL(ru.ru_inblock));
      fprintf(stderr, "  Disk Write:    %5ld block%s\n", ru.ru_oublock, ADD_PLURAL(ru.ru_oublock));
    }
#endif
}
// clang-format on

#ifdef _OPENMP
static void
print_openmp_info()
{
  fprintf(stderr, "OMP num procs       = %d\n", omp_get_num_procs());
  fprintf(stderr, "OMP max threads     = %d\n", omp_get_max_threads());
  fprintf(stderr, "OMP num threads     = %d\n", omp_get_num_threads());
#ifndef HAVE_OPENMP3
  fprintf(stderr, "OMP thread limit    = %d\n", omp_get_thread_limit());
  omp_sched_t kind;
  int modifer;
  omp_get_schedule(&kind, &modifer);
  fprintf(stderr, "OMP schedule        = %d (1:static; 2:dynamic; 3:guided; 4:auto)\n", (int) kind);
#endif
#ifdef HAVE_OPENMP4
  fprintf(stderr, "OMP proc bind       = %d (0:false; 1:true; 2:master; 3:close; 4:spread)\n", (int) omp_get_proc_bind());
#ifndef __ICC
  fprintf(stderr, "OMP num devices     = %d\n", omp_get_num_devices());
#endif
#endif
}
#endif

static void
set_external_proj_func(void)
{
#ifdef HAVE_CDI_PROJ_FUNCS
  proj_lonlat_to_lcc_func = proj_lonlat_to_lcc;
  proj_lcc_to_lonlat_func = proj_lcc_to_lonlat;

  proj_lonlat_to_stere_func = proj_lonlat_to_stere;
  proj_stere_to_lonlat_func = proj_stere_to_lonlat;
#endif
}

static const char *
get_progname(char *string)
{
#ifdef _WIN32
  //  progname = strrchr(string, '\\');
  char *progname = " cdo";
#else
  char *progname = strrchr(string, '/');
#endif

  if (progname == nullptr)
    progname = string;
  else
    progname++;

  return progname;
}

#ifdef HAVE_NC4HDF5
extern "C" void H5dont_atexit(void);
#endif

int
main(int argc, char *argv[])
{
  int status = 0;
  bool lstop = false;

  cdo::set_exit_function(cdo_exit);
  cdo::set_context_function(process_inq_prompt);
  progress::set_context_function(process_inq_prompt);

  mpmo_color_set(Auto);

  cdo_init_is_tty();

  memExitOnError();

  Options::CDO_Reduce_Dim = 0;

  // mallopt(M_MMAP_MAX, 0);

  set_command_line(argc, argv);

  cdo::progname = get_progname(argv[0]);

  get_env_vars();

  const auto parserStatus = parse_options_long(argc, argv);
  if (parserStatus == ParserResult::FAIL_EXIT) return 1;
  if (parserStatus == ParserResult::OK_EXIT) return 0;

  cdo_set_options();
  if (Debug || Version) cdo_version();

  if (Debug)
    {
      if (DebugLevel == 0)
        {
          std::cout << "No debug level given please choose: " << std::endl;
          print_debug_options();
          exit(EXIT_SUCCESS);
        }
      if (DebugLevel > 1) cdiDebug(DebugLevel);
      cdo::set_debug(DebugLevel);
    }

  if (Debug)
    {
      fprintf(stderr, "stdin_is_tty:   %d\n", stdin_is_tty);
      fprintf(stderr, "stdout_is_tty:  %d\n", stdout_is_tty);
      fprintf(stderr, "stderr_is_tty:  %d\n", stderr_is_tty);
      print_system_info();
    }

  cdo::set_stacksize(67108864);  // 64MB

  if (Debug) print_pthread_info();

#ifdef _OPENMP
  if (CDO_numThreads <= 0) CDO_numThreads = 1;
  omp_set_num_threads(CDO_numThreads);

  Threading::ompNumThreads = omp_get_max_threads();
  if (omp_get_max_threads() > omp_get_num_procs())
    fprintf(stderr, "Warning: Number of OMP threads=%d is greater than number of Cores=%d!\n", omp_get_max_threads(),
            omp_get_num_procs());

  if (Threading::ompNumThreads < CDO_numThreads)
    fprintf(stderr, "Warning: omp_get_max_threads() returns %d!\n", Threading::ompNumThreads);

  if (Debug) print_openmp_info();

  if (Options::cdoVerbose)
    {
      fprintf(stderr, " OpenMP:  num_procs=%d  max_threads=%d", omp_get_num_procs(), omp_get_max_threads());
#ifdef HAVE_OPENMP4
#ifndef __ICC
      fprintf(stderr, "  num_devices=%d", omp_get_num_devices());
#endif
#endif
      fprintf(stderr, "\n");
    }
#else
  if (CDO_numThreads > 1) fprintf(stderr, "Warning: Option -P failed, OpenMP support not compiled in!\n");
#endif

  // temprorary end
  if (CDO_optind >= argc)
    {
      if (!Version && !Help)
        {
          fprintf(stderr, "\nNo operator given!\n\n");
          cdo_usage();
          status = 1;
        }

      if (Help) cdo_usage();
      lstop = true;
    }

  if (lstop) return status;

  std::vector<std::string> new_argv(&argv[CDO_optind], argv + argc);

  new_argv = expand_wild_cards(new_argv);
  ApplyStatus expandSuccess;
  new_argv = expand_apply(new_argv, expandSuccess);
  if (expandSuccess != ApplyStatus::OK) return -1;
  if (applyDryRun == true || expandSuccess != ApplyStatus::OK)
    {
      std::cerr << argv_to_string(new_argv) << std::endl;
      exit(applyDryRun ? 0 : -1);
    }

  ///*TEMP*/ // should not be needed when std::string is standard string
  std::vector<char *> new_cargv(new_argv.size());
  for (unsigned long i = 0; i < new_argv.size(); i++)
    {
      new_cargv[i] = strdup(new_argv[i].c_str());
    }

  if (CdoDefault::TableID != CDI_UNDEFID) cdo_def_table_id(CdoDefault::TableID);

  set_external_proj_func();

  if (Help)
    {
      cdo_print_help(operator_help(argv[CDO_optind]));
    }
  else
    {
      timer_total = timer_new("total");
      timer_read = timer_new("read");
      timer_write = timer_new("write");

#ifdef HAVE_NC4HDF5
      H5dont_atexit();
#endif
#ifdef CUSTOM_MODULES
      load_custom_modules("custom_modules");
      close_library_handles();
#endif

      g_processManager.create_processes(new_argv.size(), (const char **) &new_cargv[0]);
      timer_start(timer_total);
      g_processManager.run_processes();
      timer_stop(timer_total);
      g_processManager.clear_processes();

      if (Options::Timer) timer_report();
    }

  if (CDO_Rusage) cdo_rusage();

  if (!status) status = Options::cdoExitStatus;

  return status;
}
