/*
  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.
*/
/* ============================================================= */
/*                                                               */
/* postprocessing program for ECHAM data and ECMWF analysis data */
/*                                                               */
/* Luis     Kornblueh   - MPI    Hamburg                         */
/* Uwe      Schulzweida - MPI    Hamburg                         */
/* Arno     Hellbach    - DKRZ   Hamburg                         */
/* Edilbert Kirk        - MI Uni Hamburg                         */
/* Michael  Ponater     - DLR    Oberpfaffenhofen                */
/*                                                               */
/* ============================================================= */

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

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

#include <cdi.h>

#include "cdo_default_values.h"

#define streamOpenWrite cdoOpenWrite
#define streamDefVlist cdoDefVlist
#define streamDefTimestep cdoDefTimestep

#include "afterburner.h"
#include "constants.h"
#include "compare.h"
#include "cdo_options.h"
#include "mpmo_color.h"
#include "cdi_lockedIO.h"

int scan_par_obsolate(char *namelist, const char *name, int def);
void scan_code(char *namelist, struct Variable *vars, int maxCodes, int *numCodes);
int scan_par(int verbose, char *namelist, const char *name, int def);
int scan_time(int verbose, char *namelist, int *hours, int max_hours);
void scan_darray(char *namelist, const char *name, double *values, int maxValues, int *numValues);

char zaxistypename[CDI_MAX_NAME];

struct RARG
{
  int lana, nrecs;
  struct Variable *vars;
  struct Control *globs;
};

cdo::Task *afterReadTask = nullptr;

static bool lstdout = true;

static int Source = 0;

static int ofiletype = -1;

static int DataType = -1;

static char *filename;
static const char **ifiles;
static char *ifile = nullptr;
static const char *ofile = nullptr;

static int ofileidx = 0;

static int specGridID = -1;
static int gaussGridID = -1;
static int iVertID = -1;
static int oVertID = -1;

static bool Lhybrid2pressure = false;

static int TsID;
bool afterReadAsync = true;

#define TIMESTEP_INTERVAL -1
#define MONTHLY_INTERVAL 0
#define DAILY_INTERVAL 1
#define UNLIM_INTERVAL 2

#define MaxHours 24
static int nrqh;
static int hours[MaxHours + 1];

static std::vector<double> LevelFound;

static void
lprintf(FILE *fp)
{
  int num = 67;
  int cval = '-';

  fprintf(fp, " ");
  for (int inum = 0; inum < num; inum++) fprintf(fp, "%c", cval);
  fprintf(fp, "\n");
}

static void
FreeMean(struct Variable *vars)
{
  for (int code = 0; code < MaxCodes; code++)
    if (vars[code].mean)
      {
        Free(vars[code].mean);
        vars[code].mean = nullptr;
      }
}

static void
after_PostProcess(struct Control *globs)
{
  if (globs->EndOfInterval)
    {
      if (lstdout)
        {
          if (globs->OutputInterval == DAILY_INTERVAL)
            fprintf(stdout, " Processed Day %2d  Month %2d  Year %04d", globs->OldDate.dy, globs->OldDate.mo, globs->OldDate.yr);
          else if (globs->OutputInterval == MONTHLY_INTERVAL)
            fprintf(stdout, " Processed Month %2d  Year %04d", globs->OldDate.mo, globs->OldDate.yr);
          else if (globs->OutputInterval == UNLIM_INTERVAL)
            fprintf(stdout, " Processed range from %6.4d-%2.2d-%2.2d to %6.4d-%2.2d-%2.2d", globs->StartDate.yr,
                    globs->StartDate.mo, globs->StartDate.dy, globs->OldDate.yr, globs->OldDate.mo, globs->OldDate.dy);

          if (globs->Mean)
            fprintf(stdout, "  (Mean of %3d Terms)\n", globs->MeanCount);
          else
            fprintf(stdout, "   Terms %3d\n", globs->MeanCount);
        }

      globs->EndOfInterval = false;
      globs->MeanCount = 0;
    }
}

/* ================= */
/* switch input file */
/* ================= */
static void
after_SwitchFile(struct Control *globs)
{
  bool echam4 = false;
  int n;
  char y3, y2, y1, y0;
  char m1, m0;
  char d1, d0;

  streamClose(globs->istreamID);

  if (globs->Multi > 0)
    {
      int i = strlen(ifile);
      if (i < 10)
        {
          fprintf(stderr, " Not a valid filename: %s \n", ifile);
          exit(1);
        }

      if (ifile[i - 3] == '.')
        {
          echam4 = true;
          y3 = ifile[i - 9];
          y2 = ifile[i - 8];
          y1 = ifile[i - 7];
          y0 = ifile[i - 6];
          m1 = ifile[i - 5];
          m0 = ifile[i - 4];
          d1 = ifile[i - 2];
          d0 = ifile[i - 1];
        }
      else
        {
          y3 = ifile[i - 6];
          y2 = ifile[i - 5];
          y1 = ifile[i - 4];
          y0 = ifile[i - 3];
          m1 = ifile[i - 2];
          m0 = ifile[i - 1];
          d1 = '0';
          d0 = '1';
        }

      for (n = 0; n < globs->DayIn; n++)
        {
          if (d0 == '9')
            {
              d0 = '0';
              d1++;
            }
          else
            d0++;
          if (d1 == '3' && d0 > '0')
            {
              d1 = '0';
              d0 = '1';
              if (m1 == '0')
                {
                  if (m0 == '9')
                    {
                      m0 = '0';
                      m1 = '1';
                    }
                  else
                    m0++;
                }
              else
                {
                  if (m0 < '2')
                    m0++;
                  else
                    {
                      m1 = '0';
                      m0 = '1';
                      y0++;
                      if (y0 > '9')
                        {
                          y0 = '0';
                          y1++;
                        }
                      if (y1 > '9')
                        {
                          y1 = (char) '0';
                          if (isdigit((int) y2))
                            y2++;
                          else
                            y2 = '1';
                          if (y2 > '9')
                            {
                              y2 = (char) '0';
                              if (isdigit((int) y3))
                                y3++;
                              else
                                y3 = '1';
                            }
                        }
                    }
                }
            }
        }

      if (echam4)
        {
          ifile[i - 9] = y3;
          ifile[i - 8] = y2;
          ifile[i - 7] = y1;
          ifile[i - 6] = y0;
          ifile[i - 5] = m1;
          ifile[i - 4] = m0;
          ifile[i - 2] = d1;
          ifile[i - 1] = d0;
        }
      else
        {
          ifile[i - 6] = y3;
          ifile[i - 5] = y2;
          ifile[i - 4] = y1;
          ifile[i - 3] = y0;
          ifile[i - 2] = m1;
          ifile[i - 1] = m0;
        }

      globs->Multi--;
    }

  if (globs->Nfiles > 0) ifile = (char *) ifiles[--globs->Nfiles];

  fprintf(stderr, " Continuation file: %s\n", ifile);

  globs->istreamID = streamOpenReadLocked(ifile);

  globs->ivlistID = streamInqVlist(globs->istreamID);
  globs->taxisID = vlistInqTaxis(globs->ivlistID);
}

static int64_t
after_getDate(struct Date datetime)
{
  return cdiEncodeDate(datetime.yr, datetime.mo, datetime.dy);
}

static int
after_getTime(struct Date datetime)
{
  return cdiEncodeTime(datetime.hr, datetime.mn, 0);
}

static void
after_setDateTime(struct Date *datetime, int64_t date, int time)
{
  int sec;
  cdiDecodeDate(date, &datetime->yr, &datetime->mo, &datetime->dy);
  cdiDecodeTime(time, &datetime->hr, &datetime->mn, &sec);
}

