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

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

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

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

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

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

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

#include <fenv.h>
#include <sys/stat.h>
#ifdef HAVE_GETRLIMIT
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/time.h>     /* getrlimit */
#include <sys/resource.h> /* getrlimit */
#endif
#endif
#include <unistd.h> /* sysconf, gethostname */
#include <csignal>
#include "timer.h"

#if defined(SX)
#define RLIM_T long long
#else
#define RLIM_T rlim_t
#endif

#include <cdi.h>
#include <mpim_grid.h>
#include <griddes.h>

#include "cdo_default_values.h"
#include "cdo_cdi_wrapper.h"
#include "param_conversion.h"
#include "cdo_task.h"
#include "progress.h"

#include "cdo_getopt.h"

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

#include "module_list.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"

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

#ifndef VERSION
#define VERSION "0.0.1"
#endif

#define MAX_NUM_VARNAMES 256

#include <cstring>

static ProcessManager g_processManager;
void
cdoExit()
{
  g_processManager.killProcesses();
  exit(EXIT_FAILURE);
}

static int Debug = 0;
static int Version = 0;
static int Help = 0;
static int DebugLevel = 0;
static int numThreads = 0;
static int timer_total;
static int CDO_netcdf_hdr_pad = 0;
static int CDO_Rusage = 0;

extern "C" void streamGrbDefDataScanningMode(int scanmode);

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

