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

  Author: Uwe Schulzweida

*/

#include <cdi.h>

#include "cdo_rlimit.h"
#include "process_int.h"
#include "param_conversion.h"
#include <mpim_grid.h>
#include "util_files.h"
#include "cdo_options.h"
#include "cdi_lockedIO.h"

static int globalGridType = CDI_UNDEFID;

struct GridInfo2
{
  int globalIndicesID;
  bool needed;
};

struct CollgridInfo
{
  CdoStreamID streamID;
  int vlistID;
  VarList varList;
  Field field;
  std::vector<std::vector<long>> cellIndex;
};

struct xyinfoType
{
  double x = 0.0, y = 0.0;
  int id = -1;
};

static bool
cmpxy_lt(const xyinfoType &a, const xyinfoType &b)
{
  return (a.y < b.y || (std::fabs(a.y - b.y) <= 0 && a.x < b.x));
}

static bool
cmpxy_gt(const xyinfoType &a, const xyinfoType &b)
{
  return (a.y > b.y || (std::fabs(a.y - b.y) <= 0 && a.x < b.x));
}

static int
gen_coll_grid(int ngrids, int numFiles, std::vector<CollgridInfo> &collgridInfo, int gindex, long nxblocks)
{
  auto isSouthNorth = true;
  auto isRegular = false;
  auto isCurvilinear = false;

  long nx = (nxblocks != -1) ? nxblocks : -1;

  auto gridID = vlistGrid(collgridInfo[0].vlistID, gindex);
  auto gridtype0 = (globalGridType != CDI_UNDEFID) ? globalGridType : gridInqType(gridID);
  if (ngrids > 1 && gridtype0 == GRID_GENERIC && gridInqXsize(gridID) == 0 && gridInqYsize(gridID) == 0) return -1;

  auto isUnstructured = (gridtype0 == GRID_UNSTRUCTURED);
  auto nv = isUnstructured ? gridInqNvertex(gridID) : 0;
  auto withCenter = (globalGridType == CDI_UNDEFID && gridHasCoordinates(gridID));
  auto withBounds = (isUnstructured && globalGridType == CDI_UNDEFID && gridHasBounds(gridID));

  std::vector<xyinfoType> xyinfo(numFiles);
  std::vector<long> xsize(numFiles), ysize(numFiles);
  Varray2D<double> xvals(numFiles), yvals(numFiles);
  Varray2D<double> xbounds(numFiles), ybounds(numFiles);

  for (int fileID = 0; fileID < numFiles; ++fileID)
    {
      gridID = vlistGrid(collgridInfo[fileID].vlistID, gindex);
      auto gridtype = (globalGridType != CDI_UNDEFID) ? globalGridType : gridInqType(gridID);
      if (gridtype == GRID_LONLAT || gridtype == GRID_GAUSSIAN || gridtype == GRID_PROJECTION)
        isRegular = true;
      else if (gridtype == GRID_CURVILINEAR)
        isCurvilinear = true;
      else if (gridtype == GRID_UNSTRUCTURED)
        isUnstructured = true;
      else if (gridtype == GRID_GENERIC /*&& gridInqXsize(gridID) > 0 && gridInqYsize(gridID) > 0*/)
        isRegular = withCenter;
      else
        cdo_abort("Unsupported grid type: %s!", gridNamePtr(gridtype));

      xsize[fileID] = isUnstructured ? gridInqSize(gridID) : gridInqXsize(gridID);
      ysize[fileID] = isUnstructured ? 1 : gridInqYsize(gridID);
      if (xsize[fileID] == 0) xsize[fileID] = 1;
      if (ysize[fileID] == 0) ysize[fileID] = 1;

      if (isRegular)
        {
          xvals[fileID].resize(xsize[fileID]);
          yvals[fileID].resize(ysize[fileID]);
        }
      else if (isCurvilinear || isUnstructured)
        {
          if (withCenter) xvals[fileID].resize(xsize[fileID] * ysize[fileID]);
          if (withCenter) yvals[fileID].resize(xsize[fileID] * ysize[fileID]);
          if (withBounds) xbounds[fileID].resize(nv * xsize[fileID] * ysize[fileID]);
          if (withBounds) ybounds[fileID].resize(nv * xsize[fileID] * ysize[fileID]);
        }

      if (isRegular || isCurvilinear || isUnstructured)
        {
          if (withCenter) gridInqXvals(gridID, xvals[fileID].data());
          if (withCenter) gridInqYvals(gridID, yvals[fileID].data());
          if (withBounds) gridInqXbounds(gridID, xbounds[fileID].data());
          if (withBounds) gridInqYbounds(gridID, ybounds[fileID].data());
        }
      // printf("fileID %d, gridID %d\n", fileID, gridID);

      xyinfo[fileID].id = fileID;
      if (isRegular)
        {
          xyinfo[fileID].x = xvals[fileID][0];
          xyinfo[fileID].y = yvals[fileID][0];
          if (ysize[fileID] > 1 && yvals[fileID][0] > yvals[fileID][ysize[fileID] - 1]) isSouthNorth = false;
        }
    }

  if (Options::cdoVerbose && isRegular)
    for (int fileID = 0; fileID < numFiles; ++fileID)
      printf("1 %d %g %g \n", xyinfo[fileID].id, xyinfo[fileID].x, xyinfo[fileID].y);

  if (isRegular)
    {
      ranges::sort(xyinfo, {}, &xyinfoType::x);

      if (Options::cdoVerbose)
        for (int fileID = 0; fileID < numFiles; ++fileID)
          printf("2 %d %g %g \n", xyinfo[fileID].id, xyinfo[fileID].x, xyinfo[fileID].y);

      ranges::sort(xyinfo, isSouthNorth ? cmpxy_lt : cmpxy_gt);

      if (Options::cdoVerbose)
        for (int fileID = 0; fileID < numFiles; ++fileID)
          printf("3 %d %g %g \n", xyinfo[fileID].id, xyinfo[fileID].x, xyinfo[fileID].y);

      if (nx <= 0)
        {
          nx = 1;
          for (int fileID = 1; fileID < numFiles; ++fileID)
            {
              if (fp_is_equal(xyinfo[0].y, xyinfo[fileID].y))
                nx++;
              else
                break;
            }
        }
    }
  else
    {
      if (nx <= 0) nx = numFiles;
    }

  long ny = numFiles / nx;
  if (nx * ny != numFiles) cdo_abort("Number of input files (%ld) and number of blocks (%ldx%ld) differ!", numFiles, nx, ny);

  long xsize2 = 0;
  for (long i = 0; i < nx; ++i) xsize2 += xsize[xyinfo[i].id];
  long ysize2 = 0;
  for (long j = 0; j < ny; ++j) ysize2 += ysize[xyinfo[j * nx].id];
  if (Options::cdoVerbose) cdo_print("xsize2 %ld  ysize2 %ld", xsize2, ysize2);

  {  // verify size of data
    long xs = xsize[xyinfo[0].id];
    for (long j = 1; j < ny; ++j)
      if (xsize[xyinfo[j * nx].id] != xs) cdo_abort("xsize=%ld differ from first file (xsize=%ld)!", xsize[xyinfo[j * nx].id], xs);
    long ys = ysize[xyinfo[0].id];
    for (long i = 1; i < nx; ++i)
      if (ysize[xyinfo[i].id] != ys) cdo_abort("ysize=%ld differ from first file (ysize=%ld)!", ysize[xyinfo[i].id], ys);
  }

  Varray<double> xvals2, yvals2;
  Varray<double> xbounds2, ybounds2;
  if (isRegular)
    {
      xvals2.resize(xsize2);
      yvals2.resize(ysize2);
    }
  else if (isCurvilinear || isUnstructured)
    {
      if (withCenter) xvals2.resize(xsize2 * ysize2);
      if (withCenter) yvals2.resize(xsize2 * ysize2);
      if (withBounds) xbounds2.resize(nv * xsize2 * ysize2);
      if (withBounds) ybounds2.resize(nv * xsize2 * ysize2);
    }

  std::vector<long> xoff(nx + 1), yoff(ny + 1);

  xoff[0] = 0;
  for (long i = 0; i < nx; ++i)
    {
      long idx = xyinfo[i].id;
      if (isRegular) array_copy(xsize[idx], xvals[idx].data(), &xvals2[xoff[i]]);
      xoff[i + 1] = xoff[i] + xsize[idx];
    }

  yoff[0] = 0;
  for (long j = 0; j < ny; ++j)
    {
      long idx = xyinfo[j * nx].id;
      if (isRegular) array_copy(ysize[idx], yvals[idx].data(), &yvals2[yoff[j]]);
      yoff[j + 1] = yoff[j] + ysize[idx];
    }

  for (int fileID = 0; fileID < numFiles; ++fileID)
    {
      long idx = xyinfo[fileID].id;
      long iy = fileID / nx;
      long ix = fileID - iy * nx;

      long offset = yoff[iy] * xsize2 + xoff[ix];
      // printf("fileID %d %d, iy %d, ix %d, offset %d\n", fileID, xyinfo[fileID].id, iy, ix, offset);

      long ij = 0;
      for (long j = 0; j < ysize[idx]; ++j)
        for (long i = 0; i < xsize[idx]; ++i)
          {
            if (isCurvilinear || isUnstructured)
              {
                if (withCenter) { xvals2[offset + j * xsize2 + i] = xvals[idx][ij]; }
                if (withCenter) { yvals2[offset + j * xsize2 + i] = yvals[idx][ij]; }
                if (withBounds)
                  {
                    for (long k = 0; k < nv; ++k) xbounds2[(offset + j * xsize2 + i) * nv + k] = xbounds[idx][ij * nv + k];
                  }
                if (withBounds)
                  {
                    for (long k = 0; k < nv; ++k) ybounds2[(offset + j * xsize2 + i) * nv + k] = ybounds[idx][ij * nv + k];
                  }
              }
            collgridInfo[idx].cellIndex[gindex][ij++] = offset + j * xsize2 + i;
          }
    }

  auto gridID2 = gridCreate(gridtype0, xsize2 * ysize2);
  if (!isUnstructured)
    {
      gridDefXsize(gridID2, xsize2);
      gridDefYsize(gridID2, ysize2);
    }
  else if (nv > 0) { gridDefNvertex(gridID2, nv); }

  if (isRegular || isCurvilinear || isUnstructured)
    {
      if (withCenter) gridDefXvals(gridID2, xvals2.data());
      if (withCenter) gridDefYvals(gridID2, yvals2.data());
      if (withBounds) gridDefXbounds(gridID2, xbounds2.data());
      if (withBounds) gridDefYbounds(gridID2, ybounds2.data());
    }

  gridID = vlistGrid(collgridInfo[0].vlistID, gindex);

  grid_copy_names(gridID, gridID2);

  if (gridtype0 == GRID_PROJECTION) grid_copy_mapping(gridID, gridID2);

  return gridID2;
}
/*
static void
coll_cells_reg2d(const Field &field1, Field &field2, const CollgridInfo &collgridInfo, size_t nlon)
{
  auto nx = collgridInfo.nx;
  auto ny = collgridInfo.ny;

  for (size_t j = 0; j < ny; ++j)
    {
      auto offset1 = j * nx;
      auto offset2 = collgridInfo.offset + j * nlon;

      auto func = [&](const auto &v1, auto &v2, auto n) {
        for (size_t i = 0; i < nx; ++i) { v2[offset2 + i] = v1[offset1 + i]; }
      };
      field_operation2(func, field1, field2);
    }
}
*/
static void
collect_cells(const Field &field1, Field &field2, const std::vector<long> &cellIndex)
{
  auto func = [&](const auto &v1, auto &v2, auto n) {
    for (size_t i = 0; i < n; ++i) { v2[cellIndex[i]] = v1[i]; }
  };
  field_operation2(func, field1, field2, field1.size);
}

