#include <algorithm>

#include "cdi.h"

#define streamDefRecord cdo_def_record
#define streamWriteRecord cdo_write_record

#include "afterburner.h"
#include "constants.h"
#include "compare.h"
#include "vertical_interp.h"

int afterDebug = 0;

static char *
FieldName(int code, const char *text)
{
  static char name[256];
  snprintf(name, sizeof(name), "[%3d].%s", code, text);
  return name;
}

// Free array space
static void *
FreeMemory(void *ptr)
{
  Free(ptr);
  return nullptr;
}

static void
FreeSpectral(struct Variable *vars)
{
  for (int code = MaxCodes - 1; code >= 0; --code)
    if (vars[code].spectral) vars[code].spectral = (double *) FreeMemory(vars[code].spectral);
}

static void
FreeFourier(struct Variable *vars)
{
  for (int code = 0; code < MaxCodes; ++code)
    if (vars[code].fourier) vars[code].fourier = (double *) FreeMemory(vars[code].fourier);
}

static void
FreeHybrid(struct Variable *vars)
{
  for (int code = 0; code < MaxCodes; ++code)
    if (vars[code].hybrid) vars[code].hybrid = (double *) FreeMemory(vars[code].hybrid);
}

static void
FreeGrid(struct Variable *vars)
{
  for (int code = 0; code < MaxCodes; ++code)
    if (vars[code].grid) vars[code].grid = (double *) FreeMemory(vars[code].grid);
}

static void
FreeSamp(struct Variable *vars)
{
  for (int code = 0; code < MaxCodes; ++code)
    if (vars[code].samp) vars[code].samp = (int *) FreeMemory(vars[code].samp);
}

// alloc_dp - Allocate space for double array
static double *
alloc_dp(int words, const char *array_name)
{
  double *result = nullptr;

  if (words > 0)
    {
      result = (double *) Malloc(words * sizeof(double));
      if (result == nullptr) cdo_sys_error(array_name, "No Memory!");
    }

  return result;
}

static void
IniQuaSum(double *dest, const double *restrict src, int len)
{
  for (int i = 0; i < len; ++i) dest[i] = src[i] * src[i];
}

static void
AddQuaSum(double *dest, const double *restrict src, int len)
{
  for (int i = 0; i < len; ++i) dest[i] += src[i] * src[i];
}

static void
VarQuaSum(double *Variance, const double *restrict Sum, int len, int n)
{
  const double rn1 = 1.0 / (n - 1);

  for (int i = 0; i < len; ++i) Variance[i] = (Variance[i] - Sum[i] * Sum[i] * n) * rn1;

  for (int i = 0; i < len; ++i) Variance[i] = (Variance[i] > 0.0) ? std::sqrt(Variance[i]) : 0.0;
}

static void
AddVector(double *dest, const double *src, size_t len, size_t *nmiss, double missval)
{
  if (*nmiss)
    {
      array_add_array_mv(len, dest, src, missval);
      *nmiss = array_num_mv(len, dest, missval);
      if (*nmiss == 0) *nmiss = 1;
    }
  else
    {
      array_add_array(len, dest, src);
    }
}

static void
Add2Vectors(double *dest, const double *restrict srcA, const double *restrict srcB, size_t len)
{
  for (size_t i = 0; i < len; ++i) dest[i] = srcA[i] + srcB[i];
}

static void
Sub2Vectors(double *dest, const double *restrict srcA, const double *restrict srcB, size_t len)
{
  for (size_t i = 0; i < len; ++i) dest[i] = srcA[i] - srcB[i];
}

static void
MultVectorScalar(double *dest, const double *restrict src, double factor, size_t len, size_t nmiss, double missval)
{
  if (nmiss)
    {
      for (size_t i = 0; i < len; ++i) dest[i] = (IS_EQUAL(src[i], missval)) ? missval : src[i] * factor;
    }
  else
    {
      for (size_t i = 0; i < len; ++i) dest[i] = src[i] * factor;
    }
}

static void
DivVectorIvector(double *dest, const double *restrict src, const int *samp, size_t len, size_t *nmiss, double missval)
{
  *nmiss = 0;

  for (size_t i = 0; i < len; ++i)
    {
      if (IS_EQUAL(src[i], missval) || samp[i] == 0)
        {
          dest[i] = missval;
          *nmiss = *nmiss + 1;
        }
      else
        dest[i] = src[i] / samp[i];
    }
}

void
after_gp2sp(const AfterControl &globs, struct Variable *vars, int ccode)
{
  auto *var = &vars[ccode];

  if (var->spectral == nullptr)
    {
      if (var->hybrid == nullptr)
        {
          fprintf(stderr, "%d.hybrid not found\n", ccode);
          exit(99);
        }

      if (var->fourier == nullptr)
        {
          const auto fieldSize = globs.DimFC * var->hlev;
          var->fourier = alloc_dp(fieldSize, "gp2sp.fourier");
          after_GP2FC(var->hybrid, var->fourier, globs.Latitudes, globs.Longitudes, var->hlev, globs.Fouriers);
        }

      var->spectral = alloc_dp(globs.Dim3SP, "gp2sp.spectral");
      fc2sp(var->fourier, var->spectral, globs.pold, var->hlev, globs.Latitudes, globs.Fouriers, globs.Truncation);
    }
}

void
after_GP2FC(double *gp, double *fc, long nlat, long nlon, long nlev, long nfc)
{
  static long ifax[10];
  static double *trig = nullptr;

  if (ifax[9] != nlon)
    {
      if (trig) Free(trig);
      trig = (double *) Malloc(nlon * sizeof(double));
      int status = fft_set(trig, ifax, nlon);
      if (status < 0) exit(1);
    }

  gp2fc(trig, ifax, gp, fc, nlat, nlon, nlev, nfc);
}

void
after_FC2GP(double *fc, double *gp, long nlat, long nlon, long nlev, long nfc)
{
  static long ifax[10];
  static double *trig = nullptr;

  if (ifax[9] != nlon)
    {
      if (trig) Free(trig);
      trig = (double *) Malloc(nlon * sizeof(double));
      int status = fft_set(trig, ifax, nlon);
      if (status < 0) exit(1);
    }

  fc2gp(trig, ifax, fc, gp, nlat, nlon, nlev, nfc);
}

// HUMTEST

static void
sh2rh(int AnalysisData, double *sphum, double *rhum, double *t, int lev, int dimgpout, const double *level, double *fullpresshybrid)
{
  int lpi, lfp;
  double es, qsatr;

  /* ***************************************************** */
  /* Define constants for calculation in presence of water */
  /* ***************************************************** */
  constexpr double RGAMW = (C_RCW - C_RCPV) / C_RV;
  constexpr double RBETW = C_RLVTT / C_RV + RGAMW * C_RTT;
  const double RALPW = std::log(C_RESTT) + RBETW / C_RTT + RGAMW * std::log(C_RTT);

  /* ***************************************************** */
  /* Define constants for calculation in presence of  ice  */
  /* ***************************************************** */
  /*
  const double RGAMS = (C_RCS - C_RCPV) / C_RV;
  const double RBETS = C_RLSTT / C_RV + RGAMS * C_RTT;
  const double RALPS = std::log(C_RESTT) + RBETS / C_RTT + RGAMS * std::log(C_RTT);
  */
  const double *fullp = AnalysisData ? level : fullpresshybrid;

  /***************************************************/
  /* Diagnostics of saturation water vapour pressure */
  /* over ice makes no sense, therefore ...          */
  /* Hint of Michael Ponater                08.10.97 */
  /***************************************************/
  constexpr double RGAM = RGAMW;
  constexpr double RBET = RBETW;
  const double RALP = RALPW;
  for (int lp = 0; lp < lev; lp++)
    {
      for (int i = 0; i < dimgpout; ++i)
        {
          lpi = lp * dimgpout + i;
          lfp = (1 - AnalysisData) * lpi + AnalysisData * lp;
          /*
          if (t[lpi] < C_RTT) {
            RGAM = RGAMS; RBET = RBETS; RALP = RALPS;
          } else {
            RGAM = RGAMW; RBET = RBETW; RALP = RALPW;
          }
          */
          es = (std::exp(RALP - RBET / t[lpi] - RGAM * std::log(t[lpi]))) / fullp[lfp];
          // qsat = es / (1. + C_RETV * (1. - es));
          qsatr = (1. + C_RETV * (1. - es)) / es;
          rhum[lpi] = sphum[lpi] * 100. * qsatr;
        }
    }
}

static void
rh2sh(double *sphum, double *rhum, double *t, int lev, int dimgpout, const double *level)
{
  int lpi;
  double es, qsat;

  /* ***************************************************** */
  /* Define constants for calculation in presence of water */
  /* ***************************************************** */
  constexpr double RGAMW = (C_RCW - C_RCPV) / C_RV;
  constexpr double RBETW = C_RLVTT / C_RV + RGAMW * C_RTT;
  const double RALPW = std::log(C_RESTT) + RBETW / C_RTT + RGAMW * std::log(C_RTT);

  /* ***************************************************** */
  /* Define constants for calculation in presence of  ice  */
  /* ***************************************************** */
  // const double RGAMS = (C_RCS - C_RCPV) / C_RV;
  // const double RBETS = C_RLSTT / C_RV + RGAMS * C_RTT;
  // const double RALPS = std::log(C_RESTT) + RBETS / C_RTT + RGAMS * std::log(C_RTT);

  /***************************************************/
  /* Diagnostics of saturation water vapour pressure */
  /* over ice makes no sense, therefore ...          */
  /* Hint of Michael Ponater                08.10.97 */
  /***************************************************/

  constexpr double RGAM = RGAMW;
  constexpr double RBET = RBETW;
  const double RALP = RALPW;
  for (int lp = 0; lp < lev; lp++)
    {
      for (int i = 0; i < dimgpout; ++i)
        {
          lpi = lp * dimgpout + i;
          /*       if (t[lpi] < C_RTT) { */
          /*          RGAM = RGAMS; RBET = RBETS; RALP = RALPS; */
          /*       }  else { */
          /*          RGAM = RGAMW; RBET = RBETW; RALP = RALPW; */
          /*       } */
          es = (std::exp(RALP - RBET / t[lpi] - RGAM * std::log(t[lpi]))) / level[lp];
          qsat = es / (1. + C_RETV * (1. - es));
          sphum[lpi] = rhum[lpi] * qsat / 100.;
        }
    }
}

void
after_FCrh2FCsh(const AfterControl &globs, struct Variable *vars)
{
  const auto fieldSize = globs.DimGP * globs.NumLevelRequest;

  if (vars[RHUMIDITY].grid == nullptr) vars[RHUMIDITY].grid = alloc_dp(fieldSize, "vars[RHUMIDITY].grid");
  if (vars[TEMPERATURE].grid == nullptr) vars[TEMPERATURE].grid = alloc_dp(fieldSize, "vars[TEMPERATURE].grid");
  if (vars[HUMIDITY].grid == nullptr) vars[HUMIDITY].grid = alloc_dp(fieldSize, "vars[HUMIDITY].grid");

  after_FC2GP(vars[RHUMIDITY].fourier, vars[RHUMIDITY].grid, globs.Latitudes, globs.Longitudes, vars[RHUMIDITY].plev,
              globs.Fouriers);
  after_FC2GP(vars[TEMPERATURE].fourier, vars[TEMPERATURE].grid, globs.Latitudes, globs.Longitudes, vars[TEMPERATURE].plev,
              globs.Fouriers);

  rh2sh(vars[HUMIDITY].grid, vars[RHUMIDITY].grid, vars[TEMPERATURE].grid, globs.NumLevelRequest, globs.DimGP, globs.LevelRequest);

  after_GP2FC(vars[HUMIDITY].grid, vars[HUMIDITY].fourier, globs.Latitudes, globs.Longitudes, vars[HUMIDITY].plev, globs.Fouriers);

  vars[HUMIDITY].grid = (double *) FreeMemory(vars[HUMIDITY].grid);
  vars[RHUMIDITY].grid = (double *) FreeMemory(vars[RHUMIDITY].grid);
  vars[TEMPERATURE].grid = (double *) FreeMemory(vars[TEMPERATURE].grid);
}

void
after_SPuv2SPdv(const AfterControl &globs, struct Variable *vars)
{
  double *Div, *DivOut, *Vor, *VorOut;

  Div = DivOut = vars[DIVERGENCE].spectral;
  Vor = VorOut = vars[VORTICITY].spectral;
  const auto fieldSize = globs.DimFC * globs.NumLevelRequest;

  if (vars[U_WIND].fourier == nullptr) vars[U_WIND].fourier = alloc_dp(fieldSize, "vars[U_WIND].fourier");
  if (vars[V_WIND].fourier == nullptr) vars[V_WIND].fourier = alloc_dp(fieldSize, "vars[V_WIND].fourier");

  sp2fc(vars[U_WIND].spectral, vars[U_WIND].fourier, globs.poli, globs.NumLevelRequest, globs.Latitudes, globs.Fouriers,
        globs.Truncation);
  sp2fc(vars[V_WIND].spectral, vars[V_WIND].fourier, globs.poli, globs.NumLevelRequest, globs.Latitudes, globs.Fouriers,
        globs.Truncation);
  uv2dv(vars[U_WIND].fourier, vars[V_WIND].fourier, Div, Vor, globs.pol2, globs.pol3, globs.NumLevelRequest, globs.Latitudes,
        globs.Truncation);

  vars[U_WIND].fourier = (double *) FreeMemory(vars[U_WIND].fourier);
  vars[V_WIND].fourier = (double *) FreeMemory(vars[V_WIND].fourier);

  for (int i = 0; i < globs.NumLevelRequest; ++i)
    {
      sp2sp(Div, globs.Truncation, DivOut, globs.Truncation);
      sp2sp(Vor, globs.Truncation, VorOut, globs.Truncation);
      Div += globs.DimSP;
      Vor += globs.DimSP;
      DivOut += globs.DimSP;
      VorOut += globs.DimSP;
    }
}

