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

  Author: Uwe Schulzweida

*/

/*
   This module contains the following operators:

      Interpolate remapcon        First order conservative remapping
      Interpolate remapcon2       Second order conservative remapping
      Interpolate remapbil        Bilinear interpolation
      Interpolate remapbic        Bicubic interpolation
      Interpolate remapdis        Distance-weighted averaging
      Interpolate remapnn         Nearest neighbor remapping
      Interpolate remaplaf        Largest area fraction remapping
      Remap       remap           SCRIP grid remapping
*/

#include <algorithm>

#include <cdi.h>

#include "process_int.h"
#include "param_conversion.h"
#include "remap_utils.h"
#include <mpim_grid.h>
#include "griddes.h"
#include "cdo_options.h"
#include "util_string.h"

static int
maptype_to_operfunc(const RemapSwitches &remapSwitches)
{
  int operfunc = -1;

  if (remapSwitches.mapType == RemapMethod::CONSERV_SCRIP)
    operfunc = (remapSwitches.remapOrder == 2) ? REMAPCON2 : REMAPSCON;
  else if (remapSwitches.mapType == RemapMethod::CONSERV)
    operfunc = (remapSwitches.submapType == SubmapType::LAF) ? REMAPLAF : ((remapSwitches.remapOrder == 2) ? REMAPYCON2 : REMAPCON);
  else if (remapSwitches.mapType == RemapMethod::BILINEAR)
    operfunc = REMAPBIL;
  else if (remapSwitches.mapType == RemapMethod::BICUBIC)
    operfunc = REMAPBIC;
  else if (remapSwitches.mapType == RemapMethod::DISTWGT)
    operfunc = (remapSwitches.numNeighbors == 1) ? REMAPNN : REMAPDIS;
  else
    cdo_abort("Unsupported mapping method (mapType = %d)", remapSwitches.mapType);

  return operfunc;
}

template <typename T>
static void
scale_gridbox_area(size_t gridsize1, const Varray<T> &array1, size_t gridsize2, Varray<T> &array2, const Varray<double> &grid2_area)
{
  auto array1sum = varray_sum(gridsize1, array1);
  auto array2sum = varray_sum(gridsize2, grid2_area);
  for (size_t i = 0; i < gridsize2; ++i) array2[i] = grid2_area[i] / array2sum * array1sum;

  static auto hasGridboxInfo = true;
  if (hasGridboxInfo)
    {
      cdo_print("gridbox_area replaced and scaled to %g", array1sum);
      hasGridboxInfo = false;
    }
}

static void
scale_gridbox_area(const Field &field1, Field &field2, const Varray<double> &grid2_area)
{
  scale_gridbox_area(field1.gridsize, field1.vec_f, field2.gridsize, field2.vec_f, grid2_area);
  scale_gridbox_area(field1.gridsize, field1.vec_d, field2.gridsize, field2.vec_d, grid2_area);
}

template <typename T>
void gme_grid_restore(T *p, int ni, int nd);

template <typename T>
static void
restore_gme_grid(int gridID, size_t srvGridSize, Varray<T> &v, size_t tgtGridSize, const Varray<int> &vgpm)
{
  int nd, ni, ni2, ni3;
  gridInqParamGME(gridID, &nd, &ni, &ni2, &ni3);

  auto n = tgtGridSize;
  for (size_t i = srvGridSize; i > 0; i--)
    if (vgpm[i - 1]) v[i - 1] = v[--n];

  gme_grid_restore(v.data(), ni, nd);
}

static void
restore_gme_grid(Field &field, const RemapGrid &tgtGrid)
{
  if (field.memType == MemType::Float)
    restore_gme_grid(field.grid, field.gridsize, field.vec_f, tgtGrid.size, tgtGrid.vgpm);
  else
    restore_gme_grid(field.grid, field.gridsize, field.vec_d, tgtGrid.size, tgtGrid.vgpm);
}