static std::vector<int>
get_var_gridindex(int vlistID, const VarList &varList)
{
  auto numVars = varList.numVars();
  auto numGrids = vlistNumGrids(vlistID);

  std::vector<int> varGridIndex(numVars, 0);
  for (const auto &var : varList.vars)
    {
      for (int index = 0; index < numGrids; ++index)
        {
          if (var.gridID == vlistGrid(vlistID, index))
            {
              varGridIndex[var.ID] = index;
              break;
            }
        }
    }

  return varGridIndex;
}

static std::vector<GridInfo2>
get_gridinfo(int vlistID, const VarList &varList, const std::vector<int> &varGridIndex, std::vector<bool> &selectedVars)
{
  auto numVars = varList.numVars();
  auto ngrids = vlistNumGrids(vlistID);

  std::vector<GridInfo2> gridInfo(ngrids);
  for (int index = 0; index < ngrids; ++index)
    {
      gridInfo[index].globalIndicesID = -1;
      gridInfo[index].needed = false;
    }

  int globalCellIndicesID = -1;
  int globalVertIndicesID = -1;
  int globalEdgeIndicesID = -1;
  for (int varID = 0; varID < numVars; ++varID)
    {
      const auto &var = varList.vars[varID];
      // clang-format off
      if      (var.name == "global_cell_indices") globalCellIndicesID = varID;
      else if (var.name == "global_vert_indices") globalVertIndicesID = varID;
      else if (var.name == "global_edge_indices") globalEdgeIndicesID = varID;
      // clang-format on
    }
  if (globalCellIndicesID != -1) selectedVars[globalCellIndicesID] = false;
  if (globalVertIndicesID != -1) selectedVars[globalVertIndicesID] = false;
  if (globalEdgeIndicesID != -1) selectedVars[globalEdgeIndicesID] = false;

  if (globalCellIndicesID != -1) gridInfo[varGridIndex[globalCellIndicesID]].globalIndicesID = globalCellIndicesID;
  if (globalVertIndicesID != -1) gridInfo[varGridIndex[globalVertIndicesID]].globalIndicesID = globalVertIndicesID;
  if (globalEdgeIndicesID != -1) gridInfo[varGridIndex[globalEdgeIndicesID]].globalIndicesID = globalEdgeIndicesID;

  for (int varID = 0; varID < numVars; ++varID)
    if (selectedVars[varID]) gridInfo[varGridIndex[varID]].needed = true;

  return gridInfo;
}

