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

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

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

/*
   This module contains the following operators:

      EcaEtccdi    eca_etccdi         Etccdi conform indices
*/

#include <cdi.h>

#include "cdo_options.h"
#include "cdo_vlist.h"
#include "datetime.h"
#include "functs.h"
#include "process_int.h"
#include "param_conversion.h"
#include "calendar.h"
#include "percentiles_hist.h"
#include "percentiles.h"
#include "ecautil.h"
#include "ecacore.h"

enum functions_select
{
  func_selle = 1,
  func_selge = 2
};

static const char TX90P_UNITS[]        = "%";
static const char TX90P_NAME[]         = "tx90pETCCDI";
static const char TX90P_LONGNAME[]     = "Percentage of Days when Daily Maximum Temperature is Above the 90th Percentile";

static const char TX10P_UNITS[]        = "%";
static const char TX10P_NAME[]         = "tx10pETCCDI";
static const char TX10P_LONGNAME[]     = "Percentage of Days when Daily Maximum Temperature is Below the 10th Percentile";

static const char TN90P_UNITS[]        = "%";
static const char TN90P_NAME[]         = "tn90pETCCDI";
static const char TN90P_LONGNAME[]     = "Percentage of Days when Daily Minimum Temperature is Above the 90th Percentile";

static const char TN10P_UNITS[]        = "%";
static const char TN10P_NAME[]         = "tn10pETCCDI";
static const char TN10P_LONGNAME[]     = "Percentage of Days when Daily Minimum Temperature is Below the 10th Percentile";

static const char R99P_UNITS[]        = "mm";
static const char R99P_NAME[]         = "r99pETCCDI";
static const char R99P_LONGNAME[]     = "Annual Total Precipitation when Daily Precipitation Exceeds the 99th Percentile of Wet Day Precipitation";

static const char R95P_UNITS[]        = "mm";
static const char R95P_NAME[]         = "r95pETCCDI";
static const char R95P_LONGNAME[]     = "Annual Total Precipitation when Daily Precipitation Exceeds the 95th Percentile of Wet Day Precipitation";

/* windowDays()
   Saves for each bootstrap year
   for each day of year
   the day of each window day */
static void
windowDays(int dayoy, std::vector<int> &wdays, const std::vector<bool> &wdaysRead, int MaxDays, int ndates, int sumboot)
{
  int wdayoy = dayoy;
  for ( int gobackDays = ceil(ndates / 2. - 1 ); gobackDays != 0; gobackDays-- )
    {
      wdayoy--;
      if ( wdayoy < 1 )
        wdayoy += (MaxDays-1)*sumboot;
      while ( wdaysRead[wdayoy] == false )
        {
          wdayoy--;
          if ( wdayoy == dayoy )
            cdoAbort("Too less timesteps!");
          if ( wdayoy < 1 )
            wdayoy += (MaxDays-1)*sumboot;
        }
    }
  int base = (dayoy-1)*ndates+1;
  wdays[base] = wdayoy;
  int nndates = 1;
  while ( nndates != ndates )
    {
      wdayoy++;
      if ( wdayoy > sumboot*(MaxDays - 1) )
        wdayoy -= sumboot*(MaxDays - 1);
      if ( wdaysRead[wdayoy] != false )
        {
          wdays[base+nndates] = wdayoy;
          nndates++;
        }
    }
}

static void writeTimesteps(int MaxMonths, int recentYear, FieldVector3D &cei, int frequency, int taxisID4, CdoStreamID streamID4, int *otsID, const std::vector<RecordInfo> &recinfo, int maxrecs, const std::vector<int> &tempdpm, int tempdpy, int func2)
{
  if ( frequency == 8 )
    {
      for ( int loopmonth = 2; loopmonth < MaxMonths+2; loopmonth++ )
        {              
          defineMidOfTime(frequency, taxisID4, recentYear, loopmonth, MaxMonths);
          cdoDefTimestep(streamID4, *otsID);
          for (int recID = 0; recID < maxrecs; recID++)
            {
              if (*otsID && recinfo[recID].lconst) continue;
              int varIDo = recinfo[recID].varID;
              int levelIDo = recinfo[recID].levelID;

              vfarcdiv(cei[loopmonth-2][0][levelIDo], (double) (tempdpm[loopmonth-2]/100.0));
              cdoDefRecord(streamID4, varIDo, levelIDo);
              cdoWriteRecord(streamID4, cei[loopmonth-2][0][levelIDo].vec_d.data(), cei[loopmonth-2][0][levelIDo].nmiss); 
            }
          (*otsID)++;
        }
    }
  else
    {
      defineMidOfTime(frequency, taxisID4, recentYear, 0, MaxMonths);
      cdoDefTimestep(streamID4, *otsID);
      for (int recID = 0; recID < maxrecs; recID++)
        {
          if (*otsID && recinfo[recID].lconst) continue;
          int varIDo = recinfo[recID].varID;
          int levelIDo = recinfo[recID].levelID;
          if (func2 == func_avg)
            vfarcdiv(cei[0][0][levelIDo], (double) (tempdpy/100.0));
          cdoDefRecord(streamID4, varIDo, levelIDo);
          cdoWriteRecord(streamID4, cei[0][0][levelIDo].vec_d.data(), cei[0][0][levelIDo].nmiss); 
        }
      (*otsID)++;
    }
}