static void
after_printProcessStatus(int tsID)
{
  static bool counthead = false;

  if (tsID == -1)
    {
      if (stdout_is_tty)
        {
          fprintf(stdout, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
          fflush(stdout);
        }

      counthead = false;
    }
  else
    {
      if (!counthead)
        {
          if (stdout_is_tty) fprintf(stdout, " Process timestep :       ");

          counthead = true;
        }

      if (stdout_is_tty)
        {
          fprintf(stdout, "\b\b\b\b\b\b%6d", tsID);
          fflush(stdout);
        }
    }
}

static int
after_setNextDate(struct Control *globs)
{
  int nrecs = 0;

  bool righttime = false;
  while (true)
    {
      nrecs = streamInqTimestep(globs->istreamID, TsID);
      if (nrecs == 0 && (globs->Multi > 0 || globs->Nfiles > 0))
        {
          if (lstdout) after_printProcessStatus(-1);

          after_SwitchFile(globs);

          if (globs->istreamID >= 0)
            {
              TsID = 0;
              nrecs = streamInqTimestep(globs->istreamID, TsID);
            }
        }
      if (nrecs == 0) break;

      const auto vdate = taxisInqVdate(globs->taxisID);
      const auto vtime = taxisInqVtime(globs->taxisID);
      after_setDateTime(&globs->NextDate, vdate, vtime);

      for (int i = 0; i < nrqh; i++)
        if (hours[i] < 0 || hours[i] == globs->NextDate.hr)
          {
            righttime = true;
            break;
          }

      if (righttime)
        break;
      else
        TsID += 1;
    }

  return nrecs;
}

static int num_recs = 0;

static void *
after_readTimestep(void *arg)
{
  int varID, gridID, zaxisID, levelID, timeID;
  size_t nmiss;
  auto rarg = (RARG *) arg;

  auto nrecs = rarg->nrecs;
  auto analysisData = rarg->lana;
  auto vars = rarg->vars;
  auto globs = rarg->globs;

  for (int code = 0; code < MaxCodes; code++) vars[code].nmiss0 = 0;

  for (int recID = 0; recID < nrecs; recID++)
    {
      streamInqRecord(globs->istreamID, &varID, &levelID);

      const auto code = vlistInqVarCode(globs->ivlistID, varID);
      if (code <= 0 || code >= MaxCodes) continue;

      /* Skip records containing unneeded codes */

      if (!vars[code].needed0) continue;

      vlistInqVar(globs->ivlistID, varID, &gridID, &zaxisID, &timeID);

      const auto leveltype = zaxisInqType(zaxisID);

      /* Skip records with unselected levels */

      int levelOffset = -1;
      /*
        if ( vars[code].ozaxisID != vars[code].izaxisID && ! Lhybrid2pressure )
      */
      if ((vars[code].ozaxisID != vars[code].izaxisID) && (leveltype == ZAXIS_PRESSURE))
        {
          const auto level = (int) zaxisInqLevel(zaxisID, levelID);
          for (int i = 0; i < globs->NumLevelRequest; ++i)
            {
              if (IS_EQUAL(globs->LevelRequest[i], level))
                {
                  levelOffset = i;
                  break;
                }
            }

          if (levelOffset < 0) continue;

          zaxisID = vars[code].ozaxisID;
          levelID = levelOffset;
        }

      if (globs->Debug)
        {
          fprintf(stderr, "T%d", globs->Truncation);

          fprintf(stderr, "  Code %3d   Level%6d   %6.4d-%2.2d-%2.2d  %2.2d:%2.2d:00\n", code,
                  (int) zaxisInqLevel(zaxisID, levelID), globs->OldDate.yr, globs->OldDate.mo, globs->OldDate.dy, globs->OldDate.hr,
                  globs->OldDate.mn);
        }

      if (analysisData)
        {
          streamReadRecord(globs->istreamID, globs->Field, &nmiss);
          after_AnalysisAddRecord(globs, vars, code, gridID, zaxisID, levelID, nmiss);
        }
      else
        {
          double *dataptr = after_get_dataptr(vars, code, gridID, zaxisID, levelID);
          streamReadRecord(globs->istreamID, dataptr, &nmiss);
          after_EchamAddRecord(globs, vars, code, gridID, zaxisID, levelID, nmiss);
        }

      if (iVertID != -1 && oVertID != -1 && (vars[code].izaxisID == iVertID)) vars[code].ozaxisID = oVertID;
    }

  TsID++;

  // printf("%3d  date = %d  time = %04d\n", TsID, vdate, vtime);

  num_recs = after_setNextDate(globs);

  return (void *) &num_recs;
}

static void
after_defineNextTimestep(struct Control *globs)
{
  static int otsID = 0;
  const auto vdate = after_getDate(globs->OldDate);
  const auto vtime = after_getTime(globs->OldDate);
  taxisDefVdate(globs->taxisID2, vdate);
  taxisDefVtime(globs->taxisID2, vtime);

  if (globs->Mean != 2)
    {
      if (otsID == 0)
        {
          const auto nvars = vlistNvars(globs->ovlistID);
          if (nvars == 0) afterAbort("No variable selected!");
          vlistDefTaxis(globs->ovlistID, globs->taxisID2);
          streamDefVlist(globs->ostreamID, globs->ovlistID);
        }
      taxisDefNumavg(globs->taxisID2, globs->MeanCount + 1);
      streamDefTimestep(globs->ostreamID, otsID);
    }

  otsID++;
}

static void
after_setEndOfInterval(struct Control *globs, int nrecs)
{
  if (nrecs == 0)
    {
      globs->EndOfInterval = true;
    }
  else
    {
      if (globs->OutputInterval == DAILY_INTERVAL)
        globs->EndOfInterval = globs->NewDate.dy != globs->OldDate.dy;
      else if (globs->OutputInterval == MONTHLY_INTERVAL)
        globs->EndOfInterval = globs->NewDate.mo != globs->OldDate.mo;
      else if (globs->OutputInterval == UNLIM_INTERVAL)
        globs->EndOfInterval = false;
      else
        afterAbort("output interval %d not implemented!", globs->OutputInterval);
    }
}

static void
after_moveTimestep(struct Variable *vars)
{
  int code;

  for (code = 0; code < MaxCodes; code++) vars[code].nmiss = vars[code].nmiss0;

  for (code = 0; code < MaxCodes; code++)
    if (vars[code].hybrid0)
      {
        vars[code].hybrid = vars[code].hybrid0;
        vars[code].hybrid0 = nullptr;
      }

  for (code = 0; code < MaxCodes; code++)
    if (vars[code].spectral0)
      {
        vars[code].spectral = vars[code].spectral0;
        vars[code].spectral0 = nullptr;
      }

  for (code = 0; code < MaxCodes; code++)
    if (vars[code].grid0)
      {
        vars[code].grid = vars[code].grid0;
        vars[code].grid0 = nullptr;
      }
}

static void
after_check_content(struct Variable *vars, int timestep)
{
  for (int code = 0; code < 272; code++)
    {
      /*  if ( code == GEOPOTENTIAL ) continue; */
      if (code == SLP) continue;
      if (code == GEOPOTHEIGHT) continue;
      if (code == STREAM) continue;
      if (code == VELOPOT) continue;
      if (code == U_WIND) continue;
      if (code == V_WIND) continue;
      if (code == OMEGA) continue;
      if (code == RHUMIDITY) continue;
      if (code == LOW_CLOUD) continue;
      if (code == MID_CLOUD) continue;
      if (code == HIH_CLOUD) continue;
      if (code == PS) continue;
      if (code == HUMIDITY)
        {
          if (vars[code].needed && !vars[code].selected && vars[code].spectral == nullptr && vars[code].hybrid == nullptr)
            {
              static bool lwarn = true;
              if (lwarn) cdoWarning("No humidity in data file, set to zero !");
              lwarn = false;
              vars[code].needed = false;
            }
        }
      else
        {
          if (vars[code].needed && !vars[code].comp && vars[code].spectral == nullptr && vars[code].hybrid == nullptr)
            {
              afterAbort("Code  %3d not found at timestep %d!", code, timestep);
            }
        }
    }
}

static void
after_control(struct Control *globs, struct Variable *vars)
{
  int i;
  int nrecs;
  int64_t rdate, vdate;
  int rtime, vtime;
  int code;
  RARG rarg;

  if (afterReadAsync) afterReadTask = new cdo::Task;

  for (code = 0; code < MaxCodes; code++) vars[code].needed0 = vars[code].needed;

  TsID = 0;

  bool righttime = false;
  while ((nrecs = streamInqTimestep(globs->istreamID, TsID)) > 0)
    {
      vdate = taxisInqVdate(globs->taxisID);
      vtime = taxisInqVtime(globs->taxisID);
      after_setDateTime(&globs->StartDate, vdate, vtime);
      after_setDateTime(&globs->NewDate, vdate, vtime);

      for (i = 0; i < nrqh; i++)
        if (hours[i] < 0 || hours[i] == globs->NewDate.hr)
          {
            righttime = true;
            break;
          }

      if (righttime)
        break;
      else
        TsID++;
    }

  if (taxisInqType(globs->taxisID) == TAXIS_RELATIVE)
    {
      rdate = taxisInqRdate(globs->taxisID);
      rtime = taxisInqRtime(globs->taxisID);
    }
  else
    {
      rdate = after_getDate(globs->StartDate);
      rtime = after_getTime(globs->StartDate);
    }

  if (ofiletype == CDI_FILETYPE_NC || ofiletype == CDI_FILETYPE_NC2 || ofiletype == CDI_FILETYPE_NC4
      || ofiletype == CDI_FILETYPE_NC4C || ofiletype == CDI_FILETYPE_NC5)
    {
      taxisDefCalendar(globs->taxisID2, CALENDAR_PROLEPTIC);
      taxisDefType(globs->taxisID2, TAXIS_RELATIVE);
      taxisDefTunit(globs->taxisID2, TUNIT_DAY);
      taxisDefRdate(globs->taxisID2, rdate);
      taxisDefRtime(globs->taxisID2, rtime);
    }

  globs->OldDate = globs->NewDate;

  bool tsFirst = true;

  while (nrecs > 0)
    {
      rarg.nrecs = nrecs;
      rarg.lana = globs->AnalysisData;
      rarg.vars = vars;
      rarg.globs = globs;

      int status = -1;

      if (tsFirst || !afterReadAsync)
        {
          if (afterReadAsync)
            {
              afterReadTask->start(after_readTimestep, &rarg);
            }
          else
            {
              status = *(int *) after_readTimestep(&rarg);
            }

          if (tsFirst && globs->Type > 0) after_legini_setup(globs, vars);

          if (afterReadAsync)
            {
              status = *(int *) afterReadTask->wait();
              if (status < 0) cdoAbort("after_readTimestep error! (status = %d)", status);
            }
          tsFirst = false;
        }
      else
        {
          status = *(int *) afterReadTask->wait();
          if (status < 0) cdoAbort("after_readTimestep error! (status = %d)", status);
        }

      nrecs = status;

      globs->MeanCount0 = globs->MeanCount;
      globs->NewDate = globs->NextDate;

      after_moveTimestep(vars);

      if (nrecs && afterReadAsync)
        {
          afterReadTask->start(after_readTimestep, &rarg);
        }

      after_setEndOfInterval(globs, nrecs);

      if (lstdout) after_printProcessStatus(TsID);

      if (lstdout && globs->EndOfInterval) after_printProcessStatus(-1);

      if (globs->Mean == 0 || globs->EndOfInterval)
        {
          if (!globs->AnalysisData) after_check_content(vars, globs->TermCount + 1);
          after_defineNextTimestep(globs);
        }

      if (globs->AnalysisData)
        after_processPL(globs, vars);
      else
        after_processML(globs, vars);

      after_PostProcess(globs);

      if (nrecs)
        {
          if (globs->AnalysisData)
            after_AnalysisDependencies(vars, MaxCodes);
          else
            after_EchamDependencies(vars, MaxCodes, globs->Type, Source);
        }

      globs->OldDate = globs->NewDate;
    }

  if (afterReadTask) delete afterReadTask;
}

static void
after_setLevel(struct Control *globs)
{
  int k, l, found;
  bool removeLevel[MaxLevel];
  double level;
  bool checkLevel = true;
  // default pressure level
  long plevelDefault[]
      = { 100000, 92500, 85000, 70000, 60000, 50000, 40000, 30000, 25000, 20000, 15000, 10000, 7000, 5000, 3000, 2000, 1000 };
  // default height level
  long hlevelDefault[] = { 0, 1000, 2000, 5000, 10000, 15000, 20000, 25000, 30000 };

  const int numplevelDefault = sizeof(plevelDefault) / sizeof(plevelDefault[0]);
  const int numhlevelDefault = sizeof(hlevelDefault) / sizeof(hlevelDefault[0]);

  if (iVertID != -1)
    if (zaxisInqType(iVertID) == ZAXIS_HYBRID && globs->Type > 20) Lhybrid2pressure = true;

  if (globs->Verbose) lprintf(stdout);

  if (globs->NumLevelRequest == 0)
    {
      if (iVertID == -1)
        {
          if (globs->Verbose) fprintf(stdout, " No level detected\n");
        }
      else
        {
          if (Lhybrid2pressure)
            {
              if (globs->unitsel == 0)
                {
                  if (globs->Verbose) fprintf(stdout, " Default pressure level selected:\n");
                  globs->NumLevelRequest = numplevelDefault;
                  for (l = 0; l < globs->NumLevelRequest; l++) globs->LevelRequest[l] = plevelDefault[l];
                  oVertID = zaxisCreate(ZAXIS_PRESSURE, globs->NumLevelRequest);
                  zaxisDefLevels(oVertID, globs->LevelRequest);
                }
              else
                {
                  if (globs->Verbose) fprintf(stdout, " Default height level selected:\n");
                  globs->NumLevelRequest = numhlevelDefault;
                  for (l = 0; l < globs->NumLevelRequest; l++) globs->LevelRequest[l] = hlevelDefault[l];
                  oVertID = zaxisCreate(ZAXIS_HEIGHT, globs->NumLevelRequest);
                  zaxisDefLevels(oVertID, globs->LevelRequest);
                }
            }
          else
            {
              if (globs->Verbose)
                {
                  if (zaxisInqType(iVertID) == ZAXIS_HYBRID)
                    fprintf(stdout, " All detected hybrid level selected:\n");
                  else
                    fprintf(stdout, " All detected pressure level selected:\n");
                }
              globs->NumLevelRequest = globs->NumLevelFound;
              for (l = 0; l < globs->NumLevelRequest; l++) globs->LevelRequest[l] = LevelFound[l];
              oVertID = iVertID;
            }
        }
      checkLevel = false;
    }
  else
    {
      if (iVertID == -1)
        {
          if (globs->Verbose) fprintf(stdout, " No level detected\n");
          checkLevel = false;
        }
      else if (globs->NumLevelRequest == 1 && IS_EQUAL(globs->LevelRequest[0], 0))
        {
          if (globs->Verbose) fprintf(stdout, " No level selected\n");
          globs->NumLevelRequest = 0;
          checkLevel = false;
        }
      else if (globs->Verbose)
        {
          if (Lhybrid2pressure)
            {
              if (globs->unitsel == 0)
                fprintf(stdout, " Selected pressure level:\n");
              else
                fprintf(stdout, " Selected height level:\n");
            }
          else
            {
              if (zaxisInqType(iVertID) == ZAXIS_HYBRID)
                fprintf(stdout, " Selected hybrid level:\n");
              else
                {
                  if (globs->unitsel == 0)
                    fprintf(stdout, " Selected pressure level:\n");
                  else
                    fprintf(stdout, " Selected height level:\n");
                }
            }
        }
    }

  if (globs->Verbose && iVertID != -1)
    for (l = 0; l < globs->NumLevelRequest; l++) fprintf(stdout, "  Level %2d = %13.4f\n", l + 1, globs->LevelRequest[l]);

  if (checkLevel)
    {
      for (k = 0; k < globs->NumLevelRequest; k++) removeLevel[k] = false;
      for (k = 0; k < globs->NumLevelRequest; k++)
        {
          level = globs->LevelRequest[k];
          for (l = k + 1; l < globs->NumLevelRequest; l++)
            if (removeLevel[l] == false && IS_EQUAL(level, globs->LevelRequest[l]))
              {
                if (globs->Verbose) fprintf(stdout, "  Level %2d = %13.4f double request\n", l + 1, globs->LevelRequest[l]);
                removeLevel[l] = true;
              }
        }

      l = 0;
      for (k = 0; k < globs->NumLevelRequest; k++)
        if (removeLevel[k] == false) globs->LevelRequest[l++] = globs->LevelRequest[k];

      globs->NumLevelRequest = l;

      if (globs->AnalysisData || globs->Type < 30)
        {
          for (k = 0; k < globs->NumLevelRequest; k++) removeLevel[k] = false;
          for (k = 0; k < globs->NumLevelRequest; k++)
            {
              level = globs->LevelRequest[k];
              found = false;
              for (l = 0; l < globs->NumLevelFound; l++)
                if (IS_EQUAL(level, LevelFound[l])) found = true;

              if (!found)
                {
                  fprintf(stdout, "  Level %2d = %14.4f not in input\n", k + 1, globs->LevelRequest[k]);
                  removeLevel[k] = true;
                }
            }

          l = 0;
          for (k = 0; k < globs->NumLevelRequest; k++)
            if (removeLevel[k] == false) globs->LevelRequest[l++] = globs->LevelRequest[k];

          if (l != globs->NumLevelRequest)
            {
              if (globs->Verbose) lprintf(stdout);
              afterAbort("Inconsistent or invalid level list!");
            }

          globs->NumLevelRequest = l;
        }
    }

  if (globs->Verbose) lprintf(stdout);
}

static void
after_defineLevel(struct Control *globs, struct Variable *vars)
{
  int code, i;

  /* hybrid, pressure, height */

  switch (globs->Type)
    {
    case 0:
    case 10:
    case 11:
    case 20:
      {
        if (iVertID == -1) break;

        if (zaxisInqType(iVertID) == ZAXIS_HYBRID)
          {
            if (oVertID == -1)
              {
                if (globs->NumLevelRequest > globs->NumLevelFound) afterAbort("Too much level requested");

                if (globs->NumLevelFound == globs->NumLevelRequest)
                  {
                    for (i = 0; i < globs->NumLevelRequest; i++)
                      if (IS_NOT_EQUAL(globs->LevelRequest[i], LevelFound[i])) break;

                    if (i == globs->NumLevelRequest) oVertID = iVertID;
                  }

                if (oVertID == -1 && globs->NumLevelRequest > 0)
                  {
                    oVertID = zaxisCreate(ZAXIS_HYBRID, globs->NumLevelRequest);
                    zaxisDefLevels(oVertID, globs->LevelRequest);
                    zaxisDefVct(oVertID, globs->nvct, globs->vct);
                  }
              }

            for (code = 0; code < MaxCodes; code++)
              {
                if (vars[code].selected)
                  {
                    if (vars[code].izaxisID != -1)
                      if (zaxisInqType(vars[code].izaxisID) == ZAXIS_HYBRID
                          && zaxisInqSize(vars[code].izaxisID) >= globs->NumLevelRequest)
                        vars[code].ozaxisID = oVertID;
                  }
              }
          }
        else
          {
            zaxisName(zaxisInqType(iVertID), zaxistypename);
            afterAbort("%s level data unsupported for TYPE %d", zaxistypename, globs->Type);
          }
        break;
      }
    case 30:
    case 40:
    case 41:
    case 50:
    case 60:
    case 61:
    case 70:
      {
        if (iVertID == -1) break;

        if (oVertID == -1)
          {
            if (globs->unitsel == 0)
              oVertID = zaxisCreate(ZAXIS_PRESSURE, globs->NumLevelRequest);
            else
              oVertID = zaxisCreate(ZAXIS_HEIGHT, globs->NumLevelRequest);

            zaxisDefLevels(oVertID, globs->LevelRequest);
          }

        for (code = 0; code < MaxCodes; code++)
          {
            if (vars[code].selected)
              {
                if (vars[code].izaxisID != -1)
                  {
                    int nlev = zaxisInqSize(vars[code].izaxisID);
                    if (zaxisInqType(vars[code].izaxisID) == zaxisInqType(iVertID)
                        && (nlev == globs->NumLevel || nlev == globs->NumLevel + 1) && nlev > 1)
                      vars[code].ozaxisID = oVertID;
                  }
              }
          }

        break;
      }
    default: afterAbort("TYPE %d unsupported", globs->Type);
    }
}

static void
after_defineGrid(struct Control *globs, struct Variable *vars)
{
  int ogridID = -1;
  int code;

  /* spectral, fourier, gauss, zonal mean */

  switch (globs->Type)
    {
    case 0:
    case 50:
      {
        if (specGridID == -1)
          {
            if (globs->DimSP == 0) afterAbort("dim spectral undefined");
            if (globs->Truncation == 0) afterAbort("truncation undefined");

            specGridID = gridCreate(GRID_SPECTRAL, globs->DimSP);
            gridDefTrunc(specGridID, globs->Truncation);
          }

        ogridID = specGridID;
        break;
      }
    case 20:
    case 30:
    case 70:
      {
        if (gaussGridID == -1)
          {
            if (globs->Longitudes == 0) afterAbort("number of longitudes undefined");
            if (globs->Latitudes == 0) afterAbort("number of latitudes undefined");

            gaussGridID = gridCreate(GRID_GAUSSIAN, globs->Longitudes * globs->Latitudes);
            gridDefXsize(gaussGridID, globs->Longitudes);
            gridDefYsize(gaussGridID, globs->Latitudes);
          }

        ogridID = gaussGridID;
        break;
      }
    case 10:
    case 40:
    case 60:
      {
        if (globs->Fouriers == 0) afterAbort("number of fourier coefficients undefined");
        if (globs->Latitudes == 0) afterAbort("number of latitudes undefined");

        ogridID = gridCreate(GRID_FOURIER, globs->Fouriers * globs->Latitudes);
        gridDefXsize(ogridID, globs->Latitudes);
        gridDefYsize(ogridID, globs->Fouriers);
        break;
      }
    case 11:
    case 41:
    case 61:
      {
        if (globs->Latitudes == 0) afterAbort("Number of latitudes undefined");

        ogridID = gridCreate(GRID_GAUSSIAN, globs->Latitudes);
        gridDefXsize(ogridID, 1);
        gridDefYsize(ogridID, globs->Latitudes);
        break;
      }
    default: afterAbort("TYPE %d unsupported", globs->Type);
    }

  if (ogridID != -1)
    for (code = 0; code < MaxCodes; code++)
      {
        if (vars[code].selected)
          {
            vars[code].ogridID = ogridID;
          }
      }

  if (ogridID == -1) afterAbort("out grid undefined");
}

static void
after_setCodes(struct Control *globs, struct Variable *vars, int maxCodes, int numCodes)
{
  if (globs->Verbose) lprintf(stdout);

  if (numCodes == 0)
    {
      if (globs->Verbose) fprintf(stdout, " All detected codes selected:\n");

      for (int code = 0; code < maxCodes; code++)
        if (vars[code].detected) vars[code].selected = 1;
    }
  else if (globs->Verbose)
    fprintf(stdout, " Selected codes:\n");

  if (globs->Verbose)
    {
      fprintf(stdout, "  Table Code Name              Longname\n");
      fprintf(stdout, "  ----- ---- ----              --------\n");
    }

  for (int code = 0; code < maxCodes; code++)
    if (vars[code].selected)
      {
        char name[CDI_MAX_NAME];
        name[0] = 0;
        char longname[CDI_MAX_NAME];
        longname[0] = 0;
        int tableID;
        int table = 0;
        const auto varID = vars[code].ivarID;

        if (varID == CDI_UNDEFID)
          {
            const auto modelID = vlistInqVarModel(globs->ivlistID, 0);
            table = 128;
            tableID = tableInq(modelID, table, nullptr);

            vars[code].tableID = tableID;
          }
        else
          {
            tableID = vlistInqVarTable(globs->ivlistID, varID);
            table = tableInqNum(tableID);
            vlistInqVarName(globs->ivlistID, varID, name);
            vlistInqVarLongname(globs->ivlistID, varID, longname);
          }

        if (!name[0]) tableInqEntry(tableID, code, -1, name, longname, nullptr);

        if (globs->Verbose)
          {
            fprintf(stdout, " %5d", table);
            fprintf(stdout, " %4d", code);
            if (!name[0])
              fprintf(stdout, "  var%d", code);
            else
              {
                fprintf(stdout, "  %-16s", name);
                if (longname[0]) fprintf(stdout, "  %s", longname);
              }
            fprintf(stdout, "\n");
          }
      }
}

static void
after_checkNamelist(struct Control *globs)
{
  if (globs->Mean && globs->Type < 20) afterAbort("Mean is only available for TYPE >= 20!");

  if (globs->Extrapolate == false && globs->Type >= 30)
    {
      if (globs->Type > 30) afterAbort("EXTRAPOLATE = 0 is only available for TYPE = 30!");
      if (globs->Mean) afterAbort("EXTRAPOLATE = 0 is only available with MEAN = 0!");
    }
}

static void
after_parini(struct Control *globs, struct Variable *vars)
{
  char namelist[65536];

  if (stdin_is_tty)
    {
      fprintf(stderr, "Default namelist: \n");
      fprintf(stderr, "  TYPE=0, CODE=-1, LEVEL=-1, INTERVAL=0, MEAN=0, EXTRAPOLATE=0\n");
      fprintf(stdout, "Enter namelist parameter:\n");
    }
  else
    {
      fseek(stdin, 0L, SEEK_END);
      const long length = ftell(stdin);
      if (length == 0L) fprintf(stderr, "\n stdin not connected\n");
      fseek(stdin, 0L, SEEK_SET);
    }

  int i = 1;
  namelist[0] = ' ';
  int c = getchar();
  while ((c != EOF) && i < (int) (sizeof(namelist) - 1))
    {
      if ((c >= '0' && c <= '9') || (c == '-' || c == '.'))
        namelist[i++] = c;
      else if (c >= 'a' && c <= 'z')
        namelist[i++] = c;
      else if (c >= 'A' && c <= 'Z')
        namelist[i++] = tolower(c);
      else
        c = ' ';

      if (c == ' ' && namelist[i - 1] != ' ') namelist[i++] = c;
      c = getchar();
    }
  namelist[i] = 0;

  if (globs->Debug)
    {
      lprintf(stderr);
      fprintf(stderr, "  Length of namelist:%4d bytes\n", (int) strlen(namelist));

      for (i = 0; i < (int) strlen(namelist); i += 60) fprintf(stderr, "  namelist[%02d]=%-60.60s\n", i, namelist + i);
      lprintf(stderr);
    }

  if (globs->Verbose)
    {
      lprintf(stdout);
      fprintf(stdout, " Namelist:\n");
    }

  globs->Type = scan_par(globs->Verbose, namelist, "type", 0);
  globs->Multi = scan_par(globs->Verbose, namelist, "multi", 0);
  globs->Mean = scan_par(globs->Verbose, namelist, "mean", 0);
  globs->OutputInterval = scan_par(globs->Verbose, namelist, "interval", MONTHLY_INTERVAL);

  if (globs->Mean >= 2) afterAbort("Namelist parameter MEAN=%d out of bounds (0:1)", globs->Mean);

  const auto fileFormat = scan_par(globs->Verbose, namelist, "format", -1);
  const auto gribFormat = scan_par_obsolate(namelist, "grib", 0);
  const auto cdfFormat = scan_par_obsolate(namelist, "netcdf", 0);

  if (gribFormat && cdfFormat) afterAbort("GRIB or NetCDF?");

  switch (fileFormat)
    {
    case -1: ofiletype = -1; break;
    case 0: ofiletype = CDI_FILETYPE_SRV; break;
    case 1: ofiletype = CDI_FILETYPE_GRB; break;
    case 2: ofiletype = CDI_FILETYPE_NC; break;
    case 3: ofiletype = CDI_FILETYPE_EXT; break;
    case 4: ofiletype = CDI_FILETYPE_NC2; break;
    case 5: ofiletype = CDI_FILETYPE_NC5; break;
    case 6: ofiletype = CDI_FILETYPE_NC4; break;
    default: afterAbort("unknown file format %d", fileFormat);
    }

  if (gribFormat) ofiletype = CDI_FILETYPE_GRB;
  if (cdfFormat) ofiletype = CDI_FILETYPE_NC;

  const int precision = scan_par(globs->Verbose, namelist, "precision", 0);
  if (precision) switch (precision)
      {
      case 8: DataType = CDI_DATATYPE_PACK8; break;
      case 16: DataType = CDI_DATATYPE_PACK16; break;
      case 24: DataType = CDI_DATATYPE_PACK24; break;
      case 32: DataType = CDI_DATATYPE_FLT32; break;
      case 64: DataType = CDI_DATATYPE_FLT64; break;
      default: afterAbort("unsupported data precision %d", precision);
      }

  globs->unitsel = scan_par(globs->Verbose, namelist, "unitsel", 0);
  globs->DayIn = scan_par(globs->Verbose, namelist, "dayinc", 30);
  globs->Extrapolate = scan_par(globs->Verbose, namelist, "extrapolate", 1);
  globs->Szip = scan_par(globs->Verbose, namelist, "szip", 0);

  if (globs->Multi) --globs->Multi;

  nrqh = scan_time(globs->Verbose, namelist, hours, MaxHours);
  scan_code(namelist, vars, MaxCodes, &globs->NumCodesRequest);

  scan_darray(namelist, "level", globs->LevelRequest, MaxLevel, &globs->NumLevelRequest);
  if (globs->NumLevelRequest == 1)
    if (IS_EQUAL(globs->LevelRequest[0], -1)) globs->NumLevelRequest = 0;

  if (globs->Verbose) lprintf(stdout);

  after_checkNamelist(globs);
}

static void
after_dimcalc(struct Control *globs)
{
  if (globs->AnalysisData) globs->NumLevel = globs->NumLevelRequest;

  if (globs->Latitudes == 0)
    {
      globs->Latitudes = 2 * ((globs->Truncation * 3 + 3) / 4);
      if (globs->Truncation == 30) globs->Latitudes = 48;
    }

  if (globs->Longitudes == 0)
    {
      globs->Longitudes = globs->Latitudes * 2;
      if (globs->Truncation == 62) globs->Longitudes = 192;
    }

  globs->Waves = globs->Truncation + 1;
  globs->Fouriers = globs->Waves * 2;
  globs->DimSP = (globs->Truncation + 1) * (globs->Truncation + 2);
  globs->DimFC = globs->Latitudes * globs->Fouriers;
  globs->DimGP = globs->Latitudes * globs->Longitudes;
  globs->Dim3GP = globs->NumLevel * globs->DimGP;
  globs->Dim3FC = globs->NumLevel * globs->DimFC;
  globs->Dim3SP = globs->NumLevel * globs->DimSP;
  globs->HalfLevels = globs->NumLevel + 1;
  globs->DimSP_half = globs->DimSP / 2;

  if (globs->AnalysisData) fprintf(stdout, " Found Ana or Re-Ana Data\n");

  if (globs->Verbose)
    {
      fprintf(stdout, " Dimensions:\n");
      fprintf(stdout, "  Truncation        = %4d\n", globs->Truncation);
      fprintf(stdout, "  Levels            = %4d\n", globs->NumLevel);
      fprintf(stdout, "  Latitudes         = %4d\n", globs->Latitudes);
      fprintf(stdout, "  Longitudes        = %4d\n", globs->Longitudes);
      lprintf(stdout);
    }
}

/* ----------------------------------------------------------- */
/* Extract basic dimension information                         */
/* ----------------------------------------------------------- */
static void
after_precntl(struct Control *globs, struct Variable *vars)
{
  int l;
  int varID;
  int i, index;
  int vertfound = 0;
  int nhzaxis = 0;
  int FieldDim = 0;

  const auto nvars = vlistNvars(globs->ivlistID);
  const auto ngrids = vlistNgrids(globs->ivlistID);
  const auto nverts = vlistNzaxis(globs->ivlistID);
  const auto ntsteps = vlistNtsteps(globs->ivlistID);

  if (globs->Debug)
    {
      fprintf(stderr, "nvars      = %d\n", nvars);
      fprintf(stderr, "ngrids     = %d\n", ngrids);
      fprintf(stderr, "nverts     = %d\n", nverts);
      fprintf(stderr, "ntsteps    = %d\n", ntsteps);
    }

  for (index = 0; index < ngrids; index++)
    {
      const auto gridID = vlistGrid(globs->ivlistID, index);
      const auto gridtype = gridInqType(gridID);
      const int datasize = gridInqSize(gridID);

      if (datasize > FieldDim) FieldDim = datasize;

      if (gridtype == GRID_SPECTRAL && globs->Truncation == 0)
        {
          specGridID = gridID;
          globs->Truncation = gridInqTrunc(gridID);
        }
      else if (gridtype == GRID_GAUSSIAN && globs->Latitudes == 0)
        {
          gaussGridID = gridID;
          globs->Longitudes = gridInqXsize(gridID);
          globs->Latitudes = gridInqYsize(gridID);
        }
    }

  if (globs->Truncation == 0 && globs->Latitudes == 0)
    afterAbort("Unsupported file structure (no spectral or Gaussian data found)!");

  if (globs->Truncation == 0)
    {
      if (globs->Latitudes)
        {
          switch (globs->Latitudes)
            {
            case 512: globs->Truncation = 511; break;
            case 320: globs->Truncation = 213; break;
            case 192: globs->Truncation = 127; break;
            case 160: globs->Truncation = 106; break;
            case 128: globs->Truncation = 85; break;
            case 96: globs->Truncation = 63; break;
            case 94: globs->Truncation = 62; break;
            case 64: globs->Truncation = 42; break;
            case 48: globs->Truncation = 31; break;
            case 32: globs->Truncation = 21; break;
            default: fprintf(stderr, "%d Gaussian latitudes not supported.\n", globs->Latitudes);
            }
        }
    }

  for (index = 0; index < nverts; index++)
    {
      const auto zaxisID = vlistZaxis(globs->ivlistID, index);
      const auto leveltype = zaxisInqType(zaxisID);
      const auto numlevel = zaxisInqSize(zaxisID);
      /*
        printf("leveltype : %d %d\n", leveltype, zaxisInqSize(zaxisID));
      */
      if (numlevel > 1)
        {
          if (leveltype == ZAXIS_HYBRID || leveltype == ZAXIS_PRESSURE)
            {
              if (leveltype == ZAXIS_HYBRID && globs->nvct == 0)
                {
                  nhzaxis++;
                  int nvct = zaxisInqVctSize(zaxisID);
                  if (numlevel != (nvct / 2 - 1))
                    {
                      if (nvct == 0)
                        {
                          if (numlevel != 191) cdoWarning("VCT missing for hybrid level data with %d levels!", numlevel);
                        }
                      else
                        {
                          cdoWarning("Skip %d hybrid level data with %d levels!", (nvct / 2 - 1), numlevel);
                        }
                      continue;
                    }
                }
              else if (leveltype == ZAXIS_HYBRID && globs->nvct == zaxisInqVctSize(zaxisID))
                continue;

              if (iVertID != -1) cdoWarning("More than %d different vertical grid structure found!", vertfound);

              vertfound++;

              if (iVertID != -1) continue;

              iVertID = zaxisID;
              globs->NumLevelFound = numlevel;
              LevelFound.resize(globs->NumLevelFound);
              for (l = 0; l < globs->NumLevelFound; l++) LevelFound[l] = (int) zaxisInqLevel(zaxisID, l);

              if (leveltype == ZAXIS_HYBRID)
                {
                  if (globs->nvct == 0)
                    {
                      if (zaxisInqVctSize(zaxisID))
                        {
                          globs->nvct = zaxisInqVctSize(zaxisID);

                          if (globs->vct == nullptr)
                            {
                              globs->vct = (double *) Malloc(globs->nvct * sizeof(double));
                              arrayCopy(globs->nvct, zaxisInqVctPtr(zaxisID), globs->vct);
                            }
                        }
                      else
                        {
                          afterAbort("VCT not defined in inputfile!");
                        }
                    }

                  if (numlevel != (globs->nvct / 2 - 1))
                    afterAbort("Number of hybrid levels %d does not match VCT levels %d", numlevel, globs->nvct / 2 - 1);

                  if (globs->Debug)
                    for (i = 0; i < globs->nvct / 2; i++)
                      fprintf(stderr, " vct: %4d %10.4f %10.4f\n", i, globs->vct[i], globs->vct[i + globs->nvct / 2]);
                }

              if (leveltype == ZAXIS_PRESSURE) globs->AnalysisData = true;
            }
        }
    }

  if (nhzaxis > 0 && globs->nvct == 0) afterAbort("VCT missing!");

  globs->NumLevel = globs->NumLevelFound;

  if (specGridID != -1) globs->Spectral = true;
  if (gaussGridID != -1) globs->Gaussian = true;

  if (globs->Debug) fprintf(stderr, "   T = %3d   L = %2d\n", globs->Truncation, globs->NumLevelFound);

  if (globs->Debug) fprintf(stderr, " CODE CHECK\n");

  if (globs->Verbose)
    {
      const auto instID = vlistInqVarInstitut(globs->ivlistID, 0);
      const auto modelID = vlistInqVarModel(globs->ivlistID, 0);

      lprintf(stdout);
      fprintf(stdout, " Institute : ");
      if (instID == CDI_UNDEFID)
        fprintf(stdout, "unknown\n");
      else
        {
          if (institutInqLongnamePtr(instID))
            fprintf(stdout, "%s\n", institutInqLongnamePtr(instID));
          else
            fprintf(stdout, "name unknown\n");
        }

      fprintf(stdout, " Source    : ");
      if (modelID == CDI_UNDEFID)
        fprintf(stdout, "unknown\n");
      else
        {
          if (modelInqNamePtr(modelID))
            {
              if (strncmp(modelInqNamePtr(modelID), "ECHAM5", 6) == 0) Source = S_ECHAM5;
              fprintf(stdout, "%s\n", modelInqNamePtr(modelID));
            }
          else
            fprintf(stdout, "name unknown\n");
        }
    }

  for (varID = 0; varID < nvars; varID++)
    {
      int gridID, zaxisID, timeID;
      vlistInqVar(globs->ivlistID, varID, &gridID, &zaxisID, &timeID);
      const auto code = vlistInqVarCode(globs->ivlistID, varID);
      if (code <= 0 || code >= MaxCodes)
        {
          cdoWarning("Code number %d out of range, variable ignored!", code);
          continue;
        }
      const auto gridtype = gridInqType(gridID);
      const auto numlevel = zaxisInqSize(zaxisID);
      const auto leveltype = zaxisInqType(zaxisID);

      vars[code].ivarID = varID;
      vars[code].igridID = gridID;
      vars[code].ogridID = gridID;
      vars[code].izaxisID = zaxisID;
      vars[code].ozaxisID = zaxisID;

      vars[code].detected = true;

      if (globs->Debug)
        fprintf(stderr, "Code %3d  Levels = %3d  LevelType = %3d  GridType = %3d\n", code, numlevel, leveltype, gridtype);
    }

  if (globs->Debug) fprintf(stderr, "FieldDim = %d\n", FieldDim);

  globs->Field = (double *) Malloc(FieldDim * sizeof(double));

  if (globs->Debug)
    for (int code = 0; code < MaxCodes; code++)
      {
        if (vars[code].detected) fprintf(stderr, " Detected Code %3d with %3d level\n", code, zaxisInqSize(vars[code].izaxisID));
      }
}

/*
 * -----------------------------------------------------------
 * Define output variables
 * -----------------------------------------------------------
 */
static void
after_postcntl(struct Control *globs, struct Variable *vars)
{
  int code = 0;
  char name[CDI_MAX_NAME], longname[CDI_MAX_NAME], units[CDI_MAX_NAME];
  int datatype;

  if (globs->Debug) lprintf(stdout);
  if (globs->Debug)
    for (code = 0; code < MaxCodes; code++)
      if (vars[code].detected)
        {
          const auto gridID = vars[code].igridID;
          const auto zaxisID = vars[code].izaxisID;
          zaxisName(zaxisInqType(zaxisID), zaxistypename);
          fprintf(stderr, " Detected Code %3d  grid %-8s size %5zu  level %2d %-8s\n", code, gridNamePtr(gridInqType(gridID)),
                  gridInqSize(gridID), zaxisInqSize(zaxisID), zaxistypename);
        }

  if (globs->Debug) lprintf(stdout);
  if (globs->Debug)
    for (code = 0; code < MaxCodes; code++)
      if (vars[code].needed)
        {
          fprintf(stderr, "   Needed Code %3d\n", code);
        }

  for (code = 0; code < MaxCodes; code++)
    if (vars[code].selected)
      {
        name[0] = 0;
        longname[0] = 0;
        units[0] = 0;
        const auto ivarID = vars[code].ivarID;
        const auto ogridID = vars[code].ogridID;
        const auto ozaxisID = vars[code].ozaxisID;

        if (ogridID == -1)
          {
            /*
            cdoWarning( "undefined grid for code %d", code);
            */
            continue;
          }
        if (ozaxisID == -1)
          {
            /*
            cdoWarning( "undefined level for code %d", code);
            */
            continue;
          }

        const auto instID = vlistInqVarInstitut(globs->ivlistID, ivarID);
        const auto modelID = vlistInqVarModel(globs->ivlistID, ivarID);
        auto tableID = vlistInqVarTable(globs->ivlistID, ivarID);

        vars[code].missval = vlistInqVarMissval(globs->ivlistID, ivarID);
        vars[code].samp = nullptr;

        if (DataType != -1)
          datatype = DataType;
        else
          datatype = vlistInqVarDatatype(globs->ivlistID, ivarID);

        if (vars[code].comp)
          {
            tableID = vars[code].tableID;
          }
        else
          {
            vlistInqVarName(globs->ivlistID, ivarID, name);
            vlistInqVarLongname(globs->ivlistID, ivarID, longname);
            vlistInqVarUnits(globs->ivlistID, ivarID, units);
          }

        if (globs->Mean != 2)
          {
            vlistDefTaxis(globs->ovlistID, globs->taxisID2);
            const auto ovarID = vlistDefVar(globs->ovlistID, ogridID, ozaxisID, TIME_VARYING);
            if (globs->Mean) vlistDefVarTsteptype(globs->ovlistID, ovarID, TSTEP_AVG);
            vlistDefVarCode(globs->ovlistID, ovarID, code);
            vars[code].ovarID = ovarID;
            vlistDefVarInstitut(globs->ovlistID, ovarID, instID);
            vlistDefVarModel(globs->ovlistID, ovarID, modelID);
            vlistDefVarTable(globs->ovlistID, ovarID, tableID);
            if (name[0]) cdiDefKeyString(globs->ovlistID, ovarID, CDI_KEY_NAME, name);
            if (longname[0]) cdiDefKeyString(globs->ovlistID, ovarID, CDI_KEY_LONGNAME, longname);
            if (units[0]) cdiDefKeyString(globs->ovlistID, ovarID, CDI_KEY_UNITS, units);
            vlistDefVarDatatype(globs->ovlistID, ovarID, datatype);
            vlistDefVarMissval(globs->ovlistID, ovarID, vars[code].missval);
          }

        if (globs->Mean >= 2)
          {
            vlistDefTaxis(globs->ovlistID2, globs->taxisID2);
            const auto ovarID2 = vlistDefVar(globs->ovlistID2, ogridID, ozaxisID, TIME_VARYING);
            if (globs->Mean) vlistDefVarTsteptype(globs->ovlistID2, ovarID2, TSTEP_AVG);
            vlistDefVarCode(globs->ovlistID2, ovarID2, code);
            vars[code].ovarID2 = ovarID2;
            vlistDefVarInstitut(globs->ovlistID2, ovarID2, instID);
            vlistDefVarModel(globs->ovlistID2, ovarID2, modelID);
            vlistDefVarTable(globs->ovlistID2, ovarID2, tableID);
            if (name[0]) cdiDefKeyString(globs->ovlistID2, ovarID2, CDI_KEY_NAME, name);
            if (longname[0]) cdiDefKeyString(globs->ovlistID2, ovarID2, CDI_KEY_LONGNAME, longname);
            if (units[0]) cdiDefKeyString(globs->ovlistID2, ovarID2, CDI_KEY_UNITS, units);
            vlistDefVarDatatype(globs->ovlistID2, ovarID2, datatype);
            vlistDefVarMissval(globs->ovlistID2, ovarID2, vars[code].missval);
          }
      }

  if (globs->Debug) lprintf(stdout);
  if (globs->Debug)
    for (code = 0; code < MaxCodes; code++)
      if (vars[code].selected)
        {
          const auto gridID = vars[code].ogridID;
          const auto zaxisID = vars[code].ozaxisID;
          zaxisName(zaxisInqType(zaxisID), zaxistypename);
          fprintf(stderr, " Selected Code %3d  grid %-8s size %5zu  level %2d %-8s\n", code, gridNamePtr(gridInqType(gridID)),
                  gridInqSize(gridID), zaxisInqSize(zaxisID), zaxistypename);
        }
}

static void
after_readVct(struct Control *globs, const char *vctfile)
{
  char line[1024];
  int nlines = 0;

  auto fp = fopen(vctfile, "r");
  if (fp == nullptr) cdoSysError("Open failed on %s", vctfile);

  while (fgets(line, 1023, fp))
    {
      if (line[0] == '#' || line[0] == '\0') continue;
      nlines++;
    }

  globs->nvct = nlines * 2;
  globs->vct = (double *) Malloc(globs->nvct * sizeof(double));

  rewind(fp);

  int i = 0;
  while (fgets(line, 1023, fp))
    {
      if (line[0] == '#' || line[0] == '\0') continue;
      int n;
      double va, vb;
      sscanf(line, "%d %lg %lg", &n, &va, &vb);
      globs->vct[i] = va;
      globs->vct[i + globs->nvct / 2] = vb;
      i++;
    }
  fprintf(stdout, "  Read VCT with %d hybrid levels from file %s\n", globs->nvct / 2 - 1, vctfile);

  fclose(fp);
}

static void
after_control_init(struct Control *globs)
{
  globs->AnalysisData = 0; // 0 = ECHAM Data, 1 = ECMWF Spectral Analyses
  globs->DayIn = 0;        // day increment of infiles if Multi = true
  globs->Debug = false;
  globs->Extrapolate = true;
  globs->Szip = false;

  globs->istreamID = CDI_UNDEFID;
  globs->ostreamID = CDO_STREAM_UNDEF;
  globs->ostreamID2 = CDO_STREAM_UNDEF;
  globs->ivlistID = CDI_UNDEFID;
  globs->ovlistID = CDI_UNDEFID;
  globs->ovlistID2 = CDI_UNDEFID;
  globs->taxisID = -1;
  globs->taxisID2 = -1;
}

static void
after_variable_init(struct Variable *vars)
{
  memset(vars, 0, sizeof(struct Variable));

  vars->ivarID = -1;
  vars->ovarID = -1;
  vars->ovarID2 = -1;
  vars->izaxisID = -1;
  vars->ozaxisID = -1;
  vars->igridID = -1;
  vars->ogridID = -1;
  vars->tableID = -1;
}

static void
after_processing(struct Control *globs, struct Variable *vars)
{
  globs->istreamID = streamOpenReadLocked(ifile);

  if (ofiletype == -1) ofiletype = streamInqFiletype(globs->istreamID);

  globs->ivlistID = streamInqVlist(globs->istreamID);
  globs->taxisID = vlistInqTaxis(globs->ivlistID);
  globs->taxisID2 = taxisDuplicate(globs->taxisID);

  if (globs->Mean != 2)
    {
      globs->ostreamID = cdoOpenWrite(ofileidx, ofiletype);

      if (globs->Szip) cdoDefCompType(globs->ostreamID, CDI_COMPRESS_SZIP);

      globs->ovlistID = vlistCreate();
    }

  /* ---------------- */
  /*  pre-processing  */
  /* ---------------- */
  after_precntl(globs, vars);

  /* ----------------- */
  /*  initializations  */
  /* ----------------- */
  after_setCodes(globs, vars, MaxCodes, globs->NumCodesRequest);

  if (globs->unitsel == 2)
    for (int i = 0; i < globs->NumLevelRequest; i++) globs->LevelRequest[i] = globs->LevelRequest[i] * 1000;

  if (!globs->AnalysisData)
    for (int i = 0; i < globs->NumLevelRequest; i++)
      {
        if ((globs->LevelRequest[i] >= 65535) && globs->unitsel && ofiletype == CDI_FILETYPE_GRB)
          {
            fprintf(stderr, "\n Level %9.2f out of range (max=65535)!\n", globs->LevelRequest[i]);
            exit(1);
          }

        if (!globs->unitsel && globs->Type >= 20 && globs->NumLevelRequest > 1 && IS_EQUAL(globs->LevelRequest[i], 0))
          {
            fprintf(stderr, "\n Level %9.2f illegal for Type %d\n", globs->LevelRequest[i], globs->Type);
            exit(1);
          }
      }

  after_setLevel(globs);

  after_dimcalc(globs);

  globs->rcoslat = (double *) Malloc(globs->Latitudes * sizeof(double));
  globs->coslat = (double *) Malloc(globs->Latitudes * sizeof(double));
  globs->DerivationFactor = (double *) Malloc(globs->Latitudes * sizeof(double));

  if (globs->Type < 50 && globs->AnalysisData)
    {
      fprintf(stderr, " ::::::::::::::::::::::::::::::::::::::::::::::\n");
      fprintf(stderr, " -> Type < 50 is not appropriate for Analysis.\n");
      fprintf(stderr, " -> Please check wether you can use Type >= 50.\n");
      fprintf(stderr, " -> Premature Exit. Sorry.\n");
      exit(1);
    }

  if (globs->Type == 10 || globs->Type == 40 || globs->Type == 60)
    {
      if (ofiletype == CDI_FILETYPE_GRB)
        afterAbort("Can't write fourier coefficients to GRIB!");
      else if (ofiletype == CDI_FILETYPE_NC || ofiletype == CDI_FILETYPE_NC2 || ofiletype == CDI_FILETYPE_NC4
               || ofiletype == CDI_FILETYPE_NC4C || ofiletype == CDI_FILETYPE_NC5)
        afterAbort("Can't write fourier coefficients to NetCDF!");
    }

  filename = strrchr(ifile, '/');
  if (filename == 0)
    filename = ifile;
  else
    filename++;

  if (globs->Type >= 30 && globs->Type < 50
      && (vars[DIVERGENCE].selected || vars[VELOPOT].selected || vars[VORTICITY].selected || vars[STREAM].selected
          || globs->AnalysisData))
    {
      if (globs->Type == 30) globs->Type = 70;
      if (globs->Type == 40) globs->Type = 60;
      if (globs->Type == 41) globs->Type = 61;

      if (globs->AnalysisData)
        fprintf(stderr, "\n TYPE changed to %d (for analysis data)\n", globs->Type);
      else
        fprintf(stderr, "\n TYPE changed to %d (with code %d, %d, %d or %d)\n", globs->Type, DIVERGENCE, VELOPOT, VORTICITY,
                STREAM);
    }

  if (globs->AnalysisData)
    after_AnalysisDependencies(vars, MaxCodes);
  else
    {
      after_EchamDependencies(vars, MaxCodes, globs->Type, Source);
      vars[GEOPOTENTIAL].needed |= globs->Type >= 30 || vars[SLP].comp || vars[GEOPOTHEIGHT].comp;
    }

  /*  if ( vars[U_WIND].needed || vars[V_WIND].needed ) */
  if (vars[U_WIND].comp || vars[V_WIND].comp)
    {
      globs->dv2uv_f1 = (double *) Malloc(globs->DimSP_half * sizeof(double));
      globs->dv2uv_f2 = (double *) Malloc(globs->DimSP_half * sizeof(double));
      geninx(globs->Truncation, globs->dv2uv_f1, globs->dv2uv_f2);
    }

  /* --------- */
  /*  Control  */
  /* --------- */

  after_defineLevel(globs, vars);

  after_defineGrid(globs, vars);

  after_postcntl(globs, vars); /* define output variables */

  after_control(globs, vars);

  if (globs->ostreamID != CDO_STREAM_UNDEF) cdoStreamClose(globs->ostreamID);

  processDefVarNum(vlistNvars(globs->ivlistID));

  streamClose(globs->istreamID);

  if (globs->rcoslat) Free(globs->rcoslat);
  if (globs->coslat) Free(globs->coslat);
  if (globs->DerivationFactor) Free(globs->DerivationFactor);

  if (globs->Field) Free(globs->Field);

  if (globs->poli) Free(globs->poli);
  if (globs->pold) Free(globs->pold);
  if (globs->pdev) Free(globs->pdev);
  if (globs->pol2) Free(globs->pol2);
  if (globs->pol3) Free(globs->pol3);
}

extern char *optarg;
extern int optind, opterr, optopt;

void *
Afterburner(void *process)
{
  cdoInitialize(process);

  lstdout = !Options::silentMode;

  struct Control globs = {};
  after_control_init(&globs);

  globs.Verbose = Options::cdoVerbose;

  if (operatorArgc() == 1) after_readVct(&globs, cdoOperatorArgv(0).c_str());

  struct Variable vars[MaxCodes + 5];
  for (int code = 0; code < MaxCodes + 5; code++) after_variable_init(&vars[code]);

  after_parini(&globs, vars); /* read namelist parameter */

  if (CdoDefault::FileType != CDI_UNDEFID) ofiletype = CdoDefault::FileType;

  const auto streamCnt = cdoStreamCnt();
  int nfiles = streamCnt - 1;

  ofileidx = nfiles;

  ifile = strdup(cdoGetStreamName(0));
  ofile = cdoGetStreamName(nfiles);

  globs.Nfiles = nfiles - 1;
  if (globs.Nfiles > 0)
    {
      if (globs.Multi > 0) afterAbort("Namelist parameter MULTI works only with one inputfile");

      ifiles = (const char **) Malloc(globs.Nfiles * sizeof(char *));
      for (int i = 0; i < globs.Nfiles; ++i) ifiles[i] = cdoGetStreamName(--nfiles);
      for (int i = 0; i < globs.Nfiles; ++i) printf("files %d %s\n", i + 1, ifiles[i]);
    }

  after_processing(&globs, vars);

  FreeMean(vars);

  Free(ifile);

  cdoFinish();

  return nullptr;
}