void
after_FCsh2FCrh(const AfterControl &globs, struct Variable *vars)
{
  const auto fieldSize = globs.DimGP * globs.NumLevelRequest;

  if (vars[RHUMIDITY].grid == nullptr) vars[RHUMIDITY].grid = alloc_dp(fieldSize, "vars[RHUMIDITY].grid");
  if (vars[TEMPERATURE].grid == nullptr) vars[TEMPERATURE].grid = alloc_dp(fieldSize, "vars[TEMPERATURE].grid");
  if (vars[HUMIDITY].grid == nullptr) vars[HUMIDITY].grid = alloc_dp(fieldSize, "vars[HUMIDITY].grid");

  after_FC2GP(vars[HUMIDITY].fourier, vars[HUMIDITY].grid, globs.Latitudes, globs.Longitudes, vars[HUMIDITY].plev, globs.Fouriers);
  after_FC2GP(vars[TEMPERATURE].fourier, vars[TEMPERATURE].grid, globs.Latitudes, globs.Longitudes, vars[TEMPERATURE].plev,
              globs.Fouriers);

  sh2rh(globs.AnalysisData, vars[HUMIDITY].grid, vars[RHUMIDITY].grid, vars[TEMPERATURE].grid, globs.NumLevelRequest, globs.DimGP,
        globs.LevelRequest, vars[FULL_PRESS].hybrid);

  after_GP2FC(vars[RHUMIDITY].grid, vars[RHUMIDITY].fourier, globs.Latitudes, globs.Longitudes, vars[RHUMIDITY].plev,
              globs.Fouriers);

  vars[HUMIDITY].grid = (double *) FreeMemory(vars[HUMIDITY].grid);
  vars[RHUMIDITY].grid = (double *) FreeMemory(vars[RHUMIDITY].grid);
  vars[TEMPERATURE].grid = (double *) FreeMemory(vars[TEMPERATURE].grid);
}
/* ENDE HUMTEST */

static void
CheckAnalyses(struct Variable *vars)
{
  for (int code = 0; code < 272; ++code)
    if (vars[code].needed && code != DIVERGENCE && code != VORTICITY && code != STREAM && code != U_WIND && code != HUMIDITY
        && code != VELOPOT && code != V_WIND && code != RHUMIDITY && code != GEOPOTHEIGHT && code != PS
        && vars[code].spectral == nullptr && vars[code].grid == nullptr)
      {
        afterAbort("Code  %3d not found", code);
      }
}

// Process Pressure Level data
void
after_processPL(AfterControl &globs, struct Variable *vars)
{
  globs.MeanCount++;
  globs.TermCount++;

  if (globs.MeanCount == 1)
    {
      if (globs.Debug) fprintf(stderr, "CheckAnalyses: %d %d\n", globs.TermCount, globs.MeanCount);
      CheckAnalyses(vars);
      globs.StartDate = globs.OldDate;
    }
  if (globs.TermCount > 120) globs.Debug = 0;

  /* ============================== */
  /* Computations in spectral space */
  /* ============================== */

  if (vars[TEMPERATURE].needed)
    {
      vars[TEMPERATURE].hlev = 2;
      vars[TEMPERATURE].plev = globs.NumLevelRequest;
      vars[TEMPERATURE].sfit = true;
    }

  if (vars[GEOPOTHEIGHT].comp)
    {
      vars[GEOPOTHEIGHT].hlev = 2;
      vars[GEOPOTHEIGHT].plev = globs.NumLevelRequest;
      vars[GEOPOTHEIGHT].sfit = true;
    }

  if (vars[GEOPOTHEIGHT].comp && vars[GEOPOTENTIAL].detected)
    {
      if (vars[GEOPOTHEIGHT].spectral == nullptr)
        vars[GEOPOTHEIGHT].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "GEOPOTHEIGHT.spectral");
      MultVectorScalar(vars[GEOPOTHEIGHT].spectral, vars[GEOPOTENTIAL].spectral, C_RG, globs.DimSP * globs.NumLevelRequest, 0, 0);
      vars[GEOPOTENTIAL].needed = vars[GEOPOTENTIAL].selected;
    }

  if (globs.Type == 50 && vars[HUMIDITY].needed && vars[HUMIDITY].spectral == nullptr)
    {
      vars[HUMIDITY].plev = globs.NumLevelRequest;
      vars[HUMIDITY].sfit = true;
      vars[HUMIDITY].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[HUMIDITY].spectral");
      // SPrh2SPsh();
      vars[RHUMIDITY].needed = vars[RHUMIDITY].selected;
      vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
    }

  if (vars[U_WIND].spectral && vars[V_WIND].spectral && (vars[DIVERGENCE].comp || vars[VORTICITY].comp))
    {
      vars[DIVERGENCE].hlev = vars[VORTICITY].hlev = 2;
      vars[DIVERGENCE].plev = vars[VORTICITY].plev = globs.NumLevelRequest;
      vars[DIVERGENCE].sfit = vars[VORTICITY].sfit = true;
      if (vars[DIVERGENCE].spectral == nullptr)
        vars[DIVERGENCE].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[DIVERGENCE].spectral");
      if (vars[VORTICITY].spectral == nullptr)
        vars[VORTICITY].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[VORTICITY].spectral");
      after_SPuv2SPdv(globs, vars);
    }

  if (vars[U_WIND].comp || vars[V_WIND].comp)
    {
      vars[U_WIND].hlev = vars[V_WIND].hlev = 2;
      vars[U_WIND].plev = vars[V_WIND].plev = globs.NumLevelRequest;
      vars[U_WIND].sfit = vars[V_WIND].sfit = true;
      if (vars[U_WIND].spectral == nullptr)
        vars[U_WIND].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[U_WIND].spectral");
      if (vars[V_WIND].spectral == nullptr)
        vars[V_WIND].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[V_WIND].spectral");
      dv2uv(vars[DIVERGENCE].spectral, vars[VORTICITY].spectral, vars[U_WIND].spectral, vars[V_WIND].spectral, globs.dv2uv_f1,
            globs.dv2uv_f2, globs.Truncation, globs.DimSP, globs.NumLevelRequest);
    }

  if (vars[VELOPOT].comp)
    {
      vars[VELOPOT].hlev = 2;
      vars[VELOPOT].plev = globs.NumLevelRequest;
      vars[VELOPOT].sfit = true;
      if (vars[VELOPOT].spectral == nullptr)
        vars[VELOPOT].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[VELOPOT].spectral");
      dv2ps(vars[DIVERGENCE].spectral, vars[VELOPOT].spectral, globs.NumLevelRequest, globs.Truncation);
    }

  if (vars[STREAM].comp)
    {
      vars[STREAM].hlev = 2;
      vars[STREAM].plev = globs.NumLevelRequest;
      vars[STREAM].sfit = true;
      if (vars[STREAM].spectral == nullptr)
        vars[STREAM].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[STREAM].spectral");
      dv2ps(vars[VORTICITY].spectral, vars[STREAM].spectral, globs.NumLevelRequest, globs.Truncation);
    }

  /* --------------------------- */
  /*  Output of spectral fields  */
  /* --------------------------- */

  if (globs.Type == 50)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].spectral) afterAbort("Code %d not available on spectral space!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimSP;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].spectral + offset, 0);
              }
          }

      FreeSpectral(vars);
      return;
    }

  /* =============================== */
  /* Transformation to fourier space */
  /* Computations in fourier space   */
  /* =============================== */

  if (globs.Type >= 60)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].spectral)
          {
            if (vars[code].fourier == nullptr)
              {
                const auto fieldSize = vars[code].plev * globs.DimFC;
                vars[code].fourier = alloc_dp(fieldSize, FieldName(code, "fourier"));
              }
            sp2fc(vars[code].spectral, vars[code].fourier, globs.poli, vars[code].plev, globs.Latitudes, globs.Fouriers,
                  globs.Truncation);
          }
      if (vars[U_WIND].needed && vars[U_WIND].fourier)
        scaluv(vars[U_WIND].fourier, globs.rcoslat, globs.Latitudes, globs.Fouriers * globs.NumLevelRequest);
      if (vars[V_WIND].needed && vars[V_WIND].fourier)
        scaluv(vars[V_WIND].fourier, globs.rcoslat, globs.Latitudes, globs.Fouriers * globs.NumLevelRequest);

      /* HUMTEST */
      if (globs.Type < 70 && vars[HUMIDITY].needed && vars[HUMIDITY].fourier == nullptr)
        {
          vars[HUMIDITY].plev = globs.NumLevelRequest;
          vars[HUMIDITY].sfit = true;
          vars[HUMIDITY].fourier = alloc_dp(globs.DimFC * globs.NumLevelRequest, "vars[HUMIDITY].fourier");

          after_FCrh2FCsh(globs, vars);

          vars[RHUMIDITY].needed = vars[RHUMIDITY].selected;
          vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
        }

      if (globs.Type < 70 && vars[RHUMIDITY].needed && vars[RHUMIDITY].fourier == nullptr)
        {
          vars[RHUMIDITY].plev = globs.NumLevelRequest;
          vars[RHUMIDITY].sfit = true;
          vars[RHUMIDITY].fourier = alloc_dp(globs.DimFC * globs.NumLevelRequest, "vars[RHUMIDITY].fourier");

          after_FCsh2FCrh(globs, vars);

          vars[HUMIDITY].needed = vars[HUMIDITY].selected;
          vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
        }
      /* ENDE HUMTEST */
    }

  FreeSpectral(vars);

  /* -------------------------- */
  /*  Output of fourier fields  */
  /* -------------------------- */

  if (globs.Type == 60)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on fourier space!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, 0);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ----------------------- */
  /*  Output of zonal means  */
  /* ----------------------- */

  if (globs.Type == 61)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on zonal mean!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, 0);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ============================ */
  /* Transformation to gridpoints */
  /* ============================ */

  if (vars[PS].comp && vars[LNPS].grid)
    {
      if (vars[PS].grid == nullptr) vars[PS].grid = alloc_dp(globs.DimGP, "Ps");
      for (int l = 0; l < globs.DimGP; ++l) vars[PS].grid[l] = std::exp(vars[LNPS].grid[l]);
    }

  if (globs.Type >= 70)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].fourier)
          {
            if (vars[code].grid == nullptr)
              {
                const auto fieldSize = vars[code].plev * globs.DimGP;
                vars[code].grid = alloc_dp(fieldSize, FieldName(code, "grid"));
              }

            after_FC2GP(vars[code].fourier, vars[code].grid, globs.Latitudes, globs.Longitudes, vars[code].plev, globs.Fouriers);
          }
    }

  FreeFourier(vars);

  /* HUMTEST */
  /* -------------------------------- */
  /*  Computation in gridpoint space  */
  /* -------------------------------- */

  if (vars[RHUMIDITY].needed && vars[RHUMIDITY].grid == nullptr)
    {
      vars[RHUMIDITY].plev = globs.NumLevelRequest;
      vars[RHUMIDITY].sfit = true;
      vars[RHUMIDITY].grid = alloc_dp(globs.DimGP * globs.NumLevelRequest, "vars[RHUMIDITY].grid");
      sh2rh(globs.AnalysisData, vars[HUMIDITY].grid, vars[RHUMIDITY].grid, vars[TEMPERATURE].grid, globs.NumLevelRequest,
            globs.DimGP, globs.LevelRequest, vars[FULL_PRESS].hybrid);
      vars[HUMIDITY].needed = vars[HUMIDITY].selected;
      vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
    }

  if (vars[HUMIDITY].needed && vars[HUMIDITY].grid == nullptr)
    {
      vars[HUMIDITY].plev = globs.NumLevelRequest;
      vars[HUMIDITY].sfit = true;
      vars[HUMIDITY].grid = alloc_dp(globs.DimGP * globs.NumLevelRequest, "vars[HUMIDITY].grid");
      rh2sh(vars[HUMIDITY].grid, vars[RHUMIDITY].grid, vars[TEMPERATURE].grid, globs.NumLevelRequest, globs.DimGP,
            globs.LevelRequest);
      vars[RHUMIDITY].needed = vars[RHUMIDITY].selected;
      vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
    }
  /* HUMTEST ENDE */

  /* -------------------------- */
  /*  Computation of Means      */
  /* -------------------------- */

  if (globs.Mean)
    for (int code = 0; code < MaxCodes; ++code)
      if (vars[code].needed && vars[code].grid)
        {
          const auto fieldSize = globs.DimGP * vars[code].plev;
          if (vars[code].mean == nullptr) vars[code].mean = alloc_dp(fieldSize, FieldName(code, "mean"));

          if (globs.MeanCount == 1)
            array_copy(fieldSize, vars[code].grid, vars[code].mean);
          else
            AddVector(vars[code].mean, vars[code].grid, fieldSize, &vars[code].nmiss, vars[code].missval);

          if (globs.EndOfInterval)
            {
              if (vars[code].samp == nullptr)
                MultVectorScalar(vars[code].mean, vars[code].mean, 1.0 / globs.MeanCount, fieldSize, vars[code].nmiss,
                                 vars[code].missval);
              else
                DivVectorIvector(vars[code].mean, vars[code].mean, vars[code].samp, fieldSize, &vars[code].nmiss,
                                 vars[code].missval);
            }
        }

  /* -------------------------- */
  /*  Computation of Variances  */
  /* -------------------------- */

  if (globs.Mean > 1)
    for (int code = 0; code < MaxCodes; ++code)
      if (vars[code].needed && vars[code].mean)
        {
          const auto fieldSize = globs.DimGP * vars[code].plev;
          if (vars[code].variance == nullptr) vars[code].variance = alloc_dp(fieldSize, FieldName(code, "var"));
          if (globs.MeanCount == 1)
            IniQuaSum(vars[code].variance, vars[code].grid, fieldSize);
          else
            AddQuaSum(vars[code].variance, vars[code].grid, fieldSize);

          if (globs.EndOfInterval) VarQuaSum(vars[code].variance, vars[code].mean, fieldSize, globs.MeanCount);
        }

  if (globs.Mean && !globs.EndOfInterval)
    {
      FreeGrid(vars);
      return;
    }

  /* ---------------------------------------------- */
  /*  Output of pressure level means and variances  */
  /* ---------------------------------------------- */

  if (globs.Type == 70 && globs.Mean && globs.EndOfInterval)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimGP;
                if (globs.Mean != 2)
                  {
                    streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                    streamWriteRecord(globs.ostreamID, vars[code].mean + offset, vars[code].nmiss);
                  }
                if (globs.Mean >= 2)
                  {
                    streamDefRecord(globs.ostreamID2, vars[code].ovarID2, k);
                    streamWriteRecord(globs.ostreamID2, vars[code].variance + offset, vars[code].nmiss);
                  }
              }
          }

      FreeSamp(vars);
      FreeGrid(vars);
      return;
    }

  /* -------------------------------- */
  /*  Output of pressure level grids  */
  /* -------------------------------- */

  if (globs.Type == 70)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimGP;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].grid + offset, vars[code].nmiss);
              }
          }

      FreeGrid(vars);
      return;
    }
}