#define PRINT_RLIMIT(resource)                                                           \
  {                                                                                      \
    struct rlimit rlim;                                                                  \
    const int status = getrlimit(resource, &rlim);                                       \
    if (status == 0)                                                                     \
      {                                                                                  \
        if (sizeof(RLIM_T) > sizeof(long))                                               \
          {                                                                              \
            fprintf(stderr, "CUR %-15s = %llu\n", #resource, (long long) rlim.rlim_cur); \
            fprintf(stderr, "MAX %-15s = %llu\n", #resource, (long long) rlim.rlim_max); \
          }                                                                              \
        else                                                                             \
          {                                                                              \
            fprintf(stderr, "CUR %-15s = %lu\n", #resource, (long) rlim.rlim_cur);       \
            fprintf(stderr, "MAX %-15s = %lu\n", #resource, (long) rlim.rlim_max);       \
          }                                                                              \
      }                                                                                  \
  }

#define ITSME (cstrIsEqual(cdo::Username, "\x6d\x32\x31\x34\x30\x30\x33"))

static void
cdo_stackframe()
{
#if defined HAVE_EXECINFO_H && defined HAVE_BACKTRACE
  void *callstack[32];
  const int 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
}

static int
cdo_feenableexcept(const int excepts)
{
#if defined HAVE_FEENABLEEXCEPT
  int feenableexcept(int);
  int old_excepts = feenableexcept(excepts);
  return old_excepts;
#else
  static fenv_t fenv;
  const unsigned new_excepts = ((unsigned) excepts) & FE_ALL_EXCEPT;
  int old_excepts = -1;  // previous masks

  if (fegetenv(&fenv)) return -1;
#if defined(HAVE_FENV_T___CONTROL) && defined(HAVE_FENV_T___MXCSR)
  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();
      cdoAbort("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)
    cdoAbort("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)
        cdoAbort("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

  printFeatures();
  printLibraries();

  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, "%s\n", CDO_version);*/
  /*  fprintf(stderr, "\n");*/
  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, "    -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, "    --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 perators\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, "    -i <inst>      Institution name/file\n");
  fprintf(stderr, "                   Predefined instituts: ");
  for ( int id = 0; id < institutInqNumber; id++ )
    if ( (name = institutInqNamePtr(id)) )
      fprintf(stderr, " %s", name);
  fprintf(stderr, "\n");
  */
  /* fprintf(stderr, "    -l <level>     Level file\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, 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, "    --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-2020 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
cdoPrintHelp(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 bool lprint = !(help[i][0] == '\0' && help[i + 1][0] == ' ');
          if (lprint)
            {
              if (colorEnabled())
                {
                  if (cstrIsEqual(help[i], "NAME") || cstrIsEqual(help[i], "SYNOPSIS") || cstrIsEqual(help[i], "DESCRIPTION")
                      || cstrIsEqual(help[i], "OPERATORS") || cstrIsEqual(help[i], "NAMELIST") || cstrIsEqual(help[i], "PARAMETER")
                      || cstrIsEqual(help[i], "ENVIRONMENT") || cstrIsEqual(help[i], "NOTE") || cstrIsEqual(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 int 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))
    {
      int 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
            {
              fprintf(stderr, "Unsupported number of bits %d!\n", nbits);
              fprintf(stderr, "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.\n");
              exit(EXIT_FAILURE);
            }
        }
      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 cdoAbort("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 cdoAbort("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 cdoAbort("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 cdoAbort("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
        {
          cdoAbort("Unsupported character in number of bytes: >%s< !", datatypestr);
        }
    }
}
/*
static
void setDefaultDataTypeByte(char *datatypestr)
{
  static union {unsigned long l; unsigned char c[sizeof(long)];} u_byteorder =
{1}; int datatype = -1;

  if ( isdigit((int) *datatypestr) )
    {
      datatype = atoi(datatypestr);
      datatypestr++;

      if      ( datatype == 1 ) CdoDefault::DataType = CDI_DATATYPE_PACK8;
      else if ( datatype == 2 ) CdoDefault::DataType = CDI_DATATYPE_PACK16;
      else if ( datatype == 3 ) CdoDefault::DataType = CDI_DATATYPE_PACK24;
      else if ( datatype == 4 ) CdoDefault::DataType = CDI_DATATYPE_FLT32;
      else if ( datatype == 8 ) CdoDefault::DataType = CDI_DATATYPE_FLT64;
      else
        {
          fprintf(stderr, "Unsupported datatype %d!\n", datatype);
          fprintf(stderr, "Use 4/8 for filetype nc/srv/ext/ieg and 1/2/3 for
grb1/grb2.\n"); exit(EXIT_FAILURE);
        }
    }

  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
        {
          cdoAbort("Unsupported character in number of bytes: %s!", datatypestr);
        }
    }
}
*/
static void
setDefaultFileType(const char *filetypestr)
{
  if (filetypestr)
    {
      size_t len = 0;

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

      const char *ftstr = filetypestr + len;

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

#define NTESTS 11
#include <inttypes.h>
static int
getMemAlignment()
{
  int ma = -1;
  double *ptr[NTESTS];
  int64_t iptr;
  const size_t tsize[NTESTS] = { 1, 3, 5, 9, 17, 33, 69, 121, 251, 510, 1025 };
  const size_t ma_check[4] = { 8, 16, 32, 64 };
  int ma_result[4] = { 1, 1, 1, 1 };

  for (int i = 0; i < NTESTS; ++i)
    {
      ptr[i] = (double *) malloc(tsize[i]);
      iptr = (int64_t) ptr[i];
      for (int k = 0; k < 4; ++k)
        if (iptr % ma_check[k]) ma_result[k] = 0;
    }
  for (auto &p : ptr) free(p);

  for (int i = NTESTS - 1; i >= 0; i--)
    {
      ptr[i] = (double *) malloc(tsize[i] + 5);
      iptr = (int64_t) ptr[i];
      for (int k = 0; k < 4; ++k)
        if (iptr % ma_check[k]) ma_result[k] = 0;
    }
  for (auto &p : ptr) free(p);

  for (int k = 0; k < 4; ++k)
    if (ma_result[k]) ma = ma_check[k];

  return ma;
}

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
    {
      cdoAbort("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 cdoAbort("Chunk type '%s' unsupported!", arg.c_str());
  // clang-format on
}

static void
defineVarnames(const char *const arg)
{
  size_t len = strlen(arg);
  size_t istart = 0;
  while (istart < len && (arg[istart] == ' ' || arg[istart] == ',')) istart++;

  len -= istart;

  if (len)
    {
      Options::cdoVarnames = (char **) Malloc(MAX_NUM_VARNAMES * sizeof(char *));

      char *pbuf = strdup(arg + istart);
      Options::cdoVarnames[Options::cdoNumVarnames++] = pbuf;

      char *commapos = pbuf;
      while ((commapos = strchr(commapos, ',')) != nullptr)
        {
          *commapos++ = '\0';
          if (strlen(commapos))
            {
              if (Options::cdoNumVarnames >= MAX_NUM_VARNAMES) cdoAbort("Too many variable names (limit=%d)!", MAX_NUM_VARNAMES);

              Options::cdoVarnames[Options::cdoNumVarnames++] = commapos;
            }
        }
      /*
      for ( int i = 0; i < Options::cdoNumVarnames; ++i )
        printf("varname %d: %s\n", i+1, Options::cdoVarnames[i]);
      */
    }
}

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_GRID_SEARCH_DIR");
  if (envstr)
    {
      auto len = strlen(envstr);
      if (len > 0)
        {
          len += 2;
          gridSearchDir = (char *) Malloc(len);
          memcpy(gridSearchDir, envstr, len - 1);
          if (gridSearchDir[len - 3] != '/')
            {
              gridSearchDir[len - 2] = '/';
              gridSearchDir[len - 1] = 0;
            }
        }
    }

  envstr = getenv("CDO_DISABLE_HISTORY");
  if (envstr)
    {
      if (parameter2bool(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 (parameter2bool(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)
    {
      const bool ival = parameter2bool(envstr);
      Options::CDO_Append_History = ival;
      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 (parameter2bool(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 (parameter2bool(envstr) == true)
        {
          Options::cdoDiag = true;
          if (Options::cdoVerbose) fprintf(stderr, "CDO_DIAG = %s\n", envstr);
        }
    }

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

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

static void
print_system_info()
{
  fprintf(stderr, "\n");
  fprintf(stderr, "CDO_Color           = %d\n", mpmoGetColorMode());
  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));
  fprintf(stderr, "alignof(double*)    = %zu\n", alignof(double*));
  fprintf(stderr, "mem alignment       = %d\n\n", getMemAlignment());

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

#if defined(_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", __cplusplus);
#if defined(__GNUC__)
  fprintf(stderr, "GNUC VERSION        = %d\n", __GNUC__);
#endif
#if defined(__GNUC_MINOR__)
  fprintf(stderr, "GNUC MINOR          = %d\n", __GNUC_MINOR__);
#endif
#if defined(__ICC)
  fprintf(stderr, "ICC VERSION         = %d\n", __ICC);
#endif
#if defined(__STDC__)
  fprintf(stderr, "STD ANSI C          = %d\n", __STDC__);
#endif
#if defined(__STD_VERSION__)
  fprintf(stderr, "STD VERSION         = %ld\n", __STD_VERSION__);
#endif
#if defined(__STDC_VERSION__)
  fprintf(stderr, "STDC VERSION        = %ld\n", __STDC_VERSION__);
#endif
#if defined(__STD_HOSTED__)
  fprintf(stderr, "STD HOSTED          = %d\n", __STD_HOSTED__);
#endif
#if defined(FLT_EVAL_METHOD)
  fprintf(stderr, "FLT_EVAL_METHOD     = %d\n", FLT_EVAL_METHOD);
#endif
#if defined(FP_FAST_FMA)
  fprintf(stderr, "FP_FAST_FMA         = defined\n");
#endif
#if defined(__FAST_MATH__)
  fprintf(stderr, "__FAST_MATH__       = defined\n");
#endif
  fprintf(stderr, "\n");

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

  fprintf(stderr, "\n");

#if defined(HAVE_GETRLIMIT)
#if defined(RLIMIT_FSIZE)
  PRINT_RLIMIT(RLIMIT_FSIZE);
#endif
#if defined(RLIMIT_NOFILE)
  PRINT_RLIMIT(RLIMIT_NOFILE);
#endif
#if defined(RLIMIT_STACK)
  PRINT_RLIMIT(RLIMIT_STACK);
#endif
#if defined(RLIMIT_RSS)
  PRINT_RLIMIT(RLIMIT_RSS);
#endif
#endif
  fprintf(stderr, "\n");
}

static void
check_stacksize()
{
#ifdef HAVE_GETRLIMIT
#ifdef RLIMIT_STACK
  {
    struct rlimit lim;
    auto stat = getrlimit(RLIMIT_STACK, &lim);
    if (stat == 0)
      {
#define MIN_STACK_SIZE 67108864L  // 64MB
        RLIM_T min_stack_size = MIN_STACK_SIZE;
        if (min_stack_size > lim.rlim_max) min_stack_size = lim.rlim_max;
        if (lim.rlim_cur < min_stack_size)
          {
            lim.rlim_cur = min_stack_size;

            stat = setrlimit(RLIMIT_STACK, &lim);
            if (Debug)
              {
                if (stat == 0)
                  {
                    fprintf(stderr, "Set stack size to %ld\n", (long) min_stack_size);
                    PRINT_RLIMIT(RLIMIT_STACK);
                  }
                else
                  fprintf(stderr, "Set stack size to %ld failed!\n", (long) min_stack_size);
                fprintf(stderr, "\n");
              }
          }
      }
  }
#endif
#endif
}

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 int 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
evaluateColorOptions(const std::string &arg)
{
  // clang-format off
  if      ("all"  == arg) mpmoColorSet(All);
  else if ("auto" == arg) mpmoColorSet(Auto);
  else if ("no"   == arg) mpmoColorSet(No);
  else cdoAbort("Color option <%s> unknown. Known options: auto, all, no", Yellow(arg.c_str()));
  // clang-format on
}

int
evaluateExpectOptions(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;
}

static int
parseOptionsLong(int argc, char *argv[])
{
  int lprintoperators = 0;
  int lprintoperatorsno = 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 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;
  int verbose;

  // 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             },
      { "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 },
      { "double"                     , no_argument       , (int*)&Options::CDO_Memtype  , (int)MemType::Double },
      { "rusage"                     , no_argument       , &CDO_Rusage                  , 1             },
      { "operators_no_output"        , no_argument       , &lprintoperatorsno           , 1             },
      { "operators"                  , no_argument       , &lprintoperators             , 1             },
      { "pedantic"                   , no_argument       , &lpedantic                   , 1             },
      { "no_warnings"                , no_argument       , &verbose                     , 0             },
      { "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             },
      { "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.
      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;
      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:cdhLMOpQRrsSTuVvWwXZ", opt_long, nullptr);
      if (c == -1) break;

      switch (c)
        {
        case '?':
          // cdo_usage();
          // fprintf(stderr, "Illegal option!\n");
          return -1;
        // break;
        case ':':
          // cdo_usage();
          // fprintf(stderr, "Option requires an argument!\n");
          return -1;
        // break;
        case 0:
          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 = parameter2int(CDO_optarg);
            }
          else if (leccodes)
            {
              cdiDefGlobal("ECCODES_GRIB1", true);
            }
          else if (lenableexcept)
            {
              const auto except = evaluateExpectOptions(CDO_optarg);
              if (except < 0) cdoAbort("option --%s: unsupported argument: %s", "enableexcept", CDO_optarg);
              cdo_feenableexcept(except);
              if (signal(SIGFPE, cdoSignalHandler) == SIG_ERR) cdoWarning("can't catch SIGFPE!");
            }
          else if (ltimestat_date)
            {
              setTimestatDate(CDO_optarg);
            }
          else if (luse_time_bounds)
            {
              extern bool CDO_Use_Time_Bounds;
              CDO_Use_Time_Bounds = true;
            }
          else if (luse_fftw)
            {
              const int intarg = parameter2int(CDO_optarg);
              if (intarg != 0 && intarg != 1) cdoAbort("Unsupported value for option --use_fftw=%d [range: 0-1]", intarg);
              Options::Use_FFTW = intarg;
            }
          else if (lcellsearchmethod)
            {
              setCellSearchMethod(CDO_optarg);
            }
          else if (lconfig)
            {
              cdoConfig(CDO_optarg);
            }
          else if (lpointsearchmethod)
            {
              setPointSearchMethod(CDO_optarg);
            }
          else if (lgridsearchradius)
            {
              extern double pointSearchRadius;
              const double fval = radius_str_to_deg(CDO_optarg);
              if (fval < 0 || fval > 180) cdoAbort("%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 int intarg = parameter2int(CDO_optarg);
              if (intarg != 0 && intarg != 1) cdoAbort("Unsupported value for option --remap_weights %d [0/1]", intarg);
              Options::REMAP_genweights = intarg;
            }
          else if (lseed)
            {
              const int intarg = parameter2int(CDO_optarg);
              if (intarg < 0) cdoAbort("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)
            {
              evaluateColorOptions(CDO_optarg);
            }
#ifdef HIRLAM_EXTENSIONS
          else if (ldebLevel)
            {
              const int extDebugVal = parameter2int(CDO_optarg);
              if (extDebugVal > 0)
                {
                  extern int cdiDebugExt;
                  cdoDebugExt = extDebugVal;
                  cdiDebugExt = extDebugVal;
                }
            }
          else if (lscmode)
            {
              const int scanningModeValue = parameter2int(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
                {
                  cdoAbort("Warning: %d not in allowed modes: <0, 64, 96>; Using default: 64\n", scanningModeValue);
                  streamGrbDefDataScanningMode(64);
                }
            }
#endif
          break;
        case 'a': CdoDefault::TaxisType = TAXIS_ABSOLUTE; break;
        case 'b': setDefaultDataType(CDO_optarg); break;
        case 'C': evaluateColorOptions(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)
              {
                int 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': defineInstitution(CDO_optarg); break;
        case 'k': defineChunktype(CDO_optarg); break;
        case 'L': Threading::cdoLockIO = true; break;
        case 'l': defineZaxis(CDO_optarg); break;
        case 'm': cdiDefMissval(atof(CDO_optarg)); break;
        case 'M': cdiDefGlobal("HAVE_MISSVAL", true); break;
        case 'n': defineVarnames(CDO_optarg); break;
        case 'O': Options::cdoOverwriteMode = true; break;
        case 'P': numThreads = parameter2int(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::enableSilentMode(Options::silentMode);
          progress::silentMode = true;
          break;
        case 'T': Options::Timer = true; break;
        case 't': CdoDefault::TableID = defineTable(CDO_optarg); break;
        case 'u': Options::cdoInteractive = true; break;
        case 'V': Version = 1; break;
        case 'v':
          Options::cdoVerbose = true;
          MpMO::enableVerbose(true);
          gridEnableVerbose(Options::cdoVerbose);
          break;
        case 'W':  // obsolete since 1.9.9
        case 'w':  // disable warning messages
          MpMO::enableWarnings(false);
          extern int _Verbose;  // CDI Warnings
          _Verbose = 0;
          break;
        case 'X': /* multi threaded I/O */ Options::cdoParIO = true; break;
        case 'Z': Options::cdoCompress = true; break;
        case 'z': defineCompress(CDO_optarg); break;
        case 2: break;
        }
    }

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

  if (lprintoperators || lprintoperatorsno)
    {
      set_text_color(stderr, GREEN);
      const bool print_no_output = lprintoperatorsno > 0;
      operatorPrintList(print_no_output);
      // operatorPrintAll();
      reset_text_color(stderr);
      return 1;
    }
  if (lprintVariableInputDesc)
    {
      cdo_variableInputs();
      return 1;
    }

  return 0;
}

static void
cdo_rusage(void)
{
#if defined(HAVE_SYS_RESOURCE_H) && defined(RUSAGE_SELF)
  struct rusage ru;
  const int 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 off */
/**
 * Initializes all hardcoded aliases
 */
static void
init_aliases()
{
  add_alias("afterburner"     , "after");
  add_alias("anomaly"         , "ymonsub");
  add_alias("deltap_fl"       , "deltap");
  add_alias("diffv"           , "diffn");
  add_alias("geopotheight"    , "gheight");
  add_alias("globavg"         , "fldavg");
  add_alias("import_grads"    , "import_binary");
  add_alias("infos"           , "sinfo");
  add_alias("infov"           , "infon");
  add_alias("intgrid"         , "intgridbil");
  add_alias("log"             , "ln");
  add_alias("chvar"           , "chname");
  add_alias("nvar"            , "npar");
  add_alias("outputkey"       , "outputtab");
  add_alias("vardes"          , "codetab");
  add_alias("pardes"          , "codetab");
  add_alias("selvar"          , "selname");
  add_alias("delvar"          , "delname");
  add_alias("selindex"        , "selgridcell");
  add_alias("showvar"         , "showname");
  add_alias("selgridname"     , "selgrid");
  add_alias("setvar"          , "setname");
  add_alias("setpartabv"      , "setpartabn");
  add_alias("setpartab"       , "setcodetab");
  add_alias("sinfov"          , "sinfon");
  add_alias("sortvar"         , "sortname");
  add_alias("splitvar"        , "splitname");
  add_alias("sort"            , "timsort");
  add_alias("eca_r1mm"        , "eca_rr1");
  add_alias("fpressure"       , "pressure_fl");
  add_alias("hpressure"       , "pressure_hl");
  add_alias("outputcenter"    , "gmtxyz");
  add_alias("outputbounds"    , "gmtcells");
  add_alias("selseas"         , "selseason");
  add_alias("selmon"          , "selmonth");
  add_alias("remapycon"       , "remapcon");
  add_alias("genycon"         , "gencon");
  add_alias("for"             , "seq");
}

void init_modules()
{
    modules = {
    {"Adisit"        ,module_Adisit         },
    {"Afterburner"   ,module_Afterburner    },
    {"Arith"         ,module_Arith          },
    {"Arithc"        ,module_Arithc         },
    {"Arithdays"     ,module_Arithdays      },
    {"Arithlat"      ,module_Arithlat       },
    {"Apply"         ,module_Apply          },
    {"Cat"           ,module_Cat            },
    {"CDItest"       ,module_CDItest        },
    {"CDIread"       ,module_CDIread        },
    {"CDIwrite"      ,module_CDIwrite       },
    {"Change"        ,module_Change         },
    {"Change_e5slm"  ,module_Change_e5slm   },
    {"Cloudlayer"    ,module_Cloudlayer     },
    {"CMOR"          ,module_CMOR           },
    {"CMOR_lite"     ,module_CMOR_lite      },
    {"CMOR_table"    ,module_CMOR_table     },
    {"Collgrid"      ,module_Collgrid       },
    {"Command"       ,module_Command        },
    {"Comp"          ,module_Comp           },
    {"Compc"         ,module_Compc          },
    {"Complextorect" ,module_Complextorect  },
    {"Cond"          ,module_Cond           },
    {"Cond2"         ,module_Cond2          },
    {"Condc"         ,module_Condc          },
    {"Consecstat"    ,module_Consecstat     },
    {"Copy"          ,module_Copy           },
    {"Deltat"        ,module_Deltat         },
    {"Deltime"       ,module_Deltime        },
    {"Derivepar"     ,module_Derivepar      },
    {"Detrend"       ,module_Detrend        },
    {"Diff"          ,module_Diff           },
    {"Distgrid"      ,module_Distgrid       },
    {"Duplicate"     ,module_Duplicate      },
    {"Echam5ini"     ,module_Echam5ini      },
    {"Enlarge"       ,module_Enlarge        },
    {"Enlargegrid"   ,module_Enlargegrid    },
    {"Ensstat"       ,module_Ensstat        },
    {"Ensstat3"      ,module_Ensstat3       },
    {"Ensval"        ,module_Ensval         },
    {"Eofcoeff"      ,module_Eofcoeff       },
    {"Eofcoeff3d"    ,module_Eofcoeff3d     },
    {"EOFs"          ,module_EOFs           },
    {"EOF3d"         ,module_EOF3d          },
    {"EstFreq"       ,module_EstFreq        },
    {"Expr"          ,module_Expr           },
    {"FC"            ,module_FC             },
    {"Filedes"       ,module_Filedes        },
    {"Fillmiss"      ,module_Fillmiss       },
    {"Filter"        ,module_Filter         },
    {"Fldrms"        ,module_Fldrms         },
    {"Fldstat"       ,module_Fldstat        },
    {"Fldstatcor"    ,module_Fldstatcor     },
    {"Fldstatvar"    ,module_Fldstatvar     },
    {"Fourier"       ,module_Fourier        },
    {"Gengrid"       ,module_Gengrid        },
    {"Gradsdes"      ,module_Gradsdes       },
    {"Gridboxstat"   ,module_Gridboxstat    },
    {"Gridcell"      ,module_Gridcell       },
    {"Gridsearch"    ,module_Gridsearch     },
    {"Harmonic"      ,module_Harmonic       },
    {"Histogram"     ,module_Histogram      },
    {"Importamsr"    ,module_Importamsr     },
    {"Importbinary"  ,module_Importbinary   },
    {"Importcmsaf"   ,module_Importcmsaf    },
    {"Importobs"     ,module_Importobs      },
    {"Importfv3grid" ,module_Importfv3grid  },
    {"Info"          ,module_Info           },
    {"Input"         ,module_Input          },
    {"Intgrid"       ,module_Intgrid        },
    {"Intgridtraj"   ,module_Intgridtraj    },
    {"Intlevel"      ,module_Intlevel       },
    {"Intlevel3d"    ,module_Intlevel3d     },
    {"Inttime"       ,module_Inttime        },
    {"Intntime"      ,module_Intntime       },
    {"Intyear"       ,module_Intyear        },
    {"Invert"        ,module_Invert         },
    {"Invertlev"     ,module_Invertlev      },
    {"Isosurface"    ,module_Isosurface     },
    {"Lic"           ,module_Lic            },
    {"MapReduce"     ,module_MapReduce      },
    {"Maskbox"       ,module_Maskbox        },
    {"Maskregion"    ,module_Maskregion     },
    {"Mastrfu"       ,module_Mastrfu        },
    {"Math"          ,module_Math           },
    {"Merge"         ,module_Merge          },
    {"Mergetime"     ,module_Mergetime      },
    {"Mergegrid"     ,module_Mergegrid      },
    {"Merstat"       ,module_Merstat        },
    {"Monarith"      ,module_Monarith       },
    {"Mrotuv"        ,module_Mrotuv         },
    {"Mrotuvb"       ,module_Mrotuvb        },
    {"NCL_wind"      ,module_NCL_wind       },
    {"Ninfo"         ,module_Ninfo          },
    {"Nmldump"       ,module_Nmldump        },
    {"Output"        ,module_Output         },
    {"Outputtab"     ,module_Outputtab      },
    {"Outputgmt"     ,module_Outputgmt      },
    {"Pack"          ,module_Pack           },
    {"Pardup"        ,module_Pardup         },
    {"Pinfo"         ,module_Pinfo          },
    {"Pressure"      ,module_Pressure       },
    {"Recttocomplex" ,module_Recttocomplex  },
    {"Regres"        ,module_Regres         },
    {"Remap"         ,module_Remap          },
    {"Remapbil"      ,module_Remapbil       },
    {"Remapbic"      ,module_Remapbic       },
    {"Remapnn"       ,module_Remapnn        },
    {"Remapdis"      ,module_Remapdis       },
    {"Remapcon"      ,module_Remapcon       },
    {"Remapycon2"    ,module_Remapycon2     },
    {"Remapscon"     ,module_Remapscon      },
    {"Remapcon2"     ,module_Remapcon2      },
    {"Remaplaf"      ,module_Remaplaf       },
    {"Remapgrid"     ,module_Remapgrid      },
    {"Remapweights"  ,module_Remapweights   },
    {"Remapeta"      ,module_Remapeta       },
    {"Replace"       ,module_Replace        },
    {"Replacevalues" ,module_Replacevalues  },
    {"Rhopot"        ,module_Rhopot         },
    {"Rotuv"         ,module_Rotuv          },
    {"Runpctl"       ,module_Runpctl        },
    {"Runstat"       ,module_Runstat        },
    {"Samplegridicon",module_Samplegridicon },
    {"Seascount"     ,module_Seascount      },
    {"Seaspctl"      ,module_Seaspctl       },
    {"Seasstat"      ,module_Seasstat       },
    {"Selbox"        ,module_Selbox         },
    {"Selgridcell"   ,module_Selgridcell    },
    {"Select"        ,module_Select         },
    {"Selvar"        ,module_Selvar         },
    {"Selrec"        ,module_Selrec         },
    {"Seloperator"   ,module_Seloperator    },
    {"Seltime"       ,module_Seltime        },
    {"Selyearidx"    ,module_Selyearidx     },
    {"Set"           ,module_Set            },
    {"Setattribute"  ,module_Setattribute   },
    {"Setbox"        ,module_Setbox         },
    {"Setgatt"       ,module_Setgatt        },
    {"Setgrid"       ,module_Setgrid        },
    {"Sethalo"       ,module_Sethalo        },
    {"Setmiss"       ,module_Setmiss        },
    {"Setmisstonn"   ,module_Setmisstonn    },
    {"Setcodetab"    ,module_Setcodetab     },
    {"Setpartab"     ,module_Setpartab      },
    {"Setrcaname"    ,module_Setrcaname     },
    {"Settime"       ,module_Settime        },
    {"Setzaxis"      ,module_Setzaxis       },
    {"Shiftxy"       ,module_Shiftxy        },
    {"Showinfo"      ,module_Showinfo       },
    {"Showattribute" ,module_Showattribute  },
    {"Sinfo"         ,module_Sinfo          },
    {"Smooth"        ,module_Smooth         },
    {"Sort"          ,module_Sort           },
    {"Sorttimestamp" ,module_Sorttimestamp  },
    {"Specinfo"      ,module_Specinfo       },
    {"Spectral"      ,module_Spectral       },
    {"Specconv"      ,module_Specconv       },
    {"Spectrum"      ,module_Spectrum       },
    {"Split"         ,module_Split          },
    {"Splitrec"      ,module_Splitrec       },
    {"Splitsel"      ,module_Splitsel       },
    {"Splittime"     ,module_Splittime      },
    {"Splityear"     ,module_Splityear      },
    {"Tee"           ,module_Tee            },
    {"Template1"     ,module_Template1      },
    {"Template2"     ,module_Template2      },
    {"Test"          ,module_Test           },
    {"Test2"         ,module_Test2          },
    {"Testdata"      ,module_Testdata       },
    {"Tests"         ,module_Tests          },
    {"Timcount"      ,module_Timcount       },
    {"Yearcount"     ,module_Yearcount      },
    {"Moncount"      ,module_Moncount       },
    {"Daycount"      ,module_Daycount       },
    {"Hourcount"     ,module_Hourcount      },
    {"Timcumsum"     ,module_Timcumsum      },
    {"Timpctl"       ,module_Timpctl        },
    {"Yearpctl"      ,module_Yearpctl       },
    {"Monpctl"       ,module_Monpctl        },
    {"Daypctl"       ,module_Daypctl        },
    {"Hourpctl"      ,module_Hourpctl       },
    {"Timselpctl"    ,module_Timselpctl     },
    {"Timsort"       ,module_Timsort        },
    {"Timselstat"    ,module_Timselstat     },
    {"XTimstat"      ,module_XTimstat       },
    {"Timstat"       ,module_Timstat        },
    {"Yearstat"      ,module_Yearstat       },
    {"Monstat"       ,module_Monstat        },
    {"Daystat"       ,module_Daystat        },
    {"Hourstat"      ,module_Hourstat       },
    {"Timcor"        ,module_Timcor         },
    {"Timscorvar"    ,module_Timscorvar     },
    {"Timstat3"      ,module_Timstat3       },
    {"Tinfo"         ,module_Tinfo          },
    {"Tocomplex"     ,module_Tocomplex      },
    {"Transpose"     ,module_Transpose      },
    {"Trend"         ,module_Trend          },
    {"Trendarith"    ,module_Trendarith     },
    {"Tstepcount"    ,module_Tstepcount     },
    {"Unpack"        ,module_Unpack         },
    {"Vargen"        ,module_Vargen         },
    {"Varrms"        ,module_Varrms         },
    {"Varsstat"      ,module_Varsstat       },
    {"Vertintml"     ,module_Vertintml      },
    {"Vertintap"     ,module_Vertintap      },
    {"Vertstat"      ,module_Vertstat       },
    {"Vertcum"       ,module_Vertcum        },
    {"Vertwind"      ,module_Vertwind       },
    {"Verifygrid"    ,module_Verifygrid     },
    {"Wind"          ,module_Wind           },
    {"Wind2"         ,module_Wind2          },
    {"Writegrid"     ,module_Writegrid      },
    {"Writerandom"   ,module_Writerandom    },
    {"Yeararith"     ,module_Yeararith      },
    {"Yearmonstat"   ,module_Yearmonstat    },
    {"Ydayarith"     ,module_Ydayarith      },
    {"Ydaypctl"      ,module_Ydaypctl       },
    {"Ydaystat"      ,module_Ydaystat       },
    {"Ydrunpctl"     ,module_Ydrunpctl      },
    {"Ydrunstat"     ,module_Ydrunstat      },
    {"Yhourarith"    ,module_Yhourarith     },
    {"Yhourstat"     ,module_Yhourstat      },
    {"Dhourstat"     ,module_Dhourstat      },
    {"Ymonarith"     ,module_Ymonarith      },
    {"Yseasarith"    ,module_Yseasarith     },
    {"Ymonpctl"      ,module_Ymonpctl       },
    {"Ymonstat"      ,module_Ymonstat       },
    {"Yseaspctl"     ,module_Yseaspctl      },
    {"Yseasstat"     ,module_Yseasstat      },
    {"Zonstat"       ,module_Zonstat        },
    {"EcaCfd"        ,module_EcaCfd         },
    {"EcaCsu"        ,module_EcaCsu         },
    {"EcaCwdi"       ,module_EcaCwdi        },
    {"EcaCwfi"       ,module_EcaCwfi        },
    {"EcaEtr"        ,module_EcaEtr         },
    {"EcaEtccdi"     ,module_EcaEtccdi      },
    {"EcaFd"         ,module_EcaFd          },
    {"EcaGsl"        ,module_EcaGsl         },
    {"EcaHd"         ,module_EcaHd          },
    {"EcaHwdi"       ,module_EcaHwdi        },
    {"EcaHwfi"       ,module_EcaHwfi        },
    {"EcaId"         ,module_EcaId          },
    {"EcaSu"         ,module_EcaSu          },
    {"EcaTr"         ,module_EcaTr          },
    {"EcaTg10p"      ,module_EcaTg10p       },
    {"EcaTg90p"      ,module_EcaTg90p       },
    {"EcaTn10p"      ,module_EcaTn10p       },
    {"EcaTn90p"      ,module_EcaTn90p       },
    {"EcaTx10p"      ,module_EcaTx10p       },
    {"EcaTx90p"      ,module_EcaTx90p       },
    {"EcaCdd"        ,module_EcaCdd         },
    {"EcaCwd"        ,module_EcaCwd         },
    {"EcaRr1"        ,module_EcaRr1         },
    {"EcaPd"         ,module_EcaPd          },
    {"EcaR75p"       ,module_EcaR75p        },
    {"EcaR75ptot"    ,module_EcaR75ptot     },
    {"EcaR90p"       ,module_EcaR90p        },
    {"EcaR90ptot"    ,module_EcaR90ptot     },
    {"EcaR95p"       ,module_EcaR95p        },
    {"EcaR95ptot"    ,module_EcaR95ptot     },
    {"EcaR99p"       ,module_EcaR99p        },
    {"EcaR99ptot"    ,module_EcaR99ptot     },
    {"EcaRx1day"     ,module_EcaRx1day      },
    {"EcaRx5day"     ,module_EcaRx5day      },
    {"EcaSdii"       ,module_EcaSdii        },
    {"Fdns"          ,module_Fdns           },
    {"Strwin"        ,module_Strwin         },
    {"Strbre"        ,module_Strbre         },
    {"Strgal"        ,module_Strgal         },
    {"Hurr"          ,module_Hurr           },
    {"Wct"           ,module_Wct            },
    {"Magplot"       ,module_Magplot        },
    {"Magvector"     ,module_Magvector      },
    {"Maggraph"      ,module_Maggraph       },
    {"Samplegrid"    ,module_Samplegrid     },
    {"Selmulti "     ,module_Selmulti       },
    {"WindTrans"     ,module_WindTrans      }
    };

    registerOperatorsFromModules();
    init_aliases();
}
/* 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)
{
  extern int (*proj_lonlat_to_lcc_func)();
  extern int (*proj_lcc_to_lonlat_func)();
  proj_lonlat_to_lcc_func = (int (*)()) proj_lonlat_to_lcc;
  proj_lcc_to_lonlat_func = (int (*)()) proj_lcc_to_lonlat;

  extern int (*proj_lonlat_to_stere_func)();
  extern int (*proj_stere_to_lonlat_func)();
  proj_lonlat_to_stere_func = (int (*)()) proj_lonlat_to_stere;
  proj_stere_to_lonlat_func = (int (*)()) proj_stere_to_lonlat;
}

const char *
getProgname(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::setExitFunction(cdoExit);
  cdo::setContextFunction(processInqPrompt);
  progress::setContextFunction(processInqPrompt);

  mpmoColorSet(Auto);

  cdo_init_is_tty();

  memExitOnError();

  Options::CDO_Reduce_Dim = 0;

  /* mallopt(M_MMAP_MAX, 0); */

  setCommandLine(argc, argv);

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

  get_env_vars();
  init_modules();

  status = parseOptionsLong(argc, argv);
  if (status != 0) return 1;

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

  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();
    }

  check_stacksize();

  if (Debug) print_pthread_info();

#ifdef _OPENMP
  if (numThreads <= 0) numThreads = 1;
  omp_set_num_threads(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 < 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 (numThreads > 1) fprintf(stderr, "Warning: Option -P failed, OpenMP support not compiled in!\n");
#endif

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

  new_argv = expandWildCards(new_argv);
  new_argv = expandApply(new_argv);
  if (Options::cdoVerbose) std::cerr << argvToString(new_argv) << std::endl;

  ///*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());
    }
  // 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;

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

  set_external_proj_func();

  auto operatorName = get_original(argv[CDO_optind]);

  if (Help)
    {
      cdoPrintHelp(operatorHelp(operatorName));
    }
  else
    {
      if (Debug)
        {
          if (DebugLevel == 0)
            {
              std::cout << "No debug level given please choose: " << std::endl;
              printDebugOptions();
              exit(EXIT_SUCCESS);
            }
          if (DebugLevel > 1) cdiDebug(DebugLevel);
          SetDebug(DebugLevel);
        }

      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.createProcesses(new_argv.size(), (const char **) &new_cargv[0]);
      timer_start(timer_total);
      g_processManager.runProcesses();
      timer_stop(timer_total);
      g_processManager.clearProcesses();

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

  if (Options::cdoVarnames)
    {
      if (Options::cdoNumVarnames) Free(Options::cdoVarnames[0]);
      Free(Options::cdoVarnames);
    }

  if (gridSearchDir) Free(gridSearchDir);

  if (CDO_Rusage) cdo_rusage();

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

  return status;
}