static void calculateOuterPeriod(Field &field, int MaxMonths, int recentYear, int endOfCalc, FieldVector3D &cei, FieldVector3D &varsPtemp, int frequency, int taxisID4, CdoStreamID streamID4, int *otsID, int vlistID1, const std::vector<RecordInfo> &recinfo, int selection, int func2)
{
  if (cdoAssertFilesOnly() == false)
    cdoAbort("infile1 cannot be a pipe");
  const auto maxrecs = vlistNrecs(vlistID1);
  CdiStreamID cdiStream = streamOpenRead(cdoGetStreamName(0));

  const auto cdiVlistID = streamInqVlist(cdiStream);
  const auto cdiTaxisID = vlistInqTaxis(cdiVlistID);
  std::vector<int> tempdpm(MaxMonths);
  int tempdpy = 0;
  for ( int i = 0; i<MaxMonths; i++)
    tempdpm[i] = 0;
  int year, month, day, tsID = 0, nrecs = 0, varID, levelID;
  bool lHasStarted = false;
  if (Options::cdoVerbose) cdoPrint("Start to process variables");
  while ( ( nrecs = streamInqTimestep(cdiStream, tsID++) ) )
    {
      int64_t vdate = taxisInqVdate(cdiTaxisID);
      cdiDecodeDate(vdate, &year, &month, &day);
      if ( !lHasStarted && year != recentYear )
        continue;
      else if ( !lHasStarted )
        lHasStarted = true;

      if ( year != recentYear )
        {
          writeTimesteps(MaxMonths, recentYear, cei, frequency, taxisID4, streamID4, otsID, recinfo, maxrecs, tempdpm, tempdpy, func2);
          recentYear = year;
          tempdpy = 0;
          tempdpm[0] = 0;
          fieldFill(cei[0][0][0], 0.);
          if ( frequency == 8 )
            for ( int loopmonth = 1; loopmonth < MaxMonths; loopmonth++)
              {
                tempdpm[loopmonth] = 0;
                fieldFill(cei[loopmonth][0][0], 0.);
              }
        }
      if ( year == endOfCalc && func2 == func_avg )
        break;
      tempdpy++;
      int dayoy = (month >= 1 && month <= 12) ? (month - 1) * 31 + day : 0;
      tempdpm[month-1]++;  

      if ( func2 == func_sum )
        dayoy = 1;
      for (int recID = 0; recID < nrecs; recID++)
        {
          streamInqRecord(cdiStream, &varID, &levelID);
          streamReadRecord(cdiStream, field.vec_d.data(), &field.nmiss);

          Field &pctls = varsPtemp[dayoy][0][levelID];
          if ( selection == func_selle )
            vfarselle(field, pctls);
          else if ( selection == func_selge )
            vfarselge(field, pctls);

          auto &array = field.vec_d;
          if (func2 == func_avg)
            for (size_t i = 0; i < field.size; i++) array[i] = (DBL_IS_EQUAL(array[i], field.missval)) ? 0.0 : 1.0;
          else
            for (size_t i = 0; i < field.size; i++) array[i] = (DBL_IS_EQUAL(array[i], field.missval)) ? 0.0 : array[i];
          if ( frequency == 8 )
            vfaradd(cei[(int)((dayoy-1)/31.)][0][levelID], field);
          else
            vfaradd(cei[0][0][levelID], field);
        }
    }
  if (Options::cdoVerbose) cdoPrint("Finished Processing variables");
  if ( year != endOfCalc )
    writeTimesteps(MaxMonths, year, cei, frequency, taxisID4, streamID4, otsID, recinfo, maxrecs, tempdpm, tempdpy, func2);
  fieldFill(cei[0][0][0], 0.);
  if ( frequency == 8 )
    for ( int loopmonth = 1; loopmonth < MaxMonths; loopmonth++)
      {
        tempdpm[loopmonth] = 0;
        fieldFill(cei[loopmonth][0][0], 0.);
      }
  streamClose(cdiStream);    
}