static void
theta(double *pthetaf, double *pthetah, double *ph, double *ps, double *tf, double *ts, int levels, int dimgp, int dim3gp)
{
  double *thetah = pthetah;
  double *thetaf = pthetaf;

  const double kappa = PlanetRD / C_RCPD;

  for (int h = 0; h < dimgp; h++) thetah[h] = 0.0;
  thetah += dimgp;
  for (int l = 0; l < levels - 1; ++l)
    {
      for (int h = 0; h < dimgp; h++) thetah[h] = 0.5 * (tf[h] + tf[h + dimgp]) * std::pow((ps[h] / ph[h]), kappa);

      ph += dimgp;
      tf += dimgp;
      thetah += dimgp;
    }

  array_copy(dimgp, ts, thetah);
  thetah = pthetah;
  for (int h = 0; h < dim3gp; h++) thetaf[h] = 0.5 * (thetah[h] + thetah[h + dimgp]);
}

static void
windSpeed(double *uvspeed, double *u, double *v, int dim3gp)
{
  for (int i = 0; i < dim3gp; ++i) uvspeed[i] = std::sqrt(u[i] * u[i] + v[i] * v[i]);
}

static void
Omega(double *omega_in, double *divergence, double *u_wind, double *v_wind, double *halfpress, double *fullpress, double *dpsdx,
      double *dpsdy, double *vct, int dimgp, int nlev)
{
  double DeltaHybrid, Cterm, Pterm;
  double *diver, *halfp, *fullp, *uwind, *vwind;
  double *omega = omega_in;

  // Compute half level part of vertical velocity

  for (int i = 0; i < dimgp; ++i) omega[i] = 0.0;

  for (int j = 0; j < nlev; ++j)
    {
      omega = omega_in + j * dimgp;
      halfp = halfpress + j * dimgp;
      diver = divergence + j * dimgp;
      uwind = u_wind + j * dimgp;
      vwind = v_wind + j * dimgp;

      DeltaHybrid = vct[nlev + j + 2] - vct[nlev + j + 1];
      for (int i = 0; i < dimgp; ++i)
        {
          omega[i + dimgp]
              = omega[i] - diver[i] * (halfp[i + dimgp] - halfp[i]) - DeltaHybrid * (uwind[i] * dpsdx[i] + vwind[i] * dpsdy[i]);
        }
    }

  /* interpolate to full levels  */

  for (int j = 0; j < nlev; ++j)
    {
      omega = omega_in + j * dimgp;
      for (int i = 0; i < dimgp; ++i) omega[i] = 0.5 * (omega[i] + omega[i + dimgp]);
    }

    /* compute full level part of vertical velocity */

#ifdef _OPENMP
#pragma omp parallel for default(shared) private(omega, halfp, fullp, uwind, vwind, DeltaHybrid, Cterm, Pterm)
#endif
  for (int j = 0; j < nlev; ++j)
    {
      omega = omega_in + j * dimgp;
      halfp = halfpress + j * dimgp;
      fullp = fullpress + j * dimgp;
      uwind = u_wind + j * dimgp;
      vwind = v_wind + j * dimgp;

      DeltaHybrid = vct[nlev + j + 2] - vct[nlev + j + 1];
      if (std::fabs(DeltaHybrid) > 0)
        {
          Cterm = vct[j + 1] * vct[nlev + j + 1] - vct[j] * vct[nlev + j + 2];
          for (int i = 0; i < dimgp; ++i)
            {
              if (IS_NOT_EQUAL(Cterm, 0.0))
                Pterm = Cterm / (halfp[i + dimgp] - halfp[i]) * std::log(halfp[i + dimgp] / halfp[i]);
              else
                Pterm = 0.0;

              omega[i]
                  += fullp[i] * (uwind[i] * dpsdx[i] + vwind[i] * dpsdy[i]) / (halfp[i + dimgp] - halfp[i]) * (DeltaHybrid + Pterm);
            }
        }
    }
}

void
geopot_height_halflevel(double *gheight, const double *const ta_fl, const double *const hus_fl, const double *const p_hl, const long ngp, const long nlev)
{
  // Computes geopotential height on half level

  // gheight: geopotential height (nlev+1) (bottom level contains surface geopotential)
  // ta_fl:   air temperature on full levels
  // hus_fl:  specific humutity on full levels
  // p_hl:    pressure on half levels

  const double z2log2 = 2.0 * std::log(2.0);
  const double vtmp = (C_RV / PlanetRD) - 1.0;

  if (hus_fl) // Humidity is present
    {
      for (long k = nlev; k > 1; k--)
        {
          long offset = ngp * (k - 1);
          double *gh = gheight + offset;
          const double *const p   = p_hl   + offset;
          const double *const ta  = ta_fl  + offset;
          const double *const hus = hus_fl + offset;
          for (long i = 0; i < ngp; ++i)
            gh[i] = gh[i + ngp] + PlanetRD * ta[i] * (1.0 + vtmp * hus[i]) * std::log(p[i + ngp] / p[i]);
        }

      // top level
      for (long i = 0; i < ngp; ++i) gheight[i] = gheight[i + ngp] + PlanetRD * ta_fl[i] * (1.0 + vtmp * hus_fl[i]) * z2log2;
    }
  else  // No humidity
    {
      for (long k = nlev; k > 1; k--)
        {
          long offset = ngp * (k - 1);
          double *gh = gheight + offset;
          const double *const p   = p_hl   + offset;
          const double *const ta  = ta_fl  + offset;
          for (long i = 0; i < ngp; ++i)
            gh[i] = gh[i + ngp] + PlanetRD * ta[i] * std::log(p[i + ngp] / p[i]);
        }

      // top level
      for (long i = 0; i < ngp; ++i) gheight[i] = gheight[i + ngp] + PlanetRD * ta_fl[i] * z2log2;
    }

  const double zrg = 1.0 / PlanetGrav;
  for (long i = 0; i < ngp * (nlev + 1); ++i) gheight[i] *= zrg;
}

void
geopot_height_fulllevel(double *gheight, const double *const ta_fl, const double *const hus_fl, const double *const p_hl, const long ngp, const long nlev)
{
  // Computes geopotential height on full level

  // gheight: geopotential height (nlev+1) (bottom level contains surface geopotential)
  // ta_fl:   air temperature on full levels
  // hus_fl:  specific humutity on full levels
  // p_hl:    pressure on half levels

  const double zlog2 = std::log(2.0);
  const double vtmp = (C_RV / PlanetRD) - 1.0;

  if (hus_fl) // Humidity is present
    {
      for (long k = nlev; k > 1; k--)
        {
          long offset = ngp * (k - 1);
          double *gh = gheight + offset;
          const double *const p   = p_hl   + offset;
          const double *const ta  = ta_fl  + offset;
          const double *const hus = hus_fl + offset;
          for (long i = 0; i < ngp; ++i)
            gh[i] = gh[i + ngp] + PlanetRD * ta[i] * (1.0 + vtmp * hus[i]) * std::log(p[i + ngp] / p[i]);
        }

      // top level
      for (long i = 0; i < ngp; ++i) gheight[i] = gheight[i + ngp] + PlanetRD * ta_fl[i] * (1.0 + vtmp * hus_fl[i]) * zlog2;

      for (long k = 1; k < nlev; ++k)
        {
          long offset = ngp * k;
          double *gh = gheight + offset;
          const double *const p   = p_hl   + offset;
          const double *const ta  = ta_fl  + offset;
          const double *const hus = hus_fl + offset;
          for (long i = 0; i < ngp; ++i)
            gh[i] = gh[i + ngp] + PlanetRD * ta[i] * (1.0 + vtmp * hus[i]) * (1.0 - (p[i] / (p[i + ngp] - p[i])) * std::log(p[i + ngp] / p[i]));
        }
    }
  else  // No humidity
    {
      for (long k = nlev; k > 1; k--)
        {
          long offset = ngp * (k - 1);
          double *gh = gheight + offset;
          const double *const p   = p_hl   + offset;
          const double *const ta  = ta_fl  + offset;
          for (long i = 0; i < ngp; ++i)
            gh[i] = gh[i + ngp] + PlanetRD * ta[i] * std::log(p[i + ngp] / p[i]);
        }

      // top level
      for (long i = 0; i < ngp; ++i) gheight[i] = gheight[i + ngp] + PlanetRD * ta_fl[i] * zlog2;

      for (long k = 1; k < nlev; ++k)
        {
          long offset = ngp * k;
          double *gh = gheight + offset;
          const double *const p   = p_hl   + offset;
          const double *const ta  = ta_fl  + offset;
          for (long i = 0; i < ngp; ++i)
            gh[i] = gh[i + ngp] + PlanetRD * ta[i] * (1.0 - (p[i] / (p[i + ngp] - p[i])) * std::log(p[i + ngp] / p[i]));
        }
    }

  const double zrg = 1.0 / PlanetGrav;
  for (long i = 0; i < ngp * (nlev + 1); ++i) gheight[i] *= zrg;
}

constexpr double SCALESLP = 101325.0;

/* ======================================== */
/* LayerWater integral liquid water content */
/* ======================================== */

void
LayerWater(double *ww, double *ll, double pmax, double pmin, int DimGP, int HalfLevels, double *vct)
{
  double pph[MaxLevel];

  int k;
  for (k = 0; k < HalfLevels; ++k) pph[k] = vct[k] + vct[k + HalfLevels] * SCALESLP;
  for (k = 0; k < HalfLevels; ++k)
    if (pph[k] > pmax) break;
  const auto MaxLev = k - 1;
  for (k = HalfLevels - 1; k >= 0; k--)
    if (pph[k] < pmin) break;
  const auto MinLev = k;

  varray_fill(DimGP, ll, 0.0);

  for (k = MaxLev; k <= MinLev; ++k)
    {
      for (int i = 0; i < DimGP; ++i) ll[i] += ww[i + k * DimGP] * (pph[k + 1] - pph[k]);
    }
  for (int i = 0; i < DimGP; ++i) ll[i] /= PlanetGrav;
}

/* ================================================= */
/* LayerCloud calculates random overlap cloud cover */
/* ================================================= */

void
LayerCloud(double *cc, double *ll, double pmax, double pmin, int DimGP, int HalfLevels, double *vct)
{
  double pph[MaxLevel];
  constexpr double ZEPSEC = 1.0e-12;

  int k;
  for (k = 0; k < HalfLevels; ++k) pph[k] = vct[k] + vct[k + HalfLevels] * SCALESLP;
  for (k = 0; k < HalfLevels; ++k)
    if (pph[k] > pmax) break;
  const auto MaxLev = k - 1;
  for (k = HalfLevels - 1; k >= 0; k--)
    if (pph[k] < pmin) break;
  const auto MinLev = k;

  for (int i = 0; i < DimGP; ++i) ll[i] = 1. - cc[i + MaxLev * DimGP];

  for (k = MaxLev + 1; k <= MinLev; ++k)
    {
      for (int i = 0; i < DimGP; ++i)
        ll[i]
            *= (1. - std::max(cc[i + (k - 1) * DimGP], cc[i + k * DimGP])) / (1. - std::min(cc[i + (k - 1) * DimGP], 1. - ZEPSEC));
    }
  for (int i = 0; i < DimGP; ++i) ll[i] = 1. - ll[i];
}