static std::vector<bool>
get_selected_vars(int nsel, int noff, const VarList &varList1)
{
  auto numVars = varList1.numVars();
  std::vector<bool> selectedVars(numVars, false);

  if (nsel == 0)
    {
      for (int varID = 0; varID < numVars; ++varID) selectedVars[varID] = true;
    }
  else
    {
      if (Options::cdoVerbose)
        for (int i = 0; i < nsel; ++i) cdo_print("name %d = %s", i + 1, cdo_operator_argv(noff + i));

      std::vector<bool> selfound(nsel);
      for (int i = 0; i < nsel; ++i) selfound[i] = false;

      for (int varID = 0; varID < numVars; ++varID)
        {
          for (int isel = 0; isel < nsel; isel++)
            {
              if (cdo_operator_argv(noff + isel) == varList1.vars[varID].name)
                {
                  selfound[isel] = true;
                  selectedVars[varID] = true;
                }
            }
        }

      int err = 0;
      for (int isel = 0; isel < nsel; isel++)
        {
          if (selfound[isel] == false)
            {
              err++;
              cdo_warning("Variable name %s not found!", cdo_operator_argv(noff + isel));
            }
        }
      if (err) cdo_abort("Could not find all requested variables: (%d/%d)", nsel - err, nsel);
    }

  return selectedVars;
}