void
etccdi_op(ETCCDI_REQUEST *request)
{
  constexpr int MaxDays = 373;
  constexpr int MaxMonths = 12;
  int varID;
  int nrecs;
  int levelID;
  size_t nmiss;
  int year, month, day, dayoy;
  int64_t vdate;
  int vtime;
  FieldVector2D vars2[MaxDays];
  HistogramSet hsets[MaxDays];

  const int operatorID = cdoOperatorID();
  auto selection = cdoOperatorF1(operatorID);
  auto frequency = cdoOperatorF2(operatorID);

  percentile_set_method("rtype8");

  int FIELD_MEMTYPE = 0;
  if (Options::CDO_Memtype == MemType::Float) FIELD_MEMTYPE = FIELD_FLT;

  bool wdaysSrc[MaxDays];

  if ( request->endboot < request->startboot )
    {
      cdoWarning("Your interval end '%d' is before the interval start '%d'. Switched interval years.", request->endboot, request->startboot);
      request->startboot = request->endboot;
      request->endboot = request->startboot;
    }
  int sumboot = request->endboot - request->startboot + 1;
  std::vector<bool> wdaysRead((MaxDays-1)*sumboot+1);
  std::vector<int> wdays(request->ndates*(MaxDays-1)*sumboot+1);
  std::vector<int> dpy(sumboot), dpm(MaxMonths*sumboot);

  for (year = 0; year < sumboot; year ++ )
    {
      dpy[year] = 0;
      for (month = 0; month < MaxMonths; month++ )
        dpm[month+year*MaxMonths] = 0;
    }

  const auto streamID1 = cdoOpenRead(0);
  const auto streamID2 = cdoOpenRead(1);
  const auto streamID3 = cdoOpenRead(2);

  const auto vlistID1 = cdoStreamInqVlist(streamID1);
  const auto vlistID2 = cdoStreamInqVlist(streamID2);
  const auto vlistID3 = cdoStreamInqVlist(streamID3);
  const auto vlistID4 = vlistDuplicate(vlistID1);

  vlistCompare(vlistID1, vlistID2, CMP_ALL);
  vlistCompare(vlistID1, vlistID3, CMP_ALL); 

  const auto taxisID1 = vlistInqTaxis(vlistID1);
  const auto taxisID2 = vlistInqTaxis(vlistID2);
  const auto taxisID3 = vlistInqTaxis(vlistID3);
  /* TODO - check that time axes 2 and 3 are equal */

  const auto taxisID4 = taxisDuplicate(taxisID1);
  if (taxisHasBounds(taxisID4)) taxisDeleteBounds(taxisID4);
  vlistDefTaxis(vlistID4, taxisID4);

  const auto streamID4 = cdoOpenWrite(3);

  const auto nvars = vlistNvars(vlistID1);
  bool lOnlyOneVar = true;;
  if ( nvars == 1 )
    {
      cdiDefKeyString(vlistID4, 0, CDI_KEY_NAME, request->name);
      cdiDefKeyString(vlistID4, 0, CDI_KEY_LONGNAME, request->longname);
      cdiDefKeyString(vlistID4, 0, CDI_KEY_UNITS, request->units);
      cdiDefAttTxt(vlistID4, 0, "cell_methods", (int) strlen("time: maximum"), "time: maximum");
    }
  else
    {
      lOnlyOneVar = false;
      cdoWarning("Your input file has more than one variable. No attributes can be set.");
    }

  cdoDefVlist(streamID4, vlistID4);

  const auto maxrecs = vlistNrecs(vlistID1);
  std::vector<RecordInfo> recinfo(maxrecs);

  const auto gridsizemax = vlistGridsizeMax(vlistID1);

  Field field;
  if ( FIELD_MEMTYPE == FIELD_FLT )
    field.resizef(gridsizemax);
  else
    field.resize(gridsizemax);

  FieldVector3D vars1(sumboot*(MaxDays-1)+1), cei(sumboot*MaxMonths), varsPtemp(MaxDays);
  // int64_t vdates2[sumboot*(MaxDays-1)+1], vdates1[MaxDays];
  // int vtimes2[sumboot*(MaxDays-1)+1] , vtimes2[MaxDays];
  for (dayoy = 0; dayoy < MaxDays; dayoy++)
    {
      fieldsFromVlist(vlistID1, vars1[dayoy], FIELD_VEC | FIELD_MEMTYPE);
      wdaysSrc[dayoy] = false;
      for (year = 0; year < sumboot; year++ )
        wdaysRead[dayoy+year*(MaxDays-1)] = false;
    }
  for (dayoy = MaxDays; dayoy < sumboot*(MaxDays-1)+1; dayoy++)
    fieldsFromVlist(vlistID1, vars1[dayoy], FIELD_VEC | FIELD_MEMTYPE);

  int tsID = 0;

  while ((nrecs = cdoStreamInqTimestep(streamID2, tsID)))
    {
      if (nrecs != cdoStreamInqTimestep(streamID3, tsID))
        cdoAbort("Number of records at time step %d of %s and %s differ!", tsID + 1, cdoGetStreamName(1),
                 cdoGetStreamName(2));

      vdate = taxisInqVdate(taxisID2);
      vtime = taxisInqVtime(taxisID2);

      if (vdate != taxisInqVdate(taxisID3))
        cdoAbort("Verification dates at time step %d of %s and %s differ!", tsID + 1, cdoGetStreamName(1),
                 cdoGetStreamName(2));

      if (Options::cdoVerbose) cdoPrint("process timestep: %d %d %d", tsID + 1, vdate, vtime);

      cdiDecodeDate(vdate, &year, &month, &day);

      if (month >= 1 && month <= 12)
        dayoy = (month - 1) * 31 + day;
      else
        dayoy = 0;

      if ( request->func2 == func_sum )
        dayoy = 1;

      if (dayoy < 0 || dayoy >= MaxDays) cdoAbort("Day %d out of range!", dayoy);

      if (!vars2[dayoy].size())
        {
          wdaysSrc[dayoy] = true;
          fieldsFromVlist(vlistID2, vars2[dayoy], FIELD_VEC | FIELD_MEMTYPE);
          fieldsFromVlist(vlistID2, varsPtemp[dayoy], FIELD_VEC);
          hsets[dayoy].create(nvars);

          for (varID = 0; varID < nvars; varID++)
            {
              const auto gridsize = gridInqSize(vlistInqVarGrid(vlistID2, varID));
              const auto nlevels = zaxisInqSize(vlistInqVarZaxis(vlistID2, varID));
              hsets[dayoy].createVarLevels(varID, nlevels, gridsize);
            }
        }

      for (int recID = 0; recID < nrecs; recID++)
        {
          cdoInqRecord(streamID2, &varID, &levelID);
          if ( FIELD_MEMTYPE == FIELD_FLT )
            cdoReadRecordF(streamID2, vars2[dayoy][varID][levelID].vec_f.data(), &nmiss);
          else
            cdoReadRecord(streamID2, vars2[dayoy][varID][levelID].vec_d.data(), &nmiss);
          vars2[dayoy][varID][levelID].nmiss = nmiss;
          varsPtemp[dayoy][varID][levelID].nmiss = nmiss;
        }
      for (int recID = 0; recID < nrecs; recID++)
        {
          cdoInqRecord(streamID3, &varID, &levelID);
          if ( FIELD_MEMTYPE == FIELD_FLT )
            cdoReadRecordF(streamID3, field.vec_f.data(), &nmiss);
          else
            cdoReadRecord(streamID3, field.vec_d.data(), &nmiss);
          field.nmiss = nmiss;
          field.grid = vars2[dayoy][varID][levelID].grid;
          field.missval = vars2[dayoy][varID][levelID].missval;

          if ( FIELD_MEMTYPE == FIELD_FLT )
            hsets[dayoy].defVarLevelBoundsF(varID, levelID, vars2[dayoy][varID][levelID], field);
          else
            hsets[dayoy].defVarLevelBounds(varID, levelID, vars2[dayoy][varID][levelID], field);
        }
/*      fieldsFree(vlistID2, vars2[dayoy]); */
      fieldsFromVlist(vlistID2, vars2[dayoy], FIELD_VEC);

      tsID++;
    }

  if (Options::cdoVerbose) cdoPrint("Defined the boundaries for the histograms");
  tsID = 0;
  bool lOnlyRefPeriod = true;
  int firstYear = 0, lastYear = 0;
  while ( (nrecs = cdoStreamInqTimestep(streamID1, tsID++) ) )
    {
      vdate = taxisInqVdate(taxisID1);
      vtime = taxisInqVtime(taxisID1);

      cdiDecodeDate(vdate, &year, &month, &day);
      if ( tsID == 1 ) 
        {
          if ( year > request->startboot )
            cdoAbort("The interval start year '%d' is before infile start year '%d'.", request->startboot, year);
          firstYear = year;
        }
      lastYear = year;

      if ( year >= request->startboot && year <= request->endboot )
        {
          dayoy = (month >= 1 && month <= 12) ? (month - 1) * 31 + day : 0;
          if (dayoy < 0 || dayoy >= MaxDays) cdoAbort("Day %d out of range!", dayoy);
          if (wdaysSrc[dayoy] || request->func2 == func_sum )
            {
/* Variable independent ? */
              wdaysRead[dayoy+(year-request->startboot)*(MaxDays-1)] = dayoy+(year-request->startboot)*(MaxDays-1);
              dpy[year-request->startboot]++;
              dpm[(year-request->startboot)*MaxMonths+(int)((dayoy-1)/31.)]++;
              for (int recID = 0; recID < nrecs; recID++)
                {
                  cdoInqRecord(streamID1, &varID, &levelID);
                  if ( FIELD_MEMTYPE == FIELD_FLT )
                    cdoReadRecordF(streamID1, vars1[dayoy+(year-request->startboot)*(MaxDays-1)][varID][levelID].vec_f.data(), &nmiss);
                  else
                    cdoReadRecord(streamID1, vars1[dayoy+(year-request->startboot)*(MaxDays-1)][varID][levelID].vec_d.data(), &nmiss);
                  vars1[dayoy+(year-request->startboot)*(MaxDays-1)][varID][levelID].nmiss = nmiss;
                  if ( tsID == 0 )
                    {
                      recinfo[recID].varID = varID;
                      recinfo[recID].levelID = levelID;
                      recinfo[recID].lconst = vlistInqVarTimetype(vlistID1, varID) == TIME_CONSTANT;
                    }
                }
            }
          else
            cdoWarning("Could not find histogram minimum or maximum for day of year: '%d'", dayoy);
        }
      else
        lOnlyRefPeriod = false;
    }
  if (Options::cdoVerbose) cdoPrint("Read in variables");

  if ( year < request->endboot )
    cdoAbort("The interval end year '%d' is after infile end year '%d'.", request->endboot, year);

  for (year = 0; year < sumboot; year ++ )
    {
      fieldsFromVlist(vlistID1, cei[year*MaxMonths], FIELD_VEC);
      if ( frequency == 8 )
        for ( month = 1; month < MaxMonths; month++)
          fieldsFromVlist(vlistID1, cei[year*MaxMonths+month], FIELD_VEC);
    }

/*  printf("Wir beginnen nun mit der Schleife.\n"); */
  int bootsyear = 0;
  int subyear = 0;
  int otsID = 0;

  for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
    {
      for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
        {
          if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
            {
              windowDays(loopdoy+ytoadd*(MaxDays-1), wdays, wdaysRead, MaxDays, request->ndates, sumboot);
            }
        }
    }
  if (Options::cdoVerbose) cdoPrint("Calculated window days");

  for (varID = 0; varID < nvars; varID++)
    {
      if (vlistInqVarTimetype(vlistID1, varID) == TIME_CONSTANT) continue;
      const auto nlevels = zaxisInqSize(vlistInqVarZaxis(vlistID1, varID));
      for (levelID = 0; levelID < nlevels; levelID++)
        {
          if ( request->func2 == func_sum )
            {
              for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
                {
                  for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
                    {
                      if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
                        {
                          Field &source = vars1[loopdoy+ytoadd*(MaxDays-1)][varID][levelID];
                          HistogramSet &hset = hsets[1];
                          hset.addSubVarLevelValues(varID, levelID, source, 1, FIELD_VEC | FIELD_MEMTYPE);
                        }
                    }
                }
            }
          else
            {
#ifdef _OPENMP
#pragma omp parallel for shared(sumboot, vars1, request, varID, levelID, hsets, wdays, wdaysRead) schedule(dynamic)
#endif
              for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
                {
                  for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
                    {
                      if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
                        {
                          for ( int ano = 0; ano < request->ndates; ano++)
                            {
                              Field &source = vars1[wdays[ytoadd*request->ndates*(MaxDays-1)+(loopdoy-1)*request->ndates+ano+1]][varID][levelID];
                              HistogramSet &hset = hsets[loopdoy];
                              hset.addSubVarLevelValues(varID, levelID, source, 1, FIELD_VEC | FIELD_MEMTYPE);
                            }
                        }
                    }
                }
            }
         }
     }
  tsID = 0;
  if (Options::cdoVerbose) cdoPrint("Added 30 years to histograms");
  if ( lOnlyOneVar && ( ( !lOnlyRefPeriod && firstYear != request->startboot ) || request->func2 == func_sum ) )
    {
      for (varID = 0; varID < nvars; varID++)
        {
          if (vlistInqVarTimetype(vlistID1, varID) == TIME_CONSTANT) continue;
          const auto nlevels = zaxisInqSize(vlistInqVarZaxis(vlistID1, varID));
          for (levelID = 0; levelID < nlevels; levelID++)
            {
              if ( request->func2 == func_sum )
                {
                  Field &pctls = varsPtemp[1][varID][levelID];
                  hsets[1].getVarLevelPercentiles(pctls, varID, levelID, request->pn, FIELD_MEMTYPE);
                }
              else 
                {
#ifdef _OPENMP
#pragma omp parallel for shared(request, wdaysSrc, varID, levelID, hsets, varsPtemp) schedule(dynamic)
#endif
                  for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
                    {
                      if (wdaysSrc[loopdoy])
                        {
                          Field &pctls = varsPtemp[loopdoy][varID][levelID];
                          hsets[loopdoy].getVarLevelPercentiles(pctls, varID, levelID, request->pn, FIELD_MEMTYPE);
                        }
                    }
                }
            }
        }
      field.resize(gridsizemax);
      calculateOuterPeriod(field, MaxMonths, firstYear, request->startboot, cei, varsPtemp, frequency, taxisID4, streamID4, &otsID, vlistID1, recinfo, selection, request->func2);
    }
  else if ( !lOnlyRefPeriod && firstYear != request->startboot )
    cdoWarning("Since you have more than one variable in the input file, only the bootstrapping period can be calculated");

  if ( request->func2 == func_avg )
    {
      for ( bootsyear = request->startboot; bootsyear < request->endboot+1; bootsyear++ )
        {
          if (Options::cdoVerbose) cdoPrint("Bootsyear: %d\n", bootsyear);
          for (varID = 0; varID < nvars; varID++)
            {
              if (vlistInqVarTimetype(vlistID1, varID) == TIME_CONSTANT) continue;
              const auto nlevels = zaxisInqSize(vlistInqVarZaxis(vlistID1, varID));
              for (levelID = 0; levelID < nlevels; levelID++)
                 {
#ifdef _OPENMP
#pragma omp parallel for shared(sumboot, wdaysRead, request, vars1, varID, levelID, hsets, wdays, cei) schedule(dynamic)
#endif
                   for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
                     { 
                       for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
                         {
                           if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
                             {
                               for ( int ano = 0; ano < request->ndates; ano++)
                                 {
                                    int recentWday = ytoadd*request->ndates*(MaxDays-1)+(loopdoy-1)*request->ndates+ano+1;
                                    if ( (int((wdays[recentWday]-1)/(MaxDays-1))+request->startboot) == bootsyear )
                                      {
                                        Field &source = vars1[wdays[recentWday]][varID][levelID];
                                        HistogramSet &hset = hsets[loopdoy];
                                        hset.addSubVarLevelValues(varID, levelID, source, 2, FIELD_VEC | FIELD_MEMTYPE);
                                      }
    /*percyear cannot be smaller than request->startboot */
                                    if ( (int((wdays[recentWday]-1)/(MaxDays-1))+request->startboot) == bootsyear - 1 )
                                      {
                                        Field &source = vars1[wdays[recentWday]][varID][levelID];
                                        HistogramSet &hset = hsets[loopdoy];
                                        hset.addSubVarLevelValues(varID, levelID, source, 1, FIELD_VEC | FIELD_MEMTYPE);
                                      }
                                  }
                             }
                         }
                     }
                   for ( subyear = request->startboot; subyear < request->endboot+1; subyear++ )
                     {
                       if (Options::cdoVerbose) cdoPrint("Subyear: %d\n", subyear);
                       if ( subyear != bootsyear )
                         {
#ifdef _OPENMP
#pragma omp parallel for shared(sumboot, request, vars1, varID, levelID, hsets, wdaysRead, varsPtemp, vars2, cei, subyear, bootsyear, wdays, frequency) schedule(dynamic)
#endif
                           for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
                             { 
                               for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
                                 {
                                   if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
                                     {
                                       for ( int ano = 0; ano < request->ndates; ano++)
                                         {   
                                           int recentWday = ytoadd*request->ndates*(MaxDays-1)+(loopdoy-1)*request->ndates+ano+1;
                                           if ( (int((wdays[recentWday]-1)/(MaxDays-1))+request->startboot) == subyear )
                                             {
                                                   Field &source = vars1[wdays[recentWday]][varID][levelID];
                                                   HistogramSet &hset = hsets[loopdoy];
                                                   if ( hset.addSubVarLevelValues(varID, levelID, source, 1, FIELD_VEC | FIELD_MEMTYPE) == 1 )
                                                     cdoPrint("'%d', '%d", loopdoy, wdays[recentWday]);
                                             }
                                         }
                                     }
                                 }
    /*                      printf("Haben es zum temp array addiert.\n");*/
    
    /*** Calculate percentile  ***/
                               if ( wdaysRead[loopdoy+(bootsyear-request->startboot)*(MaxDays-1)] )
                                     {
                                       Field &pctls = varsPtemp[loopdoy][varID][levelID];
                                       hsets[loopdoy].getVarLevelPercentiles(pctls, varID, levelID, request->pn, FIELD_MEMTYPE);
    /*** Compare data with percentile ***/
                                       Field &source = vars1[loopdoy+(bootsyear-request->startboot)*(MaxDays-1)][varID][levelID];
                                       Field &toCompare = vars2[loopdoy][varID][levelID];
                                       vfarcpy(toCompare, source); 
                                       if ( selection == func_selle )
                                         vfarselle(toCompare, pctls);
                                       else if ( selection == func_selge )
                                         vfarselge(toCompare, pctls);
                                       if ( request->func2 == func_avg )
                                         {
                                           auto &array = toCompare.vec_d;
                                           for (size_t i = 0; i < toCompare.size; i++) array[i] = (DBL_IS_EQUAL(array[i], toCompare.missval)) ? 0.0 : 1.0;
                                         }
                                       else
                                         {
                                           auto &array = toCompare.vec_d;
                                           for (size_t i = 0; i < toCompare.size; i++) array[i] = (DBL_IS_EQUAL(array[i], toCompare.missval)) ? 0.0 : array[i];
                                         }
    /*                      printf("Haben ein Percentil berechnet.\n");*/
    /* Year sum */
                                        if ( frequency == 8 )
#ifdef _OPENMP
#pragma omp critical
#endif
                                          vfaradd(cei[(bootsyear-request->startboot)*MaxMonths+(int)((loopdoy-1)/31.)][varID][levelID], vars2[loopdoy][varID][levelID]);
                                        else
#ifdef _OPENMP
#pragma omp critical
#endif
                                         vfaradd(cei[(bootsyear-request->startboot)*MaxMonths][varID][levelID], vars2[loopdoy][varID][levelID]);
                                     }
                               for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
                                 {
                                   if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
                                     {
                                       for ( int ano = 0; ano < request->ndates; ano++)
                                         {           
                                           int recentWday = ytoadd*request->ndates*(MaxDays-1)+(loopdoy-1)*request->ndates+ano+1;                        
                                           if ( (int((wdays[recentWday]-1)/(MaxDays-1))+request->startboot) == subyear )
                                             {
                                                   Field &source = vars1[wdays[recentWday]][varID][levelID];
                                                   HistogramSet &hset = hsets[loopdoy];
                                                   if ( hset.addSubVarLevelValues(varID, levelID, source, 2, FIELD_VEC | FIELD_MEMTYPE) == 1 )
                                                     cdoPrint("'%d', '%d", loopdoy, wdays[recentWday]);
                                             }
                                         }
                                     }
                                 }
                            }
                        }
                    }
                  if ( frequency == 8 )
                    {
                      for ( month = 0; month < MaxMonths; month++ )
                        if (cei[(bootsyear-request->startboot)*MaxMonths+month][varID][levelID].vec_d.data())
                          {
/* Divide vars2 to receive average */
                            vfarcdiv(cei[(bootsyear-request->startboot)*MaxMonths+month][varID][levelID], (double) ((sumboot-1)*dpm[(bootsyear-request->startboot)*MaxMonths+month]/100.0));
                          } 
                    }
                  else if (cei[(bootsyear-request->startboot)*MaxMonths][varID][levelID].vec_d.data())
                    {                  
                      vfarcdiv(cei[(bootsyear-request->startboot)*MaxMonths][varID][levelID], (double) ((sumboot-1)*dpy[bootsyear-request->startboot]/100.0));
                    }
                }
            }
          if ( frequency == 8 )
            {
              for ( month = 2; month < MaxMonths+2; month++ )
                {
                  defineMidOfTime(frequency, taxisID4, bootsyear, month, MaxMonths);
                  cdoDefTimestep(streamID4, otsID);
    
                  for (int recID = 0; recID < maxrecs; recID++)
                    {
                      if (otsID && recinfo[recID].lconst) continue;
                      int varIDo = recinfo[recID].varID;
                      int levelIDo = recinfo[recID].levelID;
                      cdoDefRecord(streamID4, varIDo, levelIDo);
                      cdoWriteRecord(streamID4, cei[(bootsyear-request->startboot)*MaxMonths+(month-2)][varIDo][levelIDo].vec_d.data(), cei[(bootsyear-request->startboot)*MaxMonths+(month-2)][varIDo][levelIDo].nmiss); 
                    } 
                  otsID++;
                }
            }
          else
            {
              defineMidOfTime(frequency, taxisID4, bootsyear, 0, MaxMonths);
              cdoDefTimestep(streamID4, otsID);
    
              for (int recID = 0; recID < maxrecs; recID++)
                {
                  if (otsID && recinfo[recID].lconst) continue;
                  int varIDo = recinfo[recID].varID;
                  int levelIDo = recinfo[recID].levelID;
                  cdoDefRecord(streamID4, varIDo, levelIDo);
                  cdoWriteRecord(streamID4, cei[(bootsyear-request->startboot)*MaxMonths][varIDo][levelIDo].vec_d.data(), cei[(bootsyear-request->startboot)*MaxMonths][varIDo][levelIDo].nmiss); 
                } 
              otsID++;
            }
/*      printf("Haben ein Mittel für Jahr '%d' berechnet.\n", bootsyear); */
        }
    }
  if ( !lOnlyRefPeriod && lOnlyOneVar  && lastYear != request->endboot && request->func2 == func_avg)
    {
      fieldFill(cei[0][0][0], 0.);
      if ( frequency == 8 )
        for ( int loopmonth = 1; loopmonth < MaxMonths; loopmonth++)
          fieldFill(cei[loopmonth][0][0], 0.);

      for (varID = 0; varID < nvars; varID++)
        {
          if (vlistInqVarTimetype(vlistID1, varID) == TIME_CONSTANT) continue;
          const auto nlevels = zaxisInqSize(vlistInqVarZaxis(vlistID1, varID));
          for (levelID = 0; levelID < nlevels; levelID++)
            {
#ifdef _OPENMP
#pragma omp parallel for shared(request, wdaysRead, varID, levelID, hsets, varsPtemp) schedule(dynamic)
#endif
              for ( int loopdoy = 1; loopdoy < MaxDays; loopdoy++)
                {
                   for ( int ytoadd = 0; ytoadd < sumboot; ytoadd++)
                     {
                       if ( wdaysRead[loopdoy+ytoadd*(MaxDays-1)] )
                         {
                           for ( int ano = 0; ano < request->ndates; ano++)
                             {
                                int recentWday = ytoadd*request->ndates*(MaxDays-1)+(loopdoy-1)*request->ndates+ano+1;
/*percyear cannot be smaller than request->startboot */
                                if ( (int((wdays[recentWday]-1)/(MaxDays-1))+request->startboot) == bootsyear - 1 )
                                  {
                                    Field &source = vars1[wdays[recentWday]][varID][levelID];
                                    HistogramSet &hset = hsets[loopdoy];
                                    hset.addSubVarLevelValues(varID, levelID, source, 1, FIELD_VEC | FIELD_MEMTYPE);
                                  }
                              }
                          }
                     }
                   if ( wdaysSrc[loopdoy] )
                     {
                       Field &pctls = varsPtemp[loopdoy][varID][levelID];
                       hsets[loopdoy].getVarLevelPercentiles(pctls, varID, levelID, request->pn, FIELD_MEMTYPE);
                     }
                }
            }
        }
      field.resize(gridsizemax);
      field.missval = vars1[1][0][0].missval;
      field.grid = vars1[1][0][0].grid;
      calculateOuterPeriod(field, MaxMonths, request->endboot+1, lastYear+1, cei, varsPtemp, frequency, taxisID4, streamID4, &otsID, vlistID1, recinfo, selection, request->func2);
    }

  cdoStreamClose(streamID4);
  cdoStreamClose(streamID3);
  cdoStreamClose(streamID2);
  cdoStreamClose(streamID1);
}

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

  if (operatorArgc() > 4)
    cdoAbort("Too many arguments!");

  int TX90P, TX10P, TN90P, TN10P, ALLX, R99P, R95P;
  if (operatorArgc() == 4 && 'm' == cdoOperatorArgv(3)[0])
    {
      TX90P = cdoOperatorAdd("etccdi_tx90p", func_selge, 8, nullptr); /* monthly mode */
      R99P = cdoOperatorAdd("etccdi_r99p", func_selge, 8, nullptr); /* monthly mode */
      R95P = cdoOperatorAdd("etccdi_r95p", func_selge, 8, nullptr); /* monthly mode */
      TX10P = cdoOperatorAdd("etccdi_tx10p", func_selle, 8, nullptr); /* monthly mode */
      TN90P = cdoOperatorAdd("etccdi_tn90p", func_selge, 8, nullptr); /* monthly mode */
      TN10P = cdoOperatorAdd("etccdi_tn10p", func_selle, 8, nullptr); /* monthly mode */
      ALLX = cdoOperatorAdd("etccdi", 0, 8, nullptr); /* monthly mode */
    }
  else
    {
      TX90P = cdoOperatorAdd("etccdi_tx90p", func_selge, 31, nullptr); 
      R99P = cdoOperatorAdd("etccdi_r99p", func_selge, 31, nullptr); 
      R95P = cdoOperatorAdd("etccdi_r95p", func_selge, 31, nullptr); 
      TX10P = cdoOperatorAdd("etccdi_tx10p", func_selle, 31, nullptr);
      TN90P = cdoOperatorAdd("etccdi_tn90p", func_selge, 31, nullptr); 
      TN10P = cdoOperatorAdd("etccdi_tn10p", func_selle, 31, nullptr);
      ALLX = cdoOperatorAdd("etccdi", 0, 31, nullptr);
    }
  ETCCDI_REQUEST request;

  request.ndates = parameter2int(cdoOperatorArgv(0));
  request.startboot = parameter2int(cdoOperatorArgv(1));

  const auto operatorID = cdoOperatorID();
  if (operatorID == TX90P || operatorID == TN90P || operatorID == R95P || operatorID == R99P)
    {
      if ( operatorID == TX90P || operatorID == TN90P )
        {
          if (operatorArgc() < 3 )
            cdoAbort("Operator requires at least 3 parameter values, you provided '%d'!", operatorArgc());
          request.endboot = parameter2int(cdoOperatorArgv(2));
          if ( operatorID == TX90P )
            {
              request.name     = TX90P_NAME;
              request.longname = TX90P_LONGNAME;
              request.units    = TX90P_UNITS;
              request.func2    = func_avg;
            }
          else if ( operatorID == TN90P )
            {
              request.name     = TN90P_NAME;
              request.longname = TN90P_LONGNAME;
              request.units    = TN90P_UNITS;
              request.func2    = func_avg;
            }
          request.pn = 90;
        }
      else
        {
          if (operatorArgc() < 2 )
            cdoAbort("Operator requires at least 2 parameter values, you provided '%d'!", operatorArgc());
          request.ndates = 1;
          request.startboot = parameter2int(cdoOperatorArgv(0));
          request.endboot = parameter2int(cdoOperatorArgv(1));
          if (operatorID == R95P )
            {
              request.name     = R95P_NAME;
              request.longname = R95P_LONGNAME;
              request.units    = R95P_UNITS;
              request.pn = 95;
              request.func2    = func_sum;
            }
          else if ( operatorID == R99P )
            {
              request.name     = R99P_NAME;
              request.longname = R99P_LONGNAME;
              request.units    = R99P_UNITS;
              request.pn = 99;
              request.func2    = func_sum;
            }
        }
    }
  else if (operatorID == TX10P || operatorID == TN10P)
    {
      if (operatorArgc() < 3 )
        cdoAbort("Operator requires at least 3 parameter values, you provided '%d'!", operatorArgc());
      request.endboot = parameter2int(cdoOperatorArgv(2));
      if ( operatorID == TX10P )
        {
          request.name     = TX10P_NAME;
          request.longname = TX10P_LONGNAME;
          request.units    = TX10P_UNITS;
          request.func2    = func_avg;
        }
      else
        {
          request.name     = TN10P_NAME;
          request.longname = TN10P_LONGNAME;
          request.units    = TN10P_UNITS;
          request.func2    = func_avg;
        }
      request.pn = 10;
    }
  etccdi_op(&request);
/*  else
    EcaEtccdi(-1, ndates, startboot, endboot); */

  cdoFinish();

  return nullptr;
}