// Grid Point Computations
void
after_EchamCompGP(const AfterControl &globs, struct Variable *vars)
{
  if (vars[GEOPOTHEIGHT].comp || vars[SLP].comp || vars[THETAF].needed || vars[HALF_PRESS].needed || vars[RHUMIDITY].comp
      || vars[OMEGA].comp || globs.Type >= 30)
    {
      if (vars[FULL_PRESS].hybrid == nullptr) vars[FULL_PRESS].hybrid = alloc_dp(globs.Dim3GP, "vars[FULL_PRESS].hybrid");

      vars[HALF_PRESS].hlev = globs.NumLevel + 1;
      vars[HALF_PRESS].plev = globs.NumLevelRequest;
      vars[HALF_PRESS].sfit = false;

      if (vars[HALF_PRESS].hybrid == nullptr)
        vars[HALF_PRESS].hybrid = alloc_dp(globs.Dim3GP + globs.DimGP, "vars[HALF_PRESS].hybrid");

      vct_to_hybrid_pressure(vars[FULL_PRESS].hybrid, vars[HALF_PRESS].hybrid, globs.vct, vars[PS_PROG].hybrid, globs.NumLevel,
                             globs.DimGP);
    }

  if (globs.unitsel > 2) vars[FULL_PRESS].hybrid = (double *) FreeMemory(vars[FULL_PRESS].hybrid);

  if (vars[THETAF].needed)
    {
      vars[THETAF].hlev = globs.NumLevel;
      vars[THETAF].plev = globs.NumLevelRequest;
      vars[THETAF].sfit = true;
      if (vars[THETAF].hybrid == nullptr) vars[THETAF].hybrid = alloc_dp(globs.Dim3GP, "vars[THETAF].hybrid");
      if (vars[THETAH].hybrid == nullptr) vars[THETAH].hybrid = alloc_dp(globs.Dim3GP, "vars[THETAH].hybrid");
      theta(vars[THETAF].hybrid, vars[THETAH].hybrid, vars[HALF_PRESS].hybrid, vars[PS_PROG].hybrid, vars[TEMPERATURE].hybrid,
            vars[TS].hybrid, globs.NumLevel, globs.DimGP, globs.Dim3GP);
    }

  if (vars[GEOPOTHEIGHT].comp)
    {
      vars[GEOPOTHEIGHT].hlev = globs.NumLevel + 1;
      vars[GEOPOTHEIGHT].plev = globs.NumLevelRequest;
      vars[GEOPOTHEIGHT].sfit = true;
      vars[GEOPOTHEIGHT].hybrid = alloc_dp(globs.Dim3GP + globs.DimGP, "vars[GEOPOTHEIGHT].hybrid");

      array_copy(globs.DimGP, globs.Orography, vars[GEOPOTHEIGHT].hybrid + globs.Dim3GP);
      geopot_height_fulllevel(vars[GEOPOTHEIGHT].hybrid, vars[TEMPERATURE].hybrid, vars[HUMIDITY].hybrid, vars[HALF_PRESS].hybrid,
                              globs.DimGP, globs.NumLevel);

      vars[HUMIDITY].needed = vars[HUMIDITY].selected;
    }
  else if (vars[GEOPOTHEIGHT].hybrid && vars[GEOPOTHEIGHT].hlev == globs.NumLevel)
    {
      vars[GEOPOTHEIGHT].hlev = globs.NumLevel + 1;
      vars[GEOPOTHEIGHT].sfit = true;
      vars[GEOPOTHEIGHT].hybrid = (double *) Realloc(vars[GEOPOTHEIGHT].hybrid, (globs.Dim3GP + globs.DimGP) * sizeof(double));
      array_copy(globs.DimGP, globs.Orography, vars[GEOPOTHEIGHT].hybrid + globs.Dim3GP);
      for (int i = 0; i < globs.DimGP; ++i) vars[GEOPOTHEIGHT].hybrid[globs.Dim3GP + i] /= PlanetGrav;
    }

  if (vars[DPSDX].needed || vars[DPSDY].needed)
    for (int l = 0; l < globs.DimGP; ++l)
      {
        vars[DPSDX].hybrid[l] *= vars[PS_PROG].hybrid[l];
        vars[DPSDY].hybrid[l] *= vars[PS_PROG].hybrid[l];
      }

  if (vars[OMEGA].comp)
    {
      vars[OMEGA].hlev = globs.NumLevel + 1;
      vars[OMEGA].plev = globs.NumLevelRequest;
      vars[OMEGA].sfit = true;
      vars[OMEGA].hybrid = alloc_dp(globs.Dim3GP + globs.DimGP, "OMEGA.hybrid");

      Omega(vars[OMEGA].hybrid, vars[DIVERGENCE].hybrid, vars[U_WIND].hybrid, vars[V_WIND].hybrid, vars[HALF_PRESS].hybrid,
            vars[FULL_PRESS].hybrid, vars[DPSDX].hybrid, vars[DPSDY].hybrid, globs.vct, globs.DimGP, globs.NumLevel);

      vars[DPSDX].needed = vars[DPSDX].selected;
      vars[DPSDY].needed = vars[DPSDY].selected;
    }

  if (vars[WINDSPEED].comp)
    {
      vars[WINDSPEED].hlev = globs.NumLevel;
      vars[WINDSPEED].plev = globs.NumLevelRequest;
      vars[WINDSPEED].sfit = true;
      vars[WINDSPEED].hybrid = alloc_dp(globs.Dim3GP, "vars[WINDSPEED].hybrid");

      windSpeed(vars[WINDSPEED].hybrid, vars[U_WIND].hybrid, vars[V_WIND].hybrid, globs.Dim3GP);
    }

  if (vars[RHUMIDITY].comp)
    {
      vars[RHUMIDITY].hlev = globs.NumLevel;
      vars[RHUMIDITY].plev = globs.NumLevelRequest;
      vars[RHUMIDITY].sfit = false;
      vars[RHUMIDITY].hybrid = alloc_dp(globs.Dim3GP, "vars[RHUMIDITY].hybrid");

      sh2rh(globs.AnalysisData, vars[HUMIDITY].hybrid, vars[RHUMIDITY].hybrid, vars[TEMPERATURE].hybrid, globs.NumLevel,
            globs.DimGP, globs.LevelRequest, vars[FULL_PRESS].hybrid);

      vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
      vars[HUMIDITY].needed = vars[HUMIDITY].selected;
    }

  if (vars[PS].comp)
    {
      vars[PS].hlev = 1;
      vars[PS].plev = 1;
      vars[PS].sfit = true; // ???
      vars[PS].hybrid = alloc_dp(globs.DimGP, "vars[PS].hybrid");
      for (int l = 0; l < globs.DimGP; ++l) vars[PS].hybrid[l] = std::exp(vars[LNPS].hybrid[l]);
    }

  if (vars[SLP].comp)
    {
      vars[SLP].hlev = 1;
      vars[SLP].plev = 1;
      vars[SLP].sfit = true;
      vars[SLP].hybrid = alloc_dp(globs.DimGP, "vars[SLP].hybrid");

      extrapolate_P(vars[SLP].hybrid, vars[HALF_PRESS].hybrid + globs.Dim3GP, vars[FULL_PRESS].hybrid + globs.Dim3GP - globs.DimGP,
                    globs.Orography, vars[TEMPERATURE].hybrid + globs.Dim3GP - globs.DimGP, globs.DimGP);
      vars[TEMPERATURE].needed = vars[TEMPERATURE].selected || vars[GEOPOTHEIGHT].selected;
    }

  if (vars[PRECIP].comp)
    {
      vars[PRECIP].hlev = vars[PRECIP].plev = 1;
      vars[PRECIP].sfit = false;
      vars[PRECIP].hybrid = alloc_dp(globs.DimGP, "PRECIP.hybrid");
      Add2Vectors(vars[PRECIP].hybrid, vars[142].hybrid, vars[143].hybrid, globs.DimGP);
    }

  if (vars[NET_TOP].comp)
    {
      vars[NET_TOP].hlev = vars[NET_TOP].plev = 1;
      vars[NET_TOP].sfit = false;
      vars[NET_TOP].hybrid = alloc_dp(globs.DimGP, "NET_TOP.hybrid");
      Add2Vectors(vars[NET_TOP].hybrid, vars[178].hybrid, vars[179].hybrid, globs.DimGP);
    }

  if (vars[NET_BOT].comp)
    {
      vars[NET_BOT].hlev = vars[NET_BOT].plev = 1;
      vars[NET_BOT].sfit = false;
      vars[NET_BOT].hybrid = alloc_dp(globs.DimGP, "NET_BOT.hybrid");
      Add2Vectors(vars[NET_BOT].hybrid, vars[176].hybrid, vars[177].hybrid, globs.DimGP);
    }

  if (vars[NET_HEAT].comp)
    {
      vars[NET_HEAT].hlev = vars[NET_HEAT].plev = 1;
      vars[NET_HEAT].sfit = false;
      vars[NET_HEAT].hybrid = alloc_dp(globs.DimGP, "NET_HEAT.hybrid");
      /*
      if ( Source == S_ECHAM5 )
        {
          MultVectorScalar(vars[NET_HEAT].hybrid, vars[218].hybrid, (-3.345e5), globs.DimGP, vars[218].nmiss, vars[218].missval);
          Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[176].hybrid, globs.DimGP); Add2Vectors(vars[NET_HEAT].hybrid,
                      vars[NET_HEAT].hybrid, vars[177].hybrid, globs.DimGP);
          Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[146].hybrid, globs.DimGP); Add2Vectors(vars[NET_HEAT].hybrid,
                      vars[NET_HEAT].hybrid, vars[147].hybrid, globs.DimGP);
          Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[206].hybrid, globs.DimGP); Sub2Vectors(vars[NET_HEAT].hybrid,
                      vars[NET_HEAT].hybrid, vars[208].hybrid, globs.DimGP);
          Sub2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[209].hybrid, globs.DimGP);
        }
      else
      */
      {
        MultVectorScalar(vars[NET_HEAT].hybrid, vars[218].hybrid, C_TIMES_RHOH2O, globs.DimGP, vars[218].nmiss, vars[218].missval);
        Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[176].hybrid, globs.DimGP);
        Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[177].hybrid, globs.DimGP);
        Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[146].hybrid, globs.DimGP);
        Add2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[147].hybrid, globs.DimGP);
        Sub2Vectors(vars[NET_HEAT].hybrid, vars[NET_HEAT].hybrid, vars[220].hybrid, globs.DimGP);
      }
    }

  if (vars[NET_WATER].comp)
    {
      vars[NET_WATER].hlev = vars[NET_WATER].plev = 1;
      vars[NET_WATER].sfit = false;
      vars[NET_WATER].hybrid = alloc_dp(globs.DimGP, "NET_WATER.hybrid");
      Sub2Vectors(vars[NET_WATER].hybrid, vars[182].hybrid, vars[160].hybrid, globs.DimGP);
      Add2Vectors(vars[NET_WATER].hybrid, vars[NET_WATER].hybrid, vars[142].hybrid, globs.DimGP);
      Add2Vectors(vars[NET_WATER].hybrid, vars[NET_WATER].hybrid, vars[143].hybrid, globs.DimGP);
    }

  if (vars[LOW_WATER].comp)
    {
      vars[LOW_WATER].hlev = vars[LOW_WATER].plev = 1;
      vars[LOW_WATER].sfit = false;
      vars[LOW_WATER].hybrid = alloc_dp(globs.DimGP, "vars[LOW_WATER].hybrid");
      LayerWater(vars[222].hybrid, vars[LOW_WATER].hybrid, 75000., 101300., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[MID_WATER].comp)
    {
      vars[MID_WATER].hlev = vars[MID_WATER].plev = 1;
      vars[MID_WATER].sfit = false;
      vars[MID_WATER].hybrid = alloc_dp(globs.DimGP, "vars[MID_WATER].hybrid");
      LayerWater(vars[222].hybrid, vars[MID_WATER].hybrid, 46000., 73000., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[HIH_WATER].comp)
    {
      vars[HIH_WATER].hlev = vars[HIH_WATER].plev = 1;
      vars[HIH_WATER].sfit = false;
      vars[HIH_WATER].hybrid = alloc_dp(globs.DimGP, "vars[HIH_WATER].hybrid");
      LayerWater(vars[222].hybrid, vars[HIH_WATER].hybrid, 5000., 44000., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[ALL_WATER].comp)
    {
      vars[ALL_WATER].hlev = vars[ALL_WATER].plev = 1;
      vars[ALL_WATER].sfit = false;
      vars[ALL_WATER].hybrid = alloc_dp(globs.DimGP, "vars[ALL_WATER].hybrid");
      LayerWater(vars[222].hybrid, vars[ALL_WATER].hybrid, 5000., 101300., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[LOW_CLOUD].comp)
    {
      vars[LOW_CLOUD].hlev = vars[LOW_CLOUD].plev = 1;
      vars[LOW_CLOUD].sfit = false;
      vars[LOW_CLOUD].hybrid = alloc_dp(globs.DimGP, "vars[LOW_CLOUD].hybrid");
      LayerCloud(vars[223].hybrid, vars[LOW_CLOUD].hybrid, 75000., 101300., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[MID_CLOUD].comp)
    {
      vars[MID_CLOUD].hlev = vars[MID_CLOUD].plev = 1;
      vars[MID_CLOUD].sfit = false;
      vars[MID_CLOUD].hybrid = alloc_dp(globs.DimGP, "vars[MID_CLOUD].hybrid");
      LayerCloud(vars[223].hybrid, vars[MID_CLOUD].hybrid, 46000., 73000., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[HIH_CLOUD].comp)
    {
      vars[HIH_CLOUD].hlev = vars[HIH_CLOUD].plev = 1;
      vars[HIH_CLOUD].sfit = false;
      vars[HIH_CLOUD].hybrid = alloc_dp(globs.DimGP, "vars[HIH_CLOUD].hybrid");
      LayerCloud(vars[223].hybrid, vars[HIH_CLOUD].hybrid, 5000., 44000., globs.DimGP, globs.HalfLevels, globs.vct);
    }

  if (vars[SW_CLF].comp)
    {
      vars[SW_CLF].hlev = vars[SW_CLF].plev = 1;
      vars[SW_CLF].sfit = false;
      vars[SW_CLF].hybrid = alloc_dp(globs.DimGP, "SW_CLF.hybrid");
      Sub2Vectors(vars[SW_CLF].hybrid, vars[178].hybrid, vars[224].hybrid, globs.DimGP);
    }

  if (vars[SW_BOT_CLF].comp)
    {
      vars[SW_BOT_CLF].hlev = vars[SW_BOT_CLF].plev = 1;
      vars[SW_BOT_CLF].sfit = false;
      vars[SW_BOT_CLF].hybrid = alloc_dp(globs.DimGP, "vars[SW_BOT_CLF].hybrid");
      Sub2Vectors(vars[SW_BOT_CLF].hybrid, vars[176].hybrid, vars[185].hybrid, globs.DimGP);
    }

  if (vars[SW_TOP_CLF].comp)
    {
      vars[SW_TOP_CLF].hlev = vars[SW_TOP_CLF].plev = 1;
      vars[SW_TOP_CLF].sfit = false;
      vars[SW_TOP_CLF].hybrid = alloc_dp(globs.DimGP, "vars[SW_TOP_CLF].hybrid");
      Sub2Vectors(vars[SW_TOP_CLF].hybrid, vars[178].hybrid, vars[187].hybrid, globs.DimGP);
    }

  if (vars[LW_CLF].comp)
    {
      vars[LW_CLF].hlev = vars[LW_CLF].plev = 1;
      vars[LW_CLF].sfit = false;
      vars[LW_CLF].hybrid = alloc_dp(globs.DimGP, "LW_CLF.hybrid");
      Sub2Vectors(vars[LW_CLF].hybrid, vars[179].hybrid, vars[225].hybrid, globs.DimGP);
    }

  if (vars[LW_BOT_CLF].comp)
    {
      vars[LW_BOT_CLF].hlev = vars[LW_BOT_CLF].plev = 1;
      vars[LW_BOT_CLF].sfit = false;
      vars[LW_BOT_CLF].hybrid = alloc_dp(globs.DimGP, "vars[LW_BOT_CLF].hybrid");
      Sub2Vectors(vars[LW_BOT_CLF].hybrid, vars[177].hybrid, vars[186].hybrid, globs.DimGP);
    }

  if (vars[LW_TOP_CLF].comp)
    {
      vars[LW_TOP_CLF].hlev = vars[LW_TOP_CLF].plev = 1;
      vars[LW_TOP_CLF].sfit = false;
      vars[LW_TOP_CLF].hybrid = alloc_dp(globs.DimGP, "vars[LW_TOP_CLF].hybrid");
      Sub2Vectors(vars[LW_TOP_CLF].hybrid, vars[179].hybrid, vars[188].hybrid, globs.DimGP);
    }

  if (vars[NET_CLF].comp)
    {
      vars[NET_CLF].hlev = vars[NET_CLF].plev = 1;
      vars[NET_CLF].sfit = false;
      vars[NET_CLF].hybrid = alloc_dp(globs.DimGP, "NET_CLF.hybrid");
      Add2Vectors(vars[NET_CLF].hybrid, vars[178].hybrid, vars[179].hybrid, globs.DimGP);
      Sub2Vectors(vars[NET_CLF].hybrid, vars[NET_CLF].hybrid, vars[224].hybrid, globs.DimGP);
      Sub2Vectors(vars[NET_CLF].hybrid, vars[NET_CLF].hybrid, vars[225].hybrid, globs.DimGP);
    }

  if (vars[SW_ATM].comp)
    {
      vars[SW_ATM].hlev = vars[SW_ATM].plev = 1;
      vars[SW_ATM].sfit = false;
      vars[SW_ATM].hybrid = alloc_dp(globs.DimGP, "vars[SW_ATM].hybrid");
      Sub2Vectors(vars[SW_ATM].hybrid, vars[178].hybrid, vars[176].hybrid, globs.DimGP);
    }

  if (vars[LW_ATM].comp)
    {
      vars[LW_ATM].hlev = vars[LW_ATM].plev = 1;
      vars[LW_ATM].sfit = false;
      vars[LW_ATM].hybrid = alloc_dp(globs.DimGP, "vars[LW_ATM].hybrid");
      Sub2Vectors(vars[LW_ATM].hybrid, vars[179].hybrid, vars[177].hybrid, globs.DimGP);
    }

  if (vars[NET_ATM].comp)
    {
      vars[NET_ATM].hlev = vars[NET_ATM].plev = 1;
      vars[NET_ATM].sfit = false;
      vars[NET_ATM].hybrid = alloc_dp(globs.DimGP, "vars[NET_ATM].hybrid");
      Add2Vectors(vars[NET_ATM].hybrid, vars[178].hybrid, vars[179].hybrid, globs.DimGP);
      Sub2Vectors(vars[NET_ATM].hybrid, vars[NET_ATM].hybrid, vars[176].hybrid, globs.DimGP);
      Sub2Vectors(vars[NET_ATM].hybrid, vars[NET_ATM].hybrid, vars[177].hybrid, globs.DimGP);
    }

  if (vars[SURF_RUNOFF].comp)
    {
      vars[SURF_RUNOFF].hlev = vars[SURF_RUNOFF].plev = 1;
      vars[SURF_RUNOFF].sfit = false;
      vars[SURF_RUNOFF].hybrid = alloc_dp(globs.DimGP, "vars[SURF_RUNOFF].hybrid");
      Sub2Vectors(vars[SURF_RUNOFF].hybrid, vars[182].hybrid, vars[221].hybrid, globs.DimGP);
      Add2Vectors(vars[SURF_RUNOFF].hybrid, vars[SURF_RUNOFF].hybrid, vars[142].hybrid, globs.DimGP);
      Add2Vectors(vars[SURF_RUNOFF].hybrid, vars[SURF_RUNOFF].hybrid, vars[143].hybrid, globs.DimGP);
    }

  if (vars[FRESH_WATER].comp)
    {
      vars[FRESH_WATER].hlev = vars[FRESH_WATER].plev = 1;
      vars[FRESH_WATER].sfit = false;
      vars[FRESH_WATER].hybrid = alloc_dp(globs.DimGP, "vars[FRESH_WATER].hybrid");
      Add2Vectors(vars[FRESH_WATER].hybrid, vars[142].hybrid, vars[143].hybrid, globs.DimGP);
      Add2Vectors(vars[FRESH_WATER].hybrid, vars[FRESH_WATER].hybrid, vars[182].hybrid, globs.DimGP);
    }
}

static void
Derivate(double field[], double derilam[], long nlevels, long Waves, long Latitudes, double DerivationFactor[])
{
  long i = 0;
  for (long lev = 0; lev < nlevels; lev++)
    for (long n = 0; n < Waves; ++n)
      {
        for (long l = 0; l < Latitudes; ++l)
          {
            derilam[i] = -n * field[i + Latitudes] * DerivationFactor[l];
            i++;
          }
        for (long l = 0; l < Latitudes; ++l)
          {
            derilam[i] = n * field[i - Latitudes] * DerivationFactor[l];
            i++;
          }
      }
}

// Process Model Level data
void
after_processML(AfterControl &globs, struct Variable *vars)
{
  int leveltype;
  size_t nmiss;
  double *pressureLevel = nullptr;

  globs.MeanCount++;
  globs.TermCount++;

  if (globs.MeanCount == 1)
    {
      if (globs.Debug) cdo_print("TermCount = %d MeanCount = %d", globs.TermCount, globs.MeanCount);
      globs.StartDate = globs.OldDate;
    }

  if (globs.TermCount > 120) globs.Debug = 0;

  /* ============================== */
  /* Computations in spectral space */
  /* ============================== */

  if (vars[U_WIND].comp || vars[V_WIND].comp)
    {
      vars[U_WIND].hlev = vars[V_WIND].hlev = vars[DIVERGENCE].hlev;
      vars[U_WIND].plev = vars[V_WIND].plev = vars[DIVERGENCE].plev;
      vars[U_WIND].sfit = vars[V_WIND].sfit = true;
      vars[U_WIND].spectral = alloc_dp(globs.Dim3SP, "vars[U_WIND].spectral");
      vars[V_WIND].spectral = alloc_dp(globs.Dim3SP, "vars[V_WIND].spectral");

      if (vars[DIVERGENCE].spectral == nullptr) after_gp2sp(globs, vars, DIVERGENCE);
      if (vars[VORTICITY].spectral == nullptr) after_gp2sp(globs, vars, VORTICITY);

      dv2uv(vars[DIVERGENCE].spectral, vars[VORTICITY].spectral, vars[U_WIND].spectral, vars[V_WIND].spectral, globs.dv2uv_f1,
            globs.dv2uv_f2, globs.Truncation, globs.DimSP, vars[DIVERGENCE].hlev);
    }

  if (vars[VELOPOT].comp && globs.Type < 30)
    {
      vars[VELOPOT].hlev = vars[DIVERGENCE].hlev;
      vars[VELOPOT].plev = vars[DIVERGENCE].plev;
      vars[VELOPOT].spectral = alloc_dp(globs.Dim3SP, "vars[VELOPOT].spectral");

      if (vars[DIVERGENCE].spectral == nullptr) after_gp2sp(globs, vars, DIVERGENCE);

      dv2ps(vars[DIVERGENCE].spectral, vars[VELOPOT].spectral, vars[DIVERGENCE].hlev, globs.Truncation);
    }

  if (vars[STREAM].comp && globs.Type < 30)
    {
      vars[STREAM].hlev = vars[VORTICITY].hlev;
      vars[STREAM].plev = vars[VORTICITY].plev;
      vars[STREAM].spectral = alloc_dp(globs.Dim3SP, "vars[STREAM].spectral");

      if (vars[VORTICITY].spectral == nullptr) after_gp2sp(globs, vars, VORTICITY);

      dv2ps(vars[VORTICITY].spectral, vars[STREAM].spectral, vars[VORTICITY].hlev, globs.Truncation);
    }

  if (vars[VORTICITY].spectral && !vars[VORTICITY].needed)
    vars[VORTICITY].spectral = (double *) FreeMemory(vars[VORTICITY].spectral);

  if (vars[DIVERGENCE].spectral && !vars[DIVERGENCE].needed)
    vars[DIVERGENCE].spectral = (double *) FreeMemory(vars[DIVERGENCE].spectral);

  /* --------------------------- */
  /*  Output of spectral fields  */
  /* --------------------------- */

  if (globs.Type == 0)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].spectral) afterAbort("Code %d not available on spectral space!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimSP;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].spectral + offset, 0);
              }
          }

      FreeSpectral(vars);
      return;
    }

  /* ------------------------------- */
  /*  Computations in fourier space  */
  /* ------------------------------- */

  if (globs.Type >= 10)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].spectral)
          {
            if (vars[code].fourier == nullptr)
              {
                const auto fieldSize = vars[code].hlev * globs.DimFC;
                vars[code].fourier = alloc_dp(fieldSize, FieldName(code, "fourier"));
                sp2fc(vars[code].spectral, vars[code].fourier, globs.poli, vars[code].hlev, globs.Latitudes, globs.Fouriers,
                      globs.Truncation);
              }
            if (code != LNPS) vars[code].spectral = (double *) FreeMemory(vars[code].spectral);
          }

      if (vars[U_WIND].needed && vars[U_WIND].fourier)
        scaluv(vars[U_WIND].fourier, globs.rcoslat, globs.Latitudes, globs.Fouriers * globs.NumLevel);
      if (vars[V_WIND].needed && vars[V_WIND].fourier)
        scaluv(vars[V_WIND].fourier, globs.rcoslat, globs.Latitudes, globs.Fouriers * globs.NumLevel);

      if (vars[DPSDX].needed)
        {
          vars[DPSDX].hlev = 1;
          vars[DPSDX].plev = 1;
          vars[DPSDX].sfit = false;
          vars[DPSDX].fourier = alloc_dp(globs.DimFC, "vars[DPSDX].fourier");
          if (vars[LNPS].fourier == nullptr) after_gp2sp(globs, vars, LNPS);
          Derivate(vars[LNPS].fourier, vars[DPSDX].fourier, 1, globs.Waves, globs.Latitudes, globs.DerivationFactor);
        }
      if (vars[DPSDY].needed)
        {
          vars[DPSDY].hlev = 1;
          vars[DPSDY].plev = 1;
          vars[DPSDY].sfit = false;
          vars[DPSDY].fourier = alloc_dp(globs.DimFC, "vars[DPSDY].fourier");
          if (vars[LNPS].spectral == nullptr) after_gp2sp(globs, vars, LNPS);
          sp2fc(vars[LNPS].spectral, vars[DPSDY].fourier, globs.pdev, vars[DPSDY].hlev, globs.Latitudes, globs.Fouriers,
                globs.Truncation);
        }
    }

  FreeSpectral(vars);

  /* -------------------------- */
  /*  Output of fourier fields  */
  /* -------------------------- */

  if (globs.Type == 10)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on fourier space!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, 0);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ----------------------- */
  /*  Output of zonal means  */
  /* ----------------------- */

  if (globs.Type == 11)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on zonal mean!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, 0);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ------------------------------ */
  /*  Transformation to gridpoints  */
  /* ------------------------------ */

  if (globs.Type >= 20)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].fourier)
          {
            if (vars[code].hybrid == nullptr)
              {
                const auto fieldSize = globs.DimGP * vars[code].hlev;
                vars[code].hybrid = alloc_dp(fieldSize, FieldName(code, "hybrid"));
                after_FC2GP(vars[code].fourier, vars[code].hybrid, globs.Latitudes, globs.Longitudes, vars[code].hlev,
                            globs.Fouriers);
              }
            vars[code].fourier = (double *) FreeMemory(vars[code].fourier);
          }

      if (vars[PS_PROG].comp && vars[PS_PROG].hybrid == nullptr)
        {
          vars[PS_PROG].hybrid = alloc_dp(globs.DimGP, "PS_PROG");
          if (vars[LNPS].hybrid)
            {
              for (int l = 0; l < globs.DimGP; ++l) vars[PS_PROG].hybrid[l] = std::exp(vars[LNPS].hybrid[l]);
            }
          else if (vars[PS].hybrid)
            {
              cdo_warning("log surface pressure (code 152) not found - using surface pressure (code 134)!");
              array_copy(globs.DimGP, vars[PS].hybrid, vars[PS_PROG].hybrid);
            }
          else
            {
              afterAbort("surface pressure not found!");
            }
        }
      vars[LNPS].needed = vars[LNPS].selected;

      if (globs.Orography == nullptr)
        {
          globs.Orography = alloc_dp(globs.DimGP, "Orography");
          if (vars[GEOPOTENTIAL].hybrid)
            array_copy(globs.DimGP, vars[GEOPOTENTIAL].hybrid, globs.Orography);
          else
            {
              if (vars[GEOPOTENTIAL].selected || globs.Type >= 30)
                {
                  cdo_warning("Orography not found - using zero orography!");
                  varray_fill(globs.DimGP, globs.Orography, 0.0);
                }
            }
        }
      vars[GEOPOTENTIAL].needed = vars[GEOPOTENTIAL].selected;

      after_EchamCompGP(globs, vars);
    }

  FreeFourier(vars);

  if (globs.Type == 20)
    {
      /* ----------------------- */
      /*  Means on hybrid grids  */
      /* ----------------------- */

      if (globs.Mean)
        {
          for (int code = 0; code < MaxCodes; ++code)
            {
              if (vars[code].selected && vars[code].hybrid)
                {
                  const auto fieldSize = globs.DimGP * vars[code].hlev;

                  if (vars[code].mean == nullptr) vars[code].mean = alloc_dp(fieldSize, FieldName(code, "mean"));

                  if (globs.Mean > 1 && vars[code].variance == nullptr)
                    vars[code].variance = alloc_dp(fieldSize, FieldName(code, "variance"));

                  if (globs.MeanCount == 1)
                    {
                      array_copy(fieldSize, vars[code].hybrid, vars[code].mean);
                      if (globs.Mean > 1) IniQuaSum(vars[code].variance, vars[code].hybrid, fieldSize);
                    }
                  else
                    {
                      AddVector(vars[code].mean, vars[code].hybrid, fieldSize, &vars[code].nmiss, vars[code].missval);
                      if (globs.Mean > 1) AddQuaSum(vars[code].variance, vars[code].hybrid, fieldSize);
                    }

                  if (globs.EndOfInterval)
                    {
                      if (vars[code].samp == nullptr)
                        MultVectorScalar(vars[code].hybrid, vars[code].mean, 1.0 / globs.MeanCount, fieldSize, vars[code].nmiss,
                                         vars[code].missval);
                      else
                        DivVectorIvector(vars[code].hybrid, vars[code].mean, vars[code].samp, fieldSize, &vars[code].nmiss,
                                         vars[code].missval);

                      if (globs.Mean > 1) VarQuaSum(vars[code].variance, vars[code].hybrid, fieldSize, globs.MeanCount);
                    }
                }
            }
        }

      /* ---------------------------- */
      /* Output of hybrid level grids */
      /* ---------------------------- */

      if (globs.Mean == 0 || globs.EndOfInterval)
        {
          for (int code = 0; code < MaxCodes; ++code)
            if (vars[code].selected)
              {
                if (vars[code].hybrid == nullptr) afterAbort("Internal problem. Code %d not allocated!", code);

                const int nlevels = zaxisInqSize(vars[code].ozaxisID);
                for (int k = 0; k < nlevels; ++k)
                  {
                    const auto offset = k * globs.DimGP;
                    if (globs.Mean != 2)
                      {
                        streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                        streamWriteRecord(globs.ostreamID, vars[code].hybrid + offset, vars[code].nmiss);
                      }
                    if (globs.Mean >= 2)
                      {
                        streamDefRecord(globs.ostreamID2, vars[code].ovarID2, k);
                        streamWriteRecord(globs.ostreamID2, vars[code].variance + offset, vars[code].nmiss);
                      }
                  }
              }

          FreeSamp(vars);
        }

      FreeHybrid(vars);
      return;
    }

  /* -------------------------------------- */
  /* Vertical interpolation / extrapolation */
  /* -------------------------------------- */

  if (globs.Type >= 30)
    {
      if (globs.vertIndex == nullptr) globs.vertIndex = (int *) Malloc(globs.NumLevelRequest * globs.DimGP * sizeof(int));

      if (globs.unitsel)
        {
          if (globs.p_of_height == nullptr) globs.p_of_height = alloc_dp(globs.NumLevelRequest, "p_of_height");
          height_to_pressure(globs.p_of_height, globs.LevelRequest, globs.NumLevelRequest);
          pressureLevel = globs.p_of_height;
        }
      else
        {
          pressureLevel = globs.LevelRequest;
        }

      gen_vert_index(globs.vertIndex, pressureLevel, vars[FULL_PRESS].hybrid, globs.DimGP, globs.NumLevelRequest, globs.NumLevel);

      nmiss = 0;
      if (!globs.extrapolate)
        {
          if (globs.pnmiss == nullptr) globs.pnmiss = (size_t *) Malloc(globs.NumLevelRequest * sizeof(size_t));
          gen_vert_index_mv(globs.vertIndex, pressureLevel, globs.DimGP, globs.NumLevelRequest, vars[PS_PROG].hybrid, globs.pnmiss);
          for (int i = 0; i < globs.NumLevelRequest; ++i) nmiss += globs.pnmiss[i];
        }

      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].hybrid)
          {
            leveltype = zaxisInqType(vars[code].izaxisID);
            if (vars[code].hlev == 1 || leveltype != ZAXIS_HYBRID || (vars[code].hlev < globs.NumLevel))
              {
                if (vars[code].grid) FreeMemory(vars[code].grid);
                vars[code].grid = vars[code].hybrid;
                vars[code].hybrid = nullptr;
              }
            else
              {
                if (vars[code].grid == nullptr)
                  {
                    const auto fieldSize = globs.DimGP * globs.NumLevelRequest;
                    vars[code].grid = alloc_dp(fieldSize, FieldName(code, "grid"));
                  }

                if (code == TEMPERATURE)
                  {
                    vertical_interp_T(globs.Orography, vars[TEMPERATURE].hybrid, vars[TEMPERATURE].grid, vars[FULL_PRESS].hybrid,
                                      vars[HALF_PRESS].hybrid, globs.vertIndex, pressureLevel, globs.NumLevelRequest, globs.DimGP,
                                      globs.NumLevel, vars[code].missval);
                  }
                else if (code == GEOPOTHEIGHT)
                  {
                    if (vars[TEMPERATURE].hybrid == nullptr) afterAbort("Code  130 not found!");
                    vertical_interp_Z(globs.Orography, vars[GEOPOTHEIGHT].hybrid, vars[GEOPOTHEIGHT].grid, vars[FULL_PRESS].hybrid,
                                      vars[HALF_PRESS].hybrid, globs.vertIndex, vars[TEMPERATURE].hybrid, pressureLevel,
                                      globs.NumLevelRequest, globs.DimGP, globs.NumLevel, vars[code].missval);
                  }
                else
                  {
                    int numlevel = vars[code].hlev;
                    if (code == OMEGA) numlevel = globs.NumLevel;
                    double *hyb_press = vars[FULL_PRESS].hybrid;
                    if (numlevel == (globs.NumLevel + 1)) hyb_press = vars[HALF_PRESS].hybrid;

                    vertical_interp_X(vars[code].hybrid, vars[code].grid, hyb_press, globs.vertIndex, pressureLevel,
                                      globs.NumLevelRequest, globs.DimGP, numlevel, vars[code].missval);
                  }

                if (!globs.extrapolate) vars[code].nmiss = nmiss;

                if (code != TEMPERATURE) vars[code].hybrid = (double *) FreeMemory(vars[code].hybrid);
              }
          }
    }

  vars[TEMPERATURE].needed = vars[TEMPERATURE].selected;
  FreeHybrid(vars);
  if (vars[HALF_PRESS].hybrid) vars[HALF_PRESS].hybrid = (double *) FreeMemory(vars[HALF_PRESS].hybrid);

  /* -------------------------------- */
  /*  Output of pressure level grids  */
  /* -------------------------------- */

  if (globs.Type == 30 && globs.Mean == 0)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected && vars[code].grid)
          {
            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                if (globs.Mean != 2)
                  {
                    const auto offset = k * globs.DimGP;
                    streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                    streamWriteRecord(globs.ostreamID, vars[code].grid + offset, vars[code].nmiss);
                  }
              }
          }

      FreeGrid(vars);
      return;
    }

  /* ---------------------- */
  /*  Computation of Means  */
  /* ---------------------- */

  if (globs.Type >= 30 && globs.Mean)
    for (int code = 0; code < MaxCodes; ++code)
      if (vars[code].needed && vars[code].grid)
        {
          const auto fieldSize = globs.DimGP * vars[code].plev;

          if (vars[code].mean == nullptr) vars[code].mean = alloc_dp(fieldSize, FieldName(code, "mean"));

          if (globs.MeanCount == 1)
            array_copy(fieldSize, vars[code].grid, vars[code].mean);
          else
            AddVector(vars[code].mean, vars[code].grid, fieldSize, &vars[code].nmiss, vars[code].missval);

          if (globs.EndOfInterval)
            {
              if (vars[code].samp == nullptr)
                MultVectorScalar(vars[code].mean, vars[code].mean, 1.0 / globs.MeanCount, fieldSize, vars[code].nmiss,
                                 vars[code].missval);
              else
                DivVectorIvector(vars[code].mean, vars[code].mean, vars[code].samp, fieldSize, &vars[code].nmiss,
                                 vars[code].missval);
            }
        }

  /* -------------------------- */
  /*  Computation of Variances  */
  /* -------------------------- */

  if (globs.Type >= 30 && globs.Mean > 1)
    for (int code = 0; code < MaxCodes; ++code)
      if (vars[code].needed && vars[code].mean)
        {
          const auto fieldSize = globs.DimGP * vars[code].plev;

          if (vars[code].variance == nullptr) vars[code].variance = alloc_dp(fieldSize, FieldName(code, "var"));

          if (globs.MeanCount == 1)
            IniQuaSum(vars[code].variance, vars[code].grid, fieldSize);
          else
            AddQuaSum(vars[code].variance, vars[code].grid, fieldSize);

          if (globs.EndOfInterval) VarQuaSum(vars[code].variance, vars[code].mean, fieldSize, globs.MeanCount);
        }

  if (globs.Mean && !globs.EndOfInterval)
    {
      FreeGrid(vars);
      return;
    }

  /* --------------------------------------------- */
  /*  Output of pressure level means and variances */
  /* --------------------------------------------- */

  if (globs.Type == 30 && globs.Mean)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected && vars[code].mean)
          {
            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimGP;
                if (globs.Mean != 2)
                  {
                    streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                    streamWriteRecord(globs.ostreamID, vars[code].mean + offset, vars[code].nmiss);
                  }
                if (globs.Mean >= 2)
                  {
                    streamDefRecord(globs.ostreamID2, vars[code].ovarID2, k);
                    streamWriteRecord(globs.ostreamID2, vars[code].variance + offset, vars[code].nmiss);
                  }
              }
          }

      FreeSamp(vars);
      FreeGrid(vars);
      return;
    }

  /* ------------------ */
  /*  Free mean fields  */
  /* ------------------ */

  if (globs.Type >= 40 && globs.Mean)
    for (int code = 0; code < MaxCodes; ++code)
      if (vars[code].mean)
        {
          if (vars[code].variance) vars[code].variance = (double *) FreeMemory(vars[code].variance);
          if (vars[code].grid) vars[code].grid = (double *) FreeMemory(vars[code].grid);
          vars[code].grid = vars[code].mean;
          vars[code].mean = nullptr;
        }

  /* --------------------------------- */
  /*  Transformation to fourier space  */
  /* --------------------------------- */

  if (globs.Type >= 40)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].grid && (vars[code].sfit || globs.Type < 70))
          {
            if (vars[code].nmiss) afterAbort("Missing values for code %d unsupported with TYPE > 30!", code);

            if (vars[code].fourier == nullptr)
              {
                const auto fieldSize = globs.DimFC * vars[code].plev;
                vars[code].fourier = alloc_dp(fieldSize, FieldName(code, "fourier"));
              }

            after_GP2FC(vars[code].grid, vars[code].fourier, globs.Latitudes, globs.Longitudes, vars[code].plev, globs.Fouriers);

            if (vars[code].grid && (vars[code].sfit || globs.Type < 70)) vars[code].grid = (double *) FreeMemory(vars[code].grid);
          }
    }

  for (int code = 0; code < MaxCodes; ++code)
    if (vars[code].grid && (vars[code].sfit || globs.Type < 70)) vars[code].grid = (double *) FreeMemory(vars[code].grid);

  /* -------------------------- */
  /*  Output of fourier fields  */
  /* -------------------------- */

  if (globs.Type == 40)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on fourier space!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, vars[code].nmiss);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* --------------------- */
  /* Output of zonal means */
  /* --------------------- */

  if (globs.Type == 41)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on zonal mean!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, vars[code].nmiss);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ---------------------------------- */
  /*  Transformation to spectral space  */
  /* ---------------------------------- */

  if (globs.Type >= 50)
    {
      if (vars[U_WIND].needed && vars[U_WIND].fourier)
        scaluv(vars[U_WIND].fourier, globs.coslat, globs.Latitudes, globs.Fouriers * globs.NumLevelRequest);
      if (vars[V_WIND].needed && vars[V_WIND].fourier)
        scaluv(vars[V_WIND].fourier, globs.coslat, globs.Latitudes, globs.Fouriers * globs.NumLevelRequest);

      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].fourier)
          {
            if (vars[code].spectral == nullptr)
              {
                const auto fieldSize = vars[code].plev * globs.DimSP;
                vars[code].spectral = alloc_dp(fieldSize, FieldName(code, "spectral"));
              }

            fc2sp(vars[code].fourier, vars[code].spectral, globs.pold, vars[code].plev, globs.Latitudes, globs.Fouriers,
                  globs.Truncation);
          }

      if (vars[DIVERGENCE].needed || vars[VORTICITY].needed || vars[VELOPOT].needed || vars[STREAM].needed)
        {
          if (vars[DIVERGENCE].spectral == nullptr)
            vars[DIVERGENCE].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[DIVERGENCE].spectral");
          if (vars[VORTICITY].spectral == nullptr)
            vars[VORTICITY].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[VORTICITY].spectral");
          if ((vars[U_WIND].fourier == 0 || vars[V_WIND].fourier == 0) && globs.NumLevelRequest)
            afterAbort("uwind or vwind missing!");
          uv2dv(vars[U_WIND].fourier, vars[V_WIND].fourier, vars[DIVERGENCE].spectral, vars[VORTICITY].spectral, globs.pol2,
                globs.pol3, globs.NumLevelRequest, globs.Latitudes, globs.Truncation);
        }

      if (vars[VELOPOT].needed)
        {
          vars[VELOPOT].hlev = vars[DIVERGENCE].hlev;
          vars[VELOPOT].plev = vars[DIVERGENCE].plev;
          vars[VELOPOT].sfit = true;
          if (vars[VELOPOT].spectral == nullptr)
            vars[VELOPOT].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[VELOPOT].spectral");
          dv2ps(vars[DIVERGENCE].spectral, vars[VELOPOT].spectral, globs.NumLevelRequest, globs.Truncation);
        }

      if (vars[STREAM].needed)
        {
          vars[STREAM].hlev = vars[VORTICITY].hlev;
          vars[STREAM].plev = vars[VORTICITY].plev;
          vars[STREAM].sfit = true;
          if (vars[STREAM].spectral == nullptr)
            vars[STREAM].spectral = alloc_dp(globs.DimSP * globs.NumLevelRequest, "vars[STREAM].spectral");
          dv2ps(vars[VORTICITY].spectral, vars[STREAM].spectral, globs.NumLevelRequest, globs.Truncation);
        }
    }

  for (int code = 0; code < MaxCodes; ++code)
    if (vars[code].fourier && (vars[code].sfit || globs.Type < 61)) vars[code].fourier = (double *) FreeMemory(vars[code].fourier);

  /* --------------------------- */
  /*  Output of spectral fields  */
  /* --------------------------- */

  if (globs.Type == 50)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected && vars[code].spectral)
          {
            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimSP;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].spectral + offset, 0);
              }
          }

      FreeSpectral(vars);
      return;
    }

  /* -------------------------------*/
  /*  Computations in fourier space */
  /* -------------------------------*/

  if (globs.Type >= 60)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].spectral)
          {
            if (vars[code].fourier == nullptr)
              {
                const auto fieldSize = vars[code].plev * globs.DimFC;
                vars[code].fourier = alloc_dp(fieldSize, FieldName(code, "fourier"));
              }
            sp2fc(vars[code].spectral, vars[code].fourier, globs.poli, vars[code].plev, globs.Latitudes, globs.Fouriers,
                  globs.Truncation);
          }
      if (vars[U_WIND].needed && vars[U_WIND].fourier)
        scaluv(vars[U_WIND].fourier, globs.rcoslat, globs.Latitudes, globs.Fouriers * globs.NumLevelRequest);
      if (vars[V_WIND].needed && vars[V_WIND].fourier)
        scaluv(vars[V_WIND].fourier, globs.rcoslat, globs.Latitudes, globs.Fouriers * globs.NumLevelRequest);
    }

  FreeSpectral(vars);

  /* -------------------------- */
  /*  Output of fourier fields  */
  /* -------------------------- */

  if (globs.Type == 60)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on fourier space!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, 0);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ----------------------- */
  /*  Output of zonal means  */
  /* ----------------------- */

  if (globs.Type == 61)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            if (!vars[code].fourier) afterAbort("Code %d not available on zonal mean!", code);

            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimFC;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].fourier + offset, vars[code].nmiss);
              }
          }

      FreeFourier(vars);
      return;
    }

  /* ------------------------------ */
  /*  Transformation to gridpoints  */
  /* ------------------------------ */

  if (globs.Type >= 70)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].needed && vars[code].fourier)
          {
            const auto fieldSize = vars[code].plev * globs.DimGP;
            if (vars[code].grid == nullptr) vars[code].grid = alloc_dp(fieldSize, FieldName(code, "grid"));

            after_FC2GP(vars[code].fourier, vars[code].grid, globs.Latitudes, globs.Longitudes, vars[code].plev, globs.Fouriers);
          }
    }

  FreeFourier(vars);

  /* -------------------------------- */
  /*  Output of pressure level grids  */
  /* -------------------------------- */

  if (globs.Type == 70)
    {
      for (int code = 0; code < MaxCodes; ++code)
        if (vars[code].selected)
          {
            const int nlevels = zaxisInqSize(vars[code].ozaxisID);
            for (int k = 0; k < nlevels; ++k)
              {
                const auto offset = k * globs.DimGP;
                streamDefRecord(globs.ostreamID, vars[code].ovarID, k);
                streamWriteRecord(globs.ostreamID, vars[code].grid + offset, vars[code].nmiss);
              }
          }

      FreeSamp(vars);
      FreeGrid(vars);
      return;
    }
}