template <typename T>
static void
store_gme_grid(size_t gridsize, Varray<T> &v, const Varray<int> &vgpm)
{
  for (size_t i = 0, j = 0; i < gridsize; ++i)
    if (vgpm[i]) v[j++] = v[i];
}

static void
store_gme_grid(Field &field, const Varray<int> &vgpm)
{
  if (field.memType == MemType::Float)
    store_gme_grid(field.gridsize, field.vec_f, vgpm);
  else
    store_gme_grid(field.gridsize, field.vec_d, vgpm);
}

static int
set_maxremaps(int vlistID)
{
  int maxRemaps = 0;

  auto numZaxis = vlistNzaxis(vlistID);
  for (int index = 0; index < numZaxis; ++index)
    {
      auto zaxisID = vlistZaxis(vlistID, index);
      auto zaxisSize = zaxisInqSize(zaxisID);
      if (zaxisSize > maxRemaps) maxRemaps = zaxisSize;
    }

  auto numVars = vlistNvars(vlistID);
  if (numVars > maxRemaps) maxRemaps = numVars;

  maxRemaps++;

  if (Options::cdoVerbose) cdo_print("Set maxRemaps to %d", maxRemaps);

  return maxRemaps;
}

template <typename T>
static void
remap_normalize_field(NormOpt normOpt, size_t gridsize, Varray<T> &array, T missval, const RemapGrid &tgtGrid)
{
  // used to check the result of remapcon

  if (normOpt == NormOpt::NONE)
    {
      for (size_t i = 0; i < gridsize; ++i)
        {
          if (!dbl_is_equal(array[i], missval))
            {
              auto gridError = tgtGrid.cell_frac[i] * tgtGrid.cell_area[i];
              array[i] = (std::fabs(gridError) > 0.0) ? (array[i] / gridError) : missval;
            }
        }
    }
  else if (normOpt == NormOpt::DESTAREA)
    {
      for (size_t i = 0; i < gridsize; ++i)
        {
          if (!dbl_is_equal(array[i], missval))
            {
              array[i] = (std::fabs(tgtGrid.cell_frac[i]) > 0.0) ? (array[i] / tgtGrid.cell_frac[i]) : missval;
            }
        }
    }
}

static void
remap_normalize_field(NormOpt normOpt, Field &field, const RemapGrid &tgtGrid)
{
  if (field.memType == MemType::Float)
    remap_normalize_field(normOpt, field.gridsize, field.vec_f, (float) field.missval, tgtGrid);
  else
    remap_normalize_field(normOpt, field.gridsize, field.vec_d, field.missval, tgtGrid);
}

template <typename T>
static void
remap_set_fracmin(double fracMin, size_t gridsize, Varray<T> &array, T missval, RemapGrid *tgtGrid)
{
  if (fracMin > 0.0)
    {
      for (size_t i = 0; i < gridsize; ++i)
        if (tgtGrid->cell_frac[i] < fracMin) array[i] = missval;
    }
}

static void
remap_set_fracmin(double fracMin, Field &field, RemapGrid *tgtGrid)
{
  if (field.memType == MemType::Float)
    remap_set_fracmin(fracMin, field.gridsize, field.vec_f, (float) field.missval, tgtGrid);
  else
    remap_set_fracmin(fracMin, field.gridsize, field.vec_d, field.missval, tgtGrid);
}

static void
remap_init(RemapType &remap)
{
  remap.nused = 0;
  remap.gridID = -1;
  remap.gridsize = 0;
  remap.nmiss = 0;
}

static void
remap_field(const RemapSwitches &remapSwitches, RemapType &remap, const Field &field1, Field &field2)
{
  auto mapType = remapSwitches.mapType;
  auto numNeighbors = remapSwitches.numNeighbors;
  // clang-format off
  if      (mapType == RemapMethod::BILINEAR) remap_bilinear(remap.search, field1, field2);
  else if (mapType == RemapMethod::BICUBIC)  remap_bicubic(remap.search, field1, field2);
  else if (mapType == RemapMethod::DISTWGT)  remap_dist_wgt(numNeighbors, remap.search, field1, field2);
  else if (mapType == RemapMethod::CONSERV)  remap_conserv(remap.vars.normOpt, remap.search, field1, field2);
  // clang-format on
}