static void
select_vars(const std::vector<bool> &selectedVars, int vlistID1, const VarList &varList1)
{
  auto nvars = varList1.numVars();
  int numVars = 0;

  for (int varID = 0; varID < nvars; ++varID)
    {
      if (selectedVars[varID])
        {
          numVars++;
          auto numLevels = varList1.vars[varID].nlevels;
          for (int levelID = 0; levelID < numLevels; levelID++) vlistDefFlag(vlistID1, varID, levelID, true);
        }
    }

  if (numVars == 0) cdo_abort("No variables selected!");
}

class Collgrid : public Process
{
public:
  using Process::Process;
  inline static CdoModule module = {
    .name = "Collgrid",
    .operators = { { "collgrid", CollgridHelp } },
    .aliases = {},
    .mode = EXPOSED,     // Module mode: 0:intern 1:extern
    .number = CDI_REAL,  // Allowed number type
    .constraints = { -1, 1, NoRestriction },
  };
  inline static RegisterEntry<Collgrid> registration = RegisterEntry<Collgrid>(module);

  int nxblocks = -1;

  CdoStreamID streamID1;
  int vlistID1;
  int taxisID1;
  CdoStreamID streamID2;
  int taxisID2;
  int vlistID2;

  int numFiles;

  std::vector<GridInfo2> gridInfo;
  std::vector<CollgridInfo> collgridInfo;
  std::vector<bool> collectVars2;
  std::vector<int> gridID2s;
  std::vector<int> varGridIndex;