void
after_AnalysisAddRecord(const AfterControl *globs, struct Variable *vars, int code, int gridID, int zaxisID, int levelID,
                        size_t nmiss)
{
  int truncation;

  auto gridtype = gridInqType(gridID);
  auto leveltype = zaxisInqType(zaxisID);
  auto nlevel = zaxisInqSize(zaxisID);
  auto gridSize = gridInqSize(gridID);
  auto dataSize = gridSize * nlevel;
  auto dataOffset = gridSize * levelID;

  vars[code].nmiss0 += nmiss;

  if (gridtype == GRID_SPECTRAL)
    {
      vars[code].sfit = true;
      vars[code].hlev = globs->NumLevelRequest;
      vars[code].plev = globs->NumLevelRequest;
      if (nlevel > 1 && leveltype == ZAXIS_PRESSURE)
        {
          if (code != U_WIND && code != V_WIND)
            {
              const auto fieldSize = globs->Dim3SP;
              if (vars[code].spectral0 == nullptr) vars[code].spectral0 = alloc_dp(fieldSize, FieldName(code, "spectral"));
              truncation = gridInqTrunc(gridID);
              sp2sp(globs->Field, truncation, vars[code].spectral0 + levelID * globs->DimSP, globs->Truncation);
            }
          else
            {
              const auto fieldSize = globs->Dim3SP;
              if (vars[code].spectral0 == nullptr) vars[code].spectral0 = alloc_dp(fieldSize, FieldName(code, "spectral"));
              array_copy(globs->DimSP, globs->Field, vars[code].spectral0 + levelID * globs->DimSP);
            }
        }
      else
        afterAbort("Only pressure level data supported for spectral data!");
    }
  else
    {
      if (nlevel > 1 && leveltype == ZAXIS_PRESSURE)
        {
          vars[code].sfit = true;
          const auto fieldSize = globs->Dim3GP;
          vars[code].hlev = globs->NumLevelRequest;
          vars[code].plev = globs->NumLevelRequest;
          if (vars[code].grid0 == nullptr) vars[code].grid0 = alloc_dp(fieldSize, FieldName(code, "grid0"));
          array_copy(globs->DimGP, globs->Field, vars[code].grid0 + levelID * globs->DimGP);
        }
      else
        {
          vars[code].sfit = false;
          const auto fieldSize = globs->DimGP;
          vars[code].hlev = 1;
          vars[code].plev = 1;
          if (vars[code].grid0 == nullptr) vars[code].grid0 = alloc_dp(fieldSize, FieldName(code, "grid0"));
          array_copy(globs->DimGP, globs->Field, vars[code].grid0);
        }

      if (globs->Mean > 0 && (nmiss || vars[code].samp))
        {
          if (vars[code].samp == nullptr)
            {
              vars[code].samp = (int *) Malloc(dataSize * sizeof(int));
              for (size_t i = 0; i < dataSize; ++i) vars[code].samp[i] = globs->MeanCount0;
            }

          for (size_t i = 0; i < gridSize; ++i)
            if (IS_NOT_EQUAL(globs->Field[i], vars[code].missval)) vars[code].samp[i + dataOffset]++;
        }
    }
}