static RemapSwitches
remap_read_weights(const std::string &remapWeightsFile, int gridID1, int gridID2, RemapType &remap0, bool extrapolateIsSet,
                   bool &remapExtrapolate)
{
  auto remapSwitches = remap_read_data_scrip(remapWeightsFile, gridID1, gridID2, remap0.srcGrid, remap0.tgtGrid, remap0.vars);

  if (remap0.vars.linksPerValue == 0) remap_linksPerValue(remap0.vars);

  auto gridsize = remap0.srcGrid.size;
  remap0.gridID = gridID1;
  remap0.gridsize = gridInqSize(gridID1);

  if (remapSwitches.mapType == RemapMethod::DISTWGT && !extrapolateIsSet) remapExtrapolate = true;
  if (gridIsCircular(gridID1) && !extrapolateIsSet) remapExtrapolate = true;

  if (gridInqType(gridID1) == GRID_GME) gridsize = remap0.srcGrid.nvgp;

  if (gridsize != remap0.gridsize) cdo_abort("Size of source grid and weights from %s differ!", remapWeightsFile);

  if (gridInqType(gridID1) == GRID_GME) gridsize = remap0.srcGrid.size;

  for (size_t i = 0; i < gridsize; ++i)
    if (remap0.srcGrid.mask[i] == false) remap0.nmiss++;

  auto gridsize2 = gridInqSize(gridID2);
  if (gridInqType(gridID2) == GRID_GME)
    {
      remap0.tgtGrid.nvgp = gridInqSize(gridID2);
      remap0.tgtGrid.vgpm.resize(gridInqSize(gridID2));
      auto gridID2_gme = gridToUnstructured(gridID2, NeedCorners::Yes);
      gridInqMaskGME(gridID2_gme, &remap0.tgtGrid.vgpm[0]);
      gridDestroy(gridID2_gme);
      size_t isize = 0;
      for (size_t i = 0; i < gridsize2; ++i)
        if (remap0.tgtGrid.vgpm[i]) isize++;
      gridsize2 = isize;
    }
  // printf("grid2 %zu %d %zu\n", gridsize2, remap0.tgtGrid.nvgp, remap0.tgtGrid.size);
  if (remap0.tgtGrid.size != gridsize2) cdo_abort("Size of target grid and weights from %s differ!", remapWeightsFile);

  return remapSwitches;
}

double getPlanetRadius(int gridID);

static void
print_node_info(int gridID2, const RemapVars &rv, const RemapGrid &srcGrid)
{
  double lon2{}, lat2{};
  if (gridInqSize(gridID2) == 1)
    {
      gridInqXvals(gridID2, &lon2);
      gridInqYvals(gridID2, &lat2);
    }

  if (rv.numLinks == 1)
    {
      auto srcCellIndex = rv.srcCellIndices[0];
      auto llpoint = remapgrid_get_lonlat(&srcGrid, srcCellIndex);
      auto lon1 = RAD2DEG * llpoint.lon;
      auto lat1 = RAD2DEG * llpoint.lat;
      if (lon1 > 180.0) lon1 -= 360.0;
      auto distance
          = orthodrome(DEG2RAD * lon1, DEG2RAD * lat1, DEG2RAD * lon2, DEG2RAD * lat2) * getPlanetRadius(srcGrid.gridID) / 1000;

      cdo_print("Target Point: lon=%g/lat=%g  Source Point: index=%zu lon=%g/lat=%g distance=%.3fkm", lon2, lat2, srcCellIndex + 1,
                lon1, lat1, distance);
    }
  else if (rv.numLinks == 0)
    {
      cdo_print("Target Point: lon=%g/lat=%g  Source Point: out of grid area", lon2, lat2);
    }
}