  VarList varList2;
  std::vector<size_t> targetGridsize;

public:
  void
  init() override
  {
    numFiles = cdo_stream_cnt() - 1;
    std::string ofilename = cdo_get_stream_name(numFiles);

    if (!Options::cdoOverwriteMode && FileUtils::file_exists(ofilename) && !FileUtils::user_file_overwrite(ofilename))
      cdo_abort("Outputfile %s already exists!", ofilename);

    collgridInfo = std::vector<CollgridInfo>(numFiles);

    cdo::set_numfiles(numFiles + 8);

    for (int fileID = 0; fileID < numFiles; ++fileID)
      {
        auto streamID = cdo_open_read(fileID);
        auto vlistID = cdo_stream_inq_vlist(streamID);
        collgridInfo[fileID].streamID = streamID;
        collgridInfo[fileID].vlistID = vlistID;
        collgridInfo[fileID].varList = VarList(vlistID);
      }

    const auto &varList1 = collgridInfo[0].varList;
    vlistID1 = collgridInfo[0].vlistID;
    vlistClearFlag(vlistID1);

    // check that the contents is always the same
    for (int fileID = 1; fileID < numFiles; ++fileID)
      varList_compare(varList1, collgridInfo[fileID].varList, CmpVarList::Name | CmpVarList::NumLevels);

    auto nsel = cdo_operator_argc();
    int noff = 0;

    if (nsel > 0)
      {
        auto argument = cdo_operator_argv(0);
        if (argument == "gridtype=unstructured")
          {
            nsel--;
            noff++;
            globalGridType = GRID_UNSTRUCTURED;
          }
        else
          {
            int len = argument.size();
            while (--len >= 0 && std::isdigit(argument[len]))
              ;

            if (len == -1)
              {
                nsel--;
                noff++;
                nxblocks = parameter_to_int(argument);
              }
          }
      }

    auto selectedVars = get_selected_vars(nsel, noff, varList1);
    varGridIndex = get_var_gridindex(vlistID1, varList1);
    gridInfo = get_gridinfo(vlistID1, varList1, varGridIndex, selectedVars);
    select_vars(selectedVars, vlistID1, varList1);

    vlistID2 = vlistCreate();
    cdo_vlist_copy_flag(vlistID2, vlistID1);
    vlistDefNtsteps(vlistID2, vlistNtsteps(vlistID1));

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

    // if (Options::cdoVerbose) vlistPrint(vlistID1);
    // if (Options::cdoVerbose) vlistPrint(vlistID2);

    auto ngrids1 = vlistNumGrids(vlistID1);
    auto ngrids2 = vlistNumGrids(vlistID2);
    targetGridsize.resize(ngrids1, 0);

    for (int fileID = 0; fileID < numFiles; ++fileID)
      {
        collgridInfo[fileID].cellIndex.resize(ngrids1);
        for (int gindex = 0; gindex < ngrids1; ++gindex)
          {
            if (gridInfo[gindex].needed)
              {
                auto patchSize = gridInqSize(vlistGrid(collgridInfo[fileID].vlistID, gindex));
                collgridInfo[fileID].cellIndex[gindex].resize(patchSize);
              }
          }
      }

    gridID2s = std::vector<int>(ngrids2);

    for (int i2 = 0; i2 < ngrids2; ++i2)
      {
        int i1;
        for (i1 = 0; i1 < ngrids1; ++i1)
          if (vlistGrid(vlistID1, i1) == vlistGrid(vlistID2, i2)) break;

        gridID2s[i2] = gen_coll_grid(ngrids2, numFiles, collgridInfo, i1, nxblocks);
        targetGridsize[i1] = gridInqSize(gridID2s[i2]);
      }

    for (int i = 0; i < ngrids2; ++i)
      {
        if (gridID2s[i] != -1) vlistChangeGridIndex(vlistID2, i, gridID2s[i]);
      }

    varList2 = VarList(vlistID2);
    auto numVars2 = varList2.numVars();

    collectVars2 = std::vector<bool>(numVars2, false);
    for (int varID = 0; varID < numVars2; ++varID)
      {
        auto gridID = varList2.vars[varID].gridID;
        for (int i = 0; i < ngrids2; ++i)
          {
            if (gridID2s[i] != -1 && gridID == vlistGrid(vlistID2, i))
              {
                collectVars2[varID] = true;
                break;
              }
          }
      }

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

  void
  run() override
  {
    Field field2;

    int numFields0 = 0;
    int tsID = 0;
    do {
        numFields0 = cdo_stream_inq_timestep(collgridInfo[0].streamID, tsID);
        for (int fileID = 1; fileID < numFiles; ++fileID)
          {
            auto numFields = cdo_stream_inq_timestep(collgridInfo[fileID].streamID, tsID);
            if (numFields != numFields0)
              cdo_abort("Number of fields at time step %d of %s and %s differ!", tsID + 1, cdo_get_stream_name(0),
                        cdo_get_stream_name(fileID));
          }

        cdo_taxis_copy_timestep(taxisID2, taxisID1);

        if (numFields0 > 0) cdo_def_timestep(streamID2, tsID);

        for (int fieldID = 0; fieldID < numFields0; ++fieldID)
          {
            int varID = 0, levelID = 0;
            for (int fileID = numFiles - 1; fileID >= 0; fileID--)
              {
                auto [varIDx, levelIDx] = cdo_inq_field(collgridInfo[fileID].streamID);
                varID = varIDx;
                levelID = levelIDx;
              }

            // if (Options::cdoVerbose && tsID == 0) printf(" tsID, fieldID, varID, levelID %d %d %d %d\n", tsID, fieldID, varID,
            // levelID);

            auto gindex = varGridIndex[varID];
            if (gridInfo[gindex].needed && gridInfo[gindex].globalIndicesID == varID)
              {
                Varray<double> cellIndex;
                for (int fileID = 0; fileID < numFiles; ++fileID)
                  {
                    auto &collgrid = collgridInfo[fileID];
                    auto patchSize = collgrid.varList.vars[varID].gridsize;
                    if (cellIndex.size() < patchSize) cellIndex.resize(patchSize);
                    size_t numMissVals;
                    cdo_read_field(collgrid.streamID, cellIndex.data(), &numMissVals);
                    for (size_t i = 0; i < patchSize; ++i)
                      {
                        auto index = std::lround(cellIndex[i]);
                        if (index >= (long) targetGridsize[gindex])
                          cdo_abort("global cell index out of range (%ld/%zu)", index, targetGridsize[gindex]);
                        collgrid.cellIndex[gindex][i] = index - 1;
                      }
                  }
              }

            if (vlistInqFlag(vlistID1, varID, levelID) == true)
              {
                auto varID2 = vlistFindVar(vlistID2, varID);
                auto levelID2 = vlistFindLevel(vlistID2, varID, levelID);
                // if (Options::cdoVerbose && tsID == 0) printf("varID %d %d levelID %d %d\n", varID, varID2, levelID, levelID2);

                field2.init(varList2.vars[varID2]);
                field_fill(field2, field2.missval);

#ifdef _OPENMP
#pragma omp parallel for default(shared)
#endif
                for (int fileID = 0; fileID < numFiles; ++fileID)
                  {
                    auto &collgrid = collgridInfo[fileID];
                    auto &field1 = collgrid.field;
                    field1.init(collgrid.varList.vars[varID]);
                    cdo_read_field(collgrid.streamID, field1);

                    if (collectVars2[varID2]) collect_cells(field1, field2, collgrid.cellIndex[gindex]);
                  }

                cdo_def_field(streamID2, varID2, levelID2);

                if (collectVars2[varID2])
                  {
                    field_num_mv(field2);
                    cdo_write_field(streamID2, field2);
                  }
                else { cdo_write_field(streamID2, collgridInfo[0].field); }
              }
          }

        tsID++;
      }
    while (numFields0 > 0);
  }

  void
  close() override
  {
    for (int fileID = 0; fileID < numFiles; ++fileID) cdo_stream_close(collgridInfo[fileID].streamID);

    cdo_stream_close(streamID2);
  }
};