double *
after_get_dataptr(struct Variable *vars, int code, int gridID, int zaxisID, int levelID)
{
  const auto gridtype = gridInqType(gridID);
  const auto nlevel = zaxisInqSize(zaxisID);
  const auto gridSize = gridInqSize(gridID);
  const auto dataSize = gridSize * nlevel;
  const auto dataOffset = gridSize * levelID;

  double *dataptr = nullptr;

  if (gridtype == GRID_SPECTRAL)
    {
      if (vars[code].spectral0 == nullptr) vars[code].spectral0 = alloc_dp(dataSize, FieldName(code, "spectral0"));

      dataptr = vars[code].spectral0 + dataOffset;
    }
  else
    {
      if (vars[code].hybrid0 == nullptr) vars[code].hybrid0 = alloc_dp(dataSize, FieldName(code, "hybrid0"));

      dataptr = vars[code].hybrid0 + dataOffset;
    }

  return dataptr;
}

void
after_EchamAddRecord(const AfterControl *globs, struct Variable *vars, int code, int gridID, int zaxisID, int levelID, size_t nmiss)
{
  const auto gridtype = gridInqType(gridID);
  const auto leveltype = zaxisInqType(zaxisID);
  const auto nlevel = zaxisInqSize(zaxisID);
  const auto gridSize = gridInqSize(gridID);
  const auto dataSize = gridSize * nlevel;
  const auto dataOffset = gridSize * levelID;

  vars[code].nmiss0 += nmiss;

  if (gridtype == GRID_SPECTRAL)
    {
      vars[code].sfit = true;
      vars[code].hlev = nlevel;
      vars[code].plev = 1;
      if (nlevel > 1 && leveltype == ZAXIS_HYBRID) vars[code].plev = globs->NumLevelRequest;

      if (gridInqTrunc(gridID) != globs->Truncation)
        afterAbort("Resolution error. Required %d - Found %d", globs->Truncation, gridInqTrunc(gridID));
    }
  else
    {
      vars[code].hlev = nlevel;
      vars[code].plev = nlevel;
      vars[code].sfit = false;
      if (nlevel > 1 && leveltype == ZAXIS_HYBRID && (nlevel == globs->NumLevel || nlevel == globs->NumLevel + 1))
        {
          vars[code].plev = globs->NumLevelRequest;
          vars[code].sfit = true;
        }

      if (globs->Mean > 0 && (nmiss || vars[code].samp))
        {
          if (vars[code].samp == nullptr)
            {
              vars[code].samp = (int *) Malloc(dataSize * sizeof(int));
              for (size_t i = 0; i < dataSize; ++i) vars[code].samp[i] = globs->MeanCount0;
            }

          double *dataptr = vars[code].hybrid0 + dataOffset;
          for (size_t i = 0; i < gridSize; ++i)
            if (IS_NOT_EQUAL(dataptr[i], vars[code].missval)) vars[code].samp[i + dataOffset]++;
        }
    }
}