static void
add_operators(void)
{
  // clang-format off
  cdo_operator_add("remap",        REMAP,        0, nullptr);
  cdo_operator_add("remapcon",     REMAPCON,     0, nullptr);
  cdo_operator_add("remapycon2test",REMAPYCON2,  0, nullptr);
  cdo_operator_add("remapscon",    REMAPSCON,    0, nullptr);
  cdo_operator_add("remapcon2",    REMAPCON2,    0, nullptr);
  cdo_operator_add("remapbil",     REMAPBIL,     0, nullptr);
  cdo_operator_add("remapbic",     REMAPBIC,     0, nullptr);
  cdo_operator_add("remapdis",     REMAPDIS,     0, nullptr);
  cdo_operator_add("remapnn",      REMAPNN,      0, nullptr);
  cdo_operator_add("remaplaf",     REMAPLAF,     0, nullptr);
  cdo_operator_add("remapavgtest", REMAPAVG,     0, nullptr);
  // clang-format on
}

class ModuleRemapgrid
{
  RemapSwitches remapSwitches;
  bool remap_genweights = Options::REMAP_genweights;
  int remapIndex = -1;
  int numRemaps = 0;
  int numNeighbors = 0;
  std::string remapWeightsFile;

  CdoStreamID streamID1;
  int taxisID1;
  int vlistID1;

  CdoStreamID streamID2;
  int taxisID2;
  int gridID2;

  bool useMask;
  bool extrapolateIsSet;
  bool remapExtrapolate;
  bool needGradients;
  bool doRemap;

  int operfunc;
  int maxRemaps;

  std::vector<bool> remapGrids;
  std::vector<RemapType> remaps;

  RemapGradients gradients;
  RemapParams remapParams;
  RemapMethod mapType;
  int remapOrder;

  NormOpt normOpt;