static void
MakeDependencies(struct Variable *vars, int varcode, int depcode)
{
  if (vars[varcode].needed && !vars[varcode].detected)
    {
      vars[depcode].needed = true;
      vars[varcode].comp = true;

      if (afterDebug) fprintf(stderr, "Needed code %d to compute code %d\n", depcode, varcode);

      if (vars[depcode].ivarID == -1)
        {
          if (depcode == U_WIND)
            {
              MakeDependencies(vars, U_WIND, DIVERGENCE);
              MakeDependencies(vars, U_WIND, VORTICITY);
            }
          if (depcode == V_WIND)
            {
              MakeDependencies(vars, V_WIND, DIVERGENCE);
              MakeDependencies(vars, V_WIND, VORTICITY);
            }
        }

      if (vars[varcode].ivarID == -1)
        {
          if (vars[depcode].ivarID == -1)
            {
              afterAbort("code %d undefined, needed to compute code %d", depcode, varcode);
            }
          else
            {
              vars[varcode].ivarID = vars[depcode].ivarID;
              vars[varcode].igridID = vars[depcode].igridID;
              vars[varcode].ogridID = vars[depcode].ogridID;
              vars[varcode].izaxisID = vars[depcode].izaxisID;
              vars[varcode].ozaxisID = vars[depcode].ozaxisID;
            }
        }
    }
}