  Field field1, field2;
  VarList varList1;
  VarList varList2;

public:
  void
  init(void *process)
  {
    cdo_initialize(process);

    add_operators();

    auto operatorID = cdo_operator_id();
    operfunc = cdo_operator_f1(operatorID);
    auto writeRemapWeightsOnly = false;
    doRemap = (operfunc == REMAP);

    remap_set_int(REMAP_WRITE_REMAP, writeRemapWeightsOnly);

    remapParams = remap_get_params();
    remap_set_params(remapParams);
    extrapolateIsSet = (remapParams.extrapolate != -1);
    remapExtrapolate = extrapolateIsSet ? (bool) remapParams.extrapolate : remap_func_is_dist(operfunc);

    if (Options::cdoVerbose) cdo_print("Extrapolation %s!", remapExtrapolate ? "enabled" : "disabled");

    if (doRemap)
      {
        operator_input_arg("grid description file or name, remap weights file (SCRIP NetCDF)");
        operator_check_argc(2);
        remapWeightsFile = cdo_operator_argv(1);
      }
    else
      {
        operator_input_arg("grid description file or name");
        if (operfunc == REMAPDIS && cdo_operator_argc() == 2)
          {
            auto inum = parameter_to_int(cdo_operator_argv(1));
            if (inum < 1) cdo_abort("Number of nearest neighbors out of range (>0)!");
            numNeighbors = inum;
          }
        else
          {
            operator_check_argc(1);
          }
      }

    gridID2 = cdo_define_grid(cdo_operator_argv(0));
    if (gridInqType(gridID2) == GRID_GENERIC) cdo_abort("Unsupported target grid type (generic)!");

    streamID1 = cdo_open_read(0);

    vlistID1 = cdo_stream_inq_vlist(streamID1);
    auto vlistID2 = vlistDuplicate(vlistID1);

    vlist_unpack(vlistID2);

    taxisID1 = vlistInqTaxis(vlistID1);
    taxisID2 = taxisDuplicate(taxisID1);
    vlistDefTaxis(vlistID2, taxisID2);

    varList_init(varList1, vlistID1);

    remapGrids = remap_set_grids(vlistID1, varList1);

    auto numRemapGrids = std::count_if(remapGrids.begin(), remapGrids.end(), [](auto flag) { return (flag == true); });
    if (numRemapGrids == 0) cdo_abort("No remappable grid found!");

    auto numGrids = vlistNgrids(vlistID1);
    for (int index = 0; index < numGrids; ++index)
      if (remapGrids[index]) vlistChangeGridIndex(vlistID2, index, gridID2);

    maxRemaps = remapParams.maxRemaps;
    if (maxRemaps == -1) maxRemaps = set_maxremaps(vlistID1);
    if (maxRemaps < 1) cdo_abort("max_remaps out of range (>0)!");

    remaps = std::vector<RemapType>(maxRemaps);

    if (doRemap) remap_genweights = true;

    if (doRemap)
      {
        numRemaps = 1;
        int gridIndex1;
        for (gridIndex1 = 0; gridIndex1 < numGrids; ++gridIndex1)
          if (remapGrids[gridIndex1]) break;
        auto gridID1 = vlistGrid(vlistID1, gridIndex1);
        remapSwitches = remap_read_weights(remapWeightsFile, gridID1, gridID2, remaps[0], extrapolateIsSet, remapExtrapolate);
        operfunc = maptype_to_operfunc(remapSwitches);

        if (remapParams.test) remap_vars_reorder(remaps[0].vars);
      }
    else
      {
        remapSwitches = remap_operfunc_to_maptype(operfunc);
        if (numNeighbors) remapSwitches.numNeighbors = numNeighbors;
      }

    mapType = remapSwitches.mapType;
    remapOrder = remapSwitches.remapOrder;

    varList_init(varList2, vlistID2);

    // if (!remap_genweights && (mapType == RemapMethod::CONSERV_SCRIP || mapType == RemapMethod::CONSERV)) remap_genweights = true;
    if (!remap_genweights && mapType == RemapMethod::CONSERV_SCRIP) remap_genweights = true;
    useMask = !(!remap_genweights
                && (mapType == RemapMethod::BILINEAR || mapType == RemapMethod::BICUBIC || mapType == RemapMethod::DISTWGT
                    || mapType == RemapMethod::CONSERV));

    remap_set_int(REMAP_GENWEIGHTS, (int) remap_genweights);

    normOpt = NormOpt(NormOpt::NONE);
    if (mapType == RemapMethod::CONSERV_SCRIP || mapType == RemapMethod::CONSERV) normOpt = remap_get_normOpt();

    auto grid1sizemax = vlistGridsizeMax(vlistID1);

    needGradients = (mapType == RemapMethod::BICUBIC);
    if ((mapType == RemapMethod::CONSERV_SCRIP || mapType == RemapMethod::CONSERV) && remapOrder == 2)
      {
        if (Options::cdoVerbose) cdo_print("Second order remapping");
        needGradients = true;
      }

    if (needGradients) gradients.init(grid1sizemax);

    if (remap_genweights)
      {
        // remap() gives rounding errors on target arrays with single precision floats
        auto numVars = vlistNvars(vlistID2);
        for (int varID = 0; varID < numVars; ++varID) varList2[varID].memType = MemType::Double;
      }

    streamID2 = cdo_open_write(1);
    cdo_def_vlist(streamID2, vlistID2);
  }