static void
CheckDependencies(struct Variable *vars, int analysisdata)
{
  MakeDependencies(vars, VELOPOT, U_WIND);
  MakeDependencies(vars, VELOPOT, V_WIND);
  MakeDependencies(vars, VELOPOT, VORTICITY);
  MakeDependencies(vars, VELOPOT, DIVERGENCE);

  MakeDependencies(vars, STREAM, U_WIND);
  MakeDependencies(vars, STREAM, V_WIND);
  MakeDependencies(vars, STREAM, VORTICITY);
  MakeDependencies(vars, STREAM, DIVERGENCE);

  MakeDependencies(vars, VORTICITY, U_WIND);
  MakeDependencies(vars, VORTICITY, V_WIND);

  MakeDependencies(vars, DIVERGENCE, U_WIND);
  MakeDependencies(vars, DIVERGENCE, V_WIND);

  MakeDependencies(vars, U_WIND, VORTICITY);
  MakeDependencies(vars, U_WIND, DIVERGENCE);
  MakeDependencies(vars, U_WIND, V_WIND);

  MakeDependencies(vars, V_WIND, VORTICITY);
  MakeDependencies(vars, V_WIND, DIVERGENCE);
  MakeDependencies(vars, V_WIND, U_WIND);

  MakeDependencies(vars, WINDSPEED, U_WIND);
  MakeDependencies(vars, WINDSPEED, V_WIND);

  if (analysisdata)
    {
      MakeDependencies(vars, RHUMIDITY, HUMIDITY);
      MakeDependencies(vars, RHUMIDITY, TEMPERATURE);
      MakeDependencies(vars, HUMIDITY, RHUMIDITY);
      MakeDependencies(vars, HUMIDITY, TEMPERATURE);
      MakeDependencies(vars, GEOPOTHEIGHT, GEOPOTENTIAL);
    }
  else
    {
      MakeDependencies(vars, THETAF, TEMPERATURE);
      MakeDependencies(vars, SLP, TEMPERATURE);
    }

  MakeDependencies(vars, SW_ATM, 176);
  MakeDependencies(vars, SW_ATM, 178);
  MakeDependencies(vars, LW_ATM, 177);
  MakeDependencies(vars, LW_ATM, 179);
  MakeDependencies(vars, NET_ATM, 176);
  MakeDependencies(vars, NET_ATM, 177);
  MakeDependencies(vars, NET_ATM, 178);
  MakeDependencies(vars, NET_ATM, 179);

  MakeDependencies(vars, SW_BOT_CLF, 176);
  MakeDependencies(vars, SW_BOT_CLF, 185);
  MakeDependencies(vars, LW_BOT_CLF, 177);
  MakeDependencies(vars, LW_BOT_CLF, 186);
  MakeDependencies(vars, SW_TOP_CLF, 178);
  MakeDependencies(vars, SW_TOP_CLF, 187);
  MakeDependencies(vars, LW_TOP_CLF, 179);
  MakeDependencies(vars, LW_TOP_CLF, 188);
  MakeDependencies(vars, NET_TOP_CLF, 178);
  MakeDependencies(vars, NET_TOP_CLF, 179);
  MakeDependencies(vars, NET_TOP_CLF, 187);
  MakeDependencies(vars, NET_TOP_CLF, 188);

  MakeDependencies(vars, ALL_WATER, 222);
  MakeDependencies(vars, LOW_WATER, 222);
  MakeDependencies(vars, MID_WATER, 222);
  MakeDependencies(vars, HIH_WATER, 222);

  MakeDependencies(vars, LOW_CLOUD, 223);
  MakeDependencies(vars, MID_CLOUD, 223);
  MakeDependencies(vars, HIH_CLOUD, 223);

  if (vars[LOW_CLOUD].comp || vars[MID_CLOUD].comp || vars[HIH_CLOUD].comp)
    {
      static int zaxisID = -999;
      if (zaxisID == -999) zaxisID = zaxisCreate(ZAXIS_SURFACE, 1);

      vars[LOW_CLOUD].izaxisID = zaxisID;
      vars[LOW_CLOUD].ozaxisID = zaxisID;
      vars[MID_CLOUD].izaxisID = zaxisID;
      vars[MID_CLOUD].ozaxisID = zaxisID;
      vars[HIH_CLOUD].izaxisID = zaxisID;
      vars[HIH_CLOUD].ozaxisID = zaxisID;
    }
}

void
after_AnalysisDependencies(struct Variable *vars, int ncodes)
{
  for (int code = 0; code < ncodes; ++code) vars[code].needed = vars[code].selected;

  MakeDependencies(vars, PS, LNPS);

  CheckDependencies(vars, 1);
}

void
after_EchamDependencies(struct Variable *vars, int ncodes, int type, int source)
{
  for (int code = 0; code < ncodes; ++code) vars[code].needed = vars[code].selected;

  for (int code = 0; code < ncodes; ++code)
    if (vars[code].detected == false) vars[code].ivarID = -1;

  if (type >= 50)
    {
      vars[U_WIND].needed |= vars[DIVERGENCE].needed;
      vars[V_WIND].needed |= vars[DIVERGENCE].needed;
      vars[U_WIND].needed |= vars[VORTICITY].needed;
      vars[V_WIND].needed |= vars[VORTICITY].needed;
      vars[U_WIND].needed |= vars[VELOPOT].needed;
      vars[V_WIND].needed |= vars[VELOPOT].needed;
      vars[U_WIND].needed |= vars[STREAM].needed;
      vars[V_WIND].needed |= vars[STREAM].needed;
    }

  if (type >= 30) vars[LNPS].needed = true;

  if (type >= 20)
    {
      MakeDependencies(vars, THETAF, LNPS);
      MakeDependencies(vars, SLP, LNPS);
      MakeDependencies(vars, SLP, GEOPOTENTIAL);
      /*
      MakeDependencies(vars, SLP, HALF_PRESS);
      MakeDependencies(vars, SLP, FULL_PRESS);
      */
      MakeDependencies(vars, RHUMIDITY, TEMPERATURE);
      MakeDependencies(vars, RHUMIDITY, HUMIDITY);
      MakeDependencies(vars, RHUMIDITY, LNPS);
      MakeDependencies(vars, GEOPOTHEIGHT, TEMPERATURE);
      MakeDependencies(vars, GEOPOTHEIGHT, HUMIDITY);
      MakeDependencies(vars, GEOPOTHEIGHT, LNPS);
      MakeDependencies(vars, OMEGA, DIVERGENCE);
      MakeDependencies(vars, OMEGA, U_WIND);
      MakeDependencies(vars, OMEGA, V_WIND);
      MakeDependencies(vars, OMEGA, LNPS);
      MakeDependencies(vars, OMEGA, DPSDX);
      MakeDependencies(vars, OMEGA, DPSDY);
    }

  MakeDependencies(vars, DPSDX, LNPS);
  MakeDependencies(vars, HALF_PRESS, LNPS);
  MakeDependencies(vars, PS, LNPS);

  MakeDependencies(vars, SURF_RUNOFF, 142);
  MakeDependencies(vars, SURF_RUNOFF, 143);
  MakeDependencies(vars, SURF_RUNOFF, 182);
  MakeDependencies(vars, SURF_RUNOFF, 221); /* snow depth change */

  MakeDependencies(vars, THETAF, TS);

  MakeDependencies(vars, FRESH_WATER, 142);
  MakeDependencies(vars, FRESH_WATER, 143);
  MakeDependencies(vars, FRESH_WATER, 182);

  MakeDependencies(vars, PRECIP, 142);
  MakeDependencies(vars, PRECIP, 143);

  if (source != S_ECHAM5)
    {
      MakeDependencies(vars, NET_WATER, 142);
      MakeDependencies(vars, NET_WATER, 143);
      MakeDependencies(vars, NET_WATER, 160);
      MakeDependencies(vars, NET_WATER, 182);

      MakeDependencies(vars, NET_TOP, 178);
      MakeDependencies(vars, NET_TOP, 179);

      MakeDependencies(vars, NET_BOT, 176);
      MakeDependencies(vars, NET_BOT, 177);

      MakeDependencies(vars, NET_HEAT, 146);
      MakeDependencies(vars, NET_HEAT, 147);
      MakeDependencies(vars, NET_HEAT, 176);
      MakeDependencies(vars, NET_HEAT, 177);
      MakeDependencies(vars, NET_HEAT, 218);
      /*
        if ( source == S_ECHAM5 )
        {
        MakeDependencies(vars, NET_HEAT, 206);
        MakeDependencies(vars, NET_HEAT, 208);
        MakeDependencies(vars, NET_HEAT, 209);
        }
        else
      */
      {
        MakeDependencies(vars, NET_HEAT, 220);
      }
    }

  MakeDependencies(vars, SW_CLF, 178);
  MakeDependencies(vars, SW_CLF, 224);

  MakeDependencies(vars, LW_CLF, 179);
  MakeDependencies(vars, LW_CLF, 225);

  MakeDependencies(vars, NET_CLF, 178);
  MakeDependencies(vars, NET_CLF, 179);
  MakeDependencies(vars, NET_CLF, 224);
  MakeDependencies(vars, NET_CLF, 225);

  if (vars[DPSDX].needed || vars[DPSDY].needed || vars[GEOPOTHEIGHT].comp || vars[SLP].comp || vars[THETAF].needed
      || vars[HALF_PRESS].needed || vars[RHUMIDITY].comp || vars[OMEGA].comp || type >= 30)
    vars[PS_PROG].comp = true;

  CheckDependencies(vars, 0);
}


void
after_legini_setup(AfterControl &globs, struct Variable *vars)
{
  const long ntr = globs.Truncation;
  const long nlat = globs.Latitudes;
  const long dimsp = (ntr + 1) * (ntr + 2);
  const long pdim = (dimsp / 2) * nlat;

  globs.poli = (double *) Malloc(pdim * sizeof(double));

  if (!globs.AnalysisData)
    {
      if (globs.Type >= 20) globs.pold = (double *) Malloc(pdim * sizeof(double));
      if (vars[DPSDY].needed) globs.pdev = (double *) Malloc(pdim * sizeof(double));
    }

  if ((vars[DIVERGENCE].needed || vars[VORTICITY].needed || vars[VELOPOT].needed || vars[STREAM].needed) && globs.Type > 20)
    {
      globs.pol2 = (double *) Malloc(pdim * sizeof(double));
      globs.pol3 = (double *) Malloc(pdim * sizeof(double));
    }

  if (globs.AnalysisData && (globs.Type == 70) && globs.Gaussian && !globs.Spectral)
    {
      if (globs.poli)
        {
          Free(globs.poli);
          globs.poli = nullptr;
        }
      if (globs.pol2)
        {
          Free(globs.pol2);
          globs.pol2 = nullptr;
        }
      if (globs.pol3)
        {
          Free(globs.pol3);
          globs.pol3 = nullptr;
        }
      return;
    }

  after_legini_full(ntr, nlat, globs.poli, globs.pold, globs.pdev, globs.pol2, globs.pol3, globs.coslat);

  for (long jgl = 0; jgl < nlat; ++jgl) globs.rcoslat[jgl] = 1.0 / globs.coslat[jgl];

  for (long jgl = 0; jgl < nlat; ++jgl) globs.DerivationFactor[jgl] = globs.rcoslat[jgl] / PlanetRadius;
}