  void
  run()
  {
    Varray<short> imask;

    int tsID = 0;
    while (true)
      {
        auto nrecs = cdo_stream_inq_timestep(streamID1, tsID);
        if (nrecs == 0) break;

        cdo_taxis_copy_timestep(taxisID2, taxisID1);

        cdo_def_timestep(streamID2, tsID);

        for (int recID = 0; recID < nrecs; ++recID)
          {
            int varID, levelID;
            cdo_inq_record(streamID1, &varID, &levelID);
            auto &var = varList1[varID];
            field1.init(var);
            cdo_read_record(streamID1, field1);
            auto nmiss1 = useMask ? field1.nmiss : 0;

            field2.init(varList2[varID]);

            if (!remapGrids[vlistGridIndex(vlistID1, var.gridID)]) { field_copy(field1, field2); }
            else
              {
                if (mapType != RemapMethod::CONSERV_SCRIP && mapType != RemapMethod::CONSERV && var.gridType == GRID_GME)
                  cdo_abort("Only conservative remapping is available to remap between GME grids!");

                if (gridIsCircular(var.gridID) && !extrapolateIsSet) remapExtrapolate = true;

                remap_set_mask(var.gridsize, field1, nmiss1, var.missval, imask);

                for (remapIndex = numRemaps - 1; remapIndex >= 0; remapIndex--)
                  {
                    auto &remap = remaps[remapIndex];
                    if (var.gridID == remap.gridID)
                      {
                        if ((useMask && nmiss1 == remap.nmiss && imask == remap.srcGrid.mask) || !useMask)
                          {
                            remap.nused++;
                            break;
                          }
                      }
                  }

                if (Options::cdoVerbose && remapIndex >= 0) cdo_print("Using remap %d", remapIndex);

                if (remapIndex < 0)
                  {
                    if (numRemaps < maxRemaps)
                      {
                        remapIndex = numRemaps;
                        numRemaps++;
                      }
                    else
                      {
                        int remapIndex0 = (maxRemaps > 1 && remaps[0].nused > remaps[1].nused);
                        remap_vars_free(remaps[remapIndex0].vars);
                        remap_grid_free(remaps[remapIndex0].srcGrid);
                        remap_grid_free(remaps[remapIndex0].tgtGrid);
                        remap_search_free(remaps[remapIndex0].search);
                        remapIndex = remapIndex0;
                        remap_init(remaps[remapIndex]);
                      }

                    auto &remap = remaps[remapIndex];
                    if (remap.gridID != var.gridID)
                      {
                        if (gridIsCircular(var.gridID) && !extrapolateIsSet) remapExtrapolate = true;

                        //  remap.srcGrid.luse_cell_area = false;
                        //  remap.tgtGrid.luse_cell_area = false;

                        auto numSearchBins = remapParams.numSearchBins;
                        if (var.gridType != GRID_UNSTRUCTURED && !remapParams.numSearchBinsIsSet)
                          {
                            numSearchBins = (!remapExtrapolate && mapType == RemapMethod::DISTWGT)
                                                ? 1
                                                : remap_gen_numbins(gridInqYsize(var.gridID));
                          }

                        remap_set_int(REMAP_NUM_SRCH_BINS, numSearchBins);

                        remap.vars.normOpt = normOpt;
                        remap.vars.pinit = false;

                        if ((mapType == RemapMethod::BILINEAR || mapType == RemapMethod::BICUBIC)
                            && (var.gridType == GRID_GME || var.gridType == GRID_UNSTRUCTURED))
                          cdo_abort("Bilinear/bicubic interpolation doesn't support unstructured source grids!");

                        // Initialize grid information for both grids
                        remap_init_grids(mapType, remapExtrapolate, var.gridID, remap.srcGrid, gridID2, remap.tgtGrid);
                        remap_search_init(mapType, remap.search, remap.srcGrid, remap.tgtGrid);
                      }

                    remap.gridID = var.gridID;
                    remap.nmiss = nmiss1;

                    if (var.gridType == GRID_GME)
                      {
                        for (size_t i = 0, j = 0; i < var.gridsize; ++i)
                          if (remap.srcGrid.vgpm[i]) imask[j++] = imask[i];
                      }

                    varray_copy(remap.srcGrid.size, imask, remap.srcGrid.mask);

                    if (mapType == RemapMethod::CONSERV_SCRIP || mapType == RemapMethod::CONSERV)
                      {
                        varray_fill(remap.srcGrid.cell_area, 0.0);
                        varray_fill(remap.srcGrid.cell_frac, 0.0);
                        varray_fill(remap.tgtGrid.cell_area, 0.0);
                      }
                    varray_fill(remap.tgtGrid.cell_frac, 0.0);

                    // initialize some remapping variables
                    remap_vars_init(mapType, remapOrder, remap.vars);

                    remap_print_info(operfunc, remap_genweights, remap.srcGrid, remap.tgtGrid, nmiss1, remapSwitches.numNeighbors);

                    if (remap_genweights)
                      {
                        if (needGradients)
                          {
                            if (remap.srcGrid.rank != 2 && remapOrder == 2)
                              cdo_abort("Second order remapping is not available for unstructured grids!");
                          }

                        remap_gen_weights(remapParams.sortMode, remapSwitches, remap);

                        if (remapParams.test) remap_vars_reorder(remap.vars);
                      }
                  }

                auto &remap = remaps[remapIndex];
                if (var.gridType == GRID_GME) store_gme_grid(field1, remaps[remapIndex].srcGrid.vgpm);

                auto gridsize2 = gridInqSize(gridID2);

                if (remap_genweights)
                  {
                    remap.nused++;

                    if (needGradients)
                      {
                        if (remap.srcGrid.rank != 2 && remapOrder == 2)
                          cdo_abort("Second order remapping is not available for unstructured grids!");

                        remap_gradients(remap.srcGrid, field1, gradients);
                      }

                    if (Options::cdoVerbose && operfunc == REMAPNN && gridsize2 == 1)
                      {
                        static auto printNodeInfo = true;
                        if (printNodeInfo)
                          {
                            printNodeInfo = false;
                            print_node_info(gridID2, remap.vars, remap.srcGrid);
                          }
                      }

                    if (operfunc == REMAPLAF)
                      remap_laf(field2, var.missval, gridsize2, remap.vars, field1);
                    else if (operfunc == REMAPAVG)
                      remap_avg(field2, var.missval, gridsize2, remap.vars, field1);
                    else
                      remap_field(field2, var.missval, gridsize2, remap.vars, field1, gradients);
                  }
                else
                  {
                    remap_field(remapSwitches, remap, field1, field2);
                  }

                if (operfunc == REMAPSCON || operfunc == REMAPCON2 || operfunc == REMAPCON || operfunc == REMAPYCON2)
                  {
                    // used only to check the result of remapcon
                    if (0) remap_normalize_field(remap.vars.normOpt, field2, remap.tgtGrid);

                    remap_set_fracmin(remapParams.fracMin, field2, &remap.tgtGrid);

                    if (var.name == "gridbox_area") scale_gridbox_area(field1, field2, remap.tgtGrid.cell_area);
                  }

                // calculate some statistics
                if (Options::cdoVerbose) remap_stat(remapOrder, remap.srcGrid, remap.tgtGrid, remap.vars, field1, field2);

                if (gridInqType(gridID2) == GRID_GME) restore_gme_grid(field2, remap.tgtGrid);

                field2.nmiss = field_num_mv(field2);
              }

            cdo_def_record(streamID2, varID, levelID);
            cdo_write_record(streamID2, field2);
          }

        tsID++;
      }

    if (doRemap && remap_genweights && remaps[0].nused == 0)
      remap_print_warning(remapWeightsFile, operfunc, remaps[0].srcGrid, remaps[0].nmiss);
  }

  void
  close()
  {

    cdo_stream_close(streamID2);
    cdo_stream_close(streamID1);

    for (remapIndex = 0; remapIndex < numRemaps; remapIndex++)
      {
        auto &remap = remaps[remapIndex];
        remap_vars_free(remap.vars);
        remap_grid_free(remap.srcGrid);
        remap_grid_free(remap.tgtGrid);
        remap_search_free(remap.search);
      }

    gridDestroy(gridID2);

    cdo_finish();
  }
};

void *
Remapgrid(void *process)
{
  ModuleRemapgrid remapGrid;
  remapGrid.init(process);
  remapGrid.run();
  remapGrid.close();

  return nullptr;
}
