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

  Copyright (C) 2003-2021 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

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

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

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

#ifdef HAVE_LIBNETCDF
#include "netcdf.h"
#endif

#include "cdo_options.h"
#include "process_int.h"
#include "griddes.h"
#include <mpim_grid.h>
#include "remap_vars.h"
#include "remap.h"  // SubmapType
#include "cdo_zaxis.h"

#ifdef HAVE_LIBNETCDF
static void
nce(int istat)
{
  // This routine provides a simple interface to NetCDF error message routine.
  if (istat != NC_NOERR) cdo_abort(nc_strerror(istat));
}

static size_t
cdfReadDimlen(int ncfileid, const char *dimname)
{
  size_t dimlen = 0;
  int ncdimid;
  const int status = nc_inq_dimid(ncfileid, dimname, &ncdimid);
  if (status == NC_NOERR) nce(nc_inq_dimlen(ncfileid, ncdimid, &dimlen));
  return dimlen;
}

static void
cdfReadAttText(int ncfileid, int ncvarid, const char *name, std::string &text)
{
  size_t attlen;
  nc_type atttype;
  nce(nc_inq_atttype(ncfileid, ncvarid, name, &atttype));
  nce(nc_inq_attlen(ncfileid, ncvarid, name, &attlen));

  if (atttype == NC_CHAR)
    {
      char *attbuf = new char[attlen + 1];

      nce(nc_get_att_text(ncfileid, ncvarid, name, attbuf));
      attbuf[attlen] = 0;
      text = attbuf;

      delete[] attbuf;
    }
}

static void
cdfReadVarInt(int ncfileid, const char *name, int *array)
{
  int ncvarid;
  nce(nc_inq_varid(ncfileid, name, &ncvarid));
  nce(nc_get_var_int(ncfileid, ncvarid, array));
}

static void
cdfReadVarSize(int ncfileid, const char *name, size_t len, size_t *array)
{
  if (len < 0x7FFFFC00)  // 2GB
    {
      std::vector<int> iarray(len);
      cdfReadVarInt(ncfileid, name, iarray.data());
      for (size_t i = 0; i < len; ++i) array[i] = (size_t) iarray[i];
    }
#ifdef HAVE_NETCDF4
  else
    {
      int ncvarid;
      nce(nc_inq_varid(ncfileid, name, &ncvarid));
      nce(nc_get_var_ulonglong(ncfileid, ncvarid, (unsigned long long *) array));
    }
#endif
}

static void
cdfReadVarDouble(int ncfileid, const char *name, double *array)
{
  int ncvarid;
  nce(nc_inq_varid(ncfileid, name, &ncvarid));
  nce(nc_get_var_double(ncfileid, ncvarid, array));
}

static void
cdfReadCoordinateRadian(int ncfileid, const char *name, size_t size, double *array)
{
  int ncvarid;
  nce(nc_inq_varid(ncfileid, name, &ncvarid));
  nce(nc_get_var_double(ncfileid, ncvarid, array));

  std::string grid_units;
  cdfReadAttText(ncfileid, ncvarid, "units", grid_units);
  grid_to_radian(grid_units.c_str(), size, array, name);
}

struct RemapAttributes
{
  std::string map_name;
  std::string history;
  std::string cdo_version;
};

struct RemapGridW
{
  std::string name;
  int rank;         // rank of the grid
  size_t ncells;    // total points on the grid
  size_t ncorners;  // number of corners for each grid cell
  size_t dims[2];   // size of grid dimension

  std::vector<int> mask;  // flag which cells participate

  Varray<double> cell_center_lon;  // lon/lat coordinates for
  Varray<double> cell_center_lat;  // each grid center in radians
  Varray<double> cell_corner_lon;  // lon/lat coordinates for
  Varray<double> cell_corner_lat;  // each grid corner in radians

  Varray<double> cell_area;  // total area of each grid cell
  Varray<double> cell_frac;  // fractional area of grid cells participating in remapping

  RemapGridW() : rank(0), ncells(0), ncorners(0)
  {
    dims[0] = 0;
    dims[1] = 0;
  }

  void
  setNumCells(size_t ncells_)
  {
    ncells = ncells_;

    mask.resize(ncells);

    cell_center_lon.resize(ncells);
    cell_center_lat.resize(ncells);

    cell_frac.resize(ncells, 0.0);
  }

  void
  setNumCorners(size_t ncorners_)
  {
    ncorners = ncorners_;

    cell_corner_lon.resize(ncorners * ncells, 0.0);
    cell_corner_lat.resize(ncorners * ncells, 0.0);
  }
};

void
remapGridWAlloc(RemapMethod mapType, RemapGridW &grid)
{
  if (mapType == RemapMethod::CONSERV_SCRIP || mapType == RemapMethod::CONSERV)
    {
      grid.cell_area.resize(grid.ncells, 0.0);
    }
}

struct RemapVarsW
{
  bool sort_add = false;
  RemapMethod mapType{ RemapMethod::UNDEF };  // identifier for remapping method
  NormOpt normOpt{ NormOpt::NONE };           // option for normalization (conserv only)
  size_t num_links = 0;                       // number of links for remapping
  size_t num_wts = 0;                         // num of weights used in remapping

  std::vector<size_t> src_cell_add;  // source grid address for each link
  std::vector<size_t> tgt_cell_add;  // target grid address for each link
  Varray<double> wts;                // map weights for each link [num_links*num_wts]
};

static void
remapVarsWInit(RemapMethod mapType, int remapOrder, RemapVarsW &rv)
{
  rv.sort_add = (mapType == RemapMethod::CONSERV_SCRIP);

  // Determine the number of weights
  rv.num_wts = (mapType == RemapMethod::CONSERV_SCRIP) ? 3 : ((mapType == RemapMethod::BICUBIC) ? 4 : 1);
  if (mapType == RemapMethod::CONSERV && remapOrder == 2) rv.num_wts = 3;
}

static NormOpt
remapGetNormOpt(const std::string &normOptStr)
{
  NormOpt normOpt(NormOpt::NONE);
  // clang-format off
  if      (normOptStr == "none")     normOpt = NormOpt::NONE;
  else if (normOptStr == "fracarea") normOpt = NormOpt::FRACAREA;
  else if (normOptStr == "destarea") normOpt = NormOpt::DESTAREA;
  else
    {
      cdo_print("normalize_opt = %s", normOptStr.c_str());
      cdo_abort("Invalid normalization option");
    }
  // clang-format on

  if (Options::cdoVerbose) cdo_print("normalize_opt = %s", normOptStr.c_str());

  return normOpt;
}

RemapMethod getMapType(int ncfileid, SubmapType &submapType, int &numNeighbors, int &remapOrder);

static void
readRemapGridScrip(const int ncfileid, const std::string &prefix, bool lgridarea, RemapGridW &grid)
{
  // Read all variables of the grid
  cdfReadVarSize(ncfileid, (prefix + "_dims").c_str(), 2, grid.dims);

  cdfReadVarInt(ncfileid, (prefix + "_imask").c_str(), grid.mask.data());

  cdfReadCoordinateRadian(ncfileid, (prefix + "_center_lat").c_str(), grid.ncells, grid.cell_center_lat.data());
  cdfReadCoordinateRadian(ncfileid, (prefix + "_center_lon").c_str(), grid.ncells, grid.cell_center_lon.data());

  if (grid.ncorners)
    {
      const size_t size = grid.ncorners * grid.ncells;
      cdfReadCoordinateRadian(ncfileid, (prefix + "_corner_lat").c_str(), size, grid.cell_corner_lat.data());
      cdfReadCoordinateRadian(ncfileid, (prefix + "_corner_lon").c_str(), size, grid.cell_corner_lon.data());
    }

  if (lgridarea) cdfReadVarDouble(ncfileid, (prefix + "_area").c_str(), grid.cell_area.data());
  cdfReadVarDouble(ncfileid, (prefix + "_frac").c_str(), grid.cell_frac.data());
}

static void
readRemapWeightsScrip(const int ncfileid, RemapVarsW &rv)
{
  cdfReadVarSize(ncfileid, "src_address", rv.num_links, rv.src_cell_add.data());
  cdfReadVarSize(ncfileid, "dst_address", rv.num_links, rv.tgt_cell_add.data());

  for (size_t i = 0; i < rv.num_links; ++i) rv.src_cell_add[i]--;
  for (size_t i = 0; i < rv.num_links; ++i) rv.tgt_cell_add[i]--;

  cdfReadVarDouble(ncfileid, "remap_matrix", rv.wts.data());
}

static void
readRemapFileScrip(const char *remapFile, RemapAttributes &remapAtts, RemapMethod &mapType, SubmapType &submapType,
                   int &numNeighbors, int &remapOrder, RemapGridW &src_grid, RemapGridW &tgt_grid, RemapVarsW &rv)
{
  // The routine reads a NetCDF file to extract remapping info in SCRIP format

  // Open file and read some global information
  const int ncfileid = cdo_cdf_openread(remapFile);

  cdfReadAttText(ncfileid, NC_GLOBAL, "history", remapAtts.history);

  // Map name
  cdfReadAttText(ncfileid, NC_GLOBAL, "title", remapAtts.map_name);

  if (Options::cdoVerbose)
    {
      cdo_print("Reading remapping: %s", remapAtts.map_name.c_str());
      cdo_print("From file: %s", remapFile);
    }

  // Map Type
  mapType = getMapType(ncfileid, submapType, numNeighbors, remapOrder);
  const bool lgridarea = (mapType == RemapMethod::CONSERV || mapType == RemapMethod::CONSERV_SCRIP);

  remapVarsWInit(mapType, remapOrder, rv);

  rv.mapType = mapType;
  rv.sort_add = false;

  // Normalization option
  std::string normalizeOptStr;  // character string for normalization option
  cdfReadAttText(ncfileid, NC_GLOBAL, "normalization", normalizeOptStr);
  rv.normOpt = remapGetNormOpt(normalizeOptStr);

  // File convention
  std::string convention;  // character string for output convention
  cdfReadAttText(ncfileid, NC_GLOBAL, "conventions", convention);
  if (convention != "SCRIP")
    {
      cdo_print("convention = %s", convention.c_str());
      if (convention == "NCAR-CSM")
        cdo_abort("Unsupported file convention: %s!", convention.c_str());
      else
        cdo_abort("Unknown file convention!");
    }

  // Read some additional global attributes

  // Source and destination grid names
  cdfReadAttText(ncfileid, NC_GLOBAL, "source_grid", src_grid.name);
  cdfReadAttText(ncfileid, NC_GLOBAL, "dest_grid", tgt_grid.name);

  if (Options::cdoVerbose) cdo_print("Remapping between: %s and %s", src_grid.name.c_str(), tgt_grid.name.c_str());

  // Read dimension information
  src_grid.setNumCells(cdfReadDimlen(ncfileid, "src_grid_size"));
  tgt_grid.setNumCells(cdfReadDimlen(ncfileid, "dst_grid_size"));

  src_grid.setNumCorners(cdfReadDimlen(ncfileid, "src_grid_corners"));
  tgt_grid.setNumCorners(cdfReadDimlen(ncfileid, "dst_grid_corners"));

  src_grid.rank = cdfReadDimlen(ncfileid, "src_grid_rank");
  tgt_grid.rank = cdfReadDimlen(ncfileid, "dst_grid_rank");

  remapGridWAlloc(rv.mapType, src_grid);
  remapGridWAlloc(rv.mapType, tgt_grid);

  rv.num_links = cdfReadDimlen(ncfileid, "num_links");
  //  if ( rv.num_links == 0 ) cdo_abort("Number of remap links is 0, no remap weights found!");
  rv.num_wts = cdfReadDimlen(ncfileid, "num_wgts");

  // Allocate address and weight arrays
  if (rv.num_links > 0)
    {
      rv.src_cell_add.resize(rv.num_links);
      rv.tgt_cell_add.resize(rv.num_links);
      rv.wts.resize(rv.num_wts * rv.num_links);
    }

  readRemapGridScrip(ncfileid, "src_grid", lgridarea, src_grid);
  readRemapGridScrip(ncfileid, "dst_grid", lgridarea, tgt_grid);

  if (rv.num_links > 0) readRemapWeightsScrip(ncfileid, rv);

  // Close input file
  cdo_cdf_close(ncfileid);
}  // readRemapFileScrip

static int
cdfDefDim(int ncfileid, const char *name, size_t len)
{
  int ncdimid = -1;
  nce(nc_def_dim(ncfileid, name, len, &ncdimid));
  return ncdimid;
}

static int
cdfDefVar(int ncfileid, const char *name, nc_type xtype, int ndims, const int *dimidsp)
{
  int ncvarid = -1;
  nce(nc_def_var(ncfileid, name, xtype, ndims, dimidsp, &ncvarid));
  return ncvarid;
}

static void
cdfWriteAttText(int ncfileid, int ncvarid, const char *name, const std::string &text)
{
  if (text.size()) nce(nc_put_att_text(ncfileid, ncvarid, name, text.size(), text.c_str()));
}

static void
cdfWriteVarInt(int ncfileid, int ncvarid, int *array)
{
  nce(nc_put_var_int(ncfileid, ncvarid, array));
}

static void
cdfWriteVarDouble(int ncfileid, int ncvarid, double *array)
{
  nce(nc_put_var_double(ncfileid, ncvarid, array));
}

static void
cdfWriteVarSize(int ncfileid, int ncvarid, nc_type sizetype, size_t len, size_t *array)
{
  if (len == 0) return;

  if (sizetype == NC_INT)
    {
      std::vector<int> iarray(len);
      for (size_t i = 0; i < len; ++i) iarray[i] = (int) array[i];
      nce(nc_put_var_int(ncfileid, ncvarid, iarray.data()));
    }
#ifdef HAVE_NETCDF4
  else
    {
      nce(nc_put_var_ulonglong(ncfileid, ncvarid, (unsigned long long *) array));
    }
#endif
}

static void
checkRemapFilesize(const RemapGridW &src_grid, const RemapGridW &tgt_grid, const RemapVarsW &rv, int &writemode, nc_type &sizetype)
{
  const size_t nlinks = rv.num_links;
  const size_t nele1 = 4 * 8 + 4 + src_grid.ncorners * 2 * 8;
  const size_t nele2 = 4 * 8 + 4 + tgt_grid.ncorners * 2 * 8;
  const size_t filesize = src_grid.ncells * nele1 + tgt_grid.ncells * nele2 + nlinks * (4 + 4 + rv.num_wts * 8);

  if (Options::cdoVerbose)
    {
      cdo_print("Number of remap links:       %zu", nlinks);
      cdo_print("Filesize for remap weights: ~%zu", filesize);
    }

  if (filesize > 0x7FFFFC00)  // 2**31 - 1024 (<2GB)
    {
      const size_t maxlinks = 0x3FFFFFFF;  // 1GB
      const auto gridsizemax = (src_grid.ncells > tgt_grid.ncells) ? src_grid.ncells : tgt_grid.ncells;
      if (nlinks > maxlinks || filesize > 8 * maxlinks || gridsizemax > 0x7FFFFC00)
        {
#ifdef HAVE_NETCDF4
          if (Options::cdoVerbose) cdo_print("Store weights and links to NetCDF4!");
          writemode |= NC_NETCDF4;
          if (gridsizemax > 0x7FFFFC00)
            sizetype = NC_UINT64;
          else
            writemode |= NC_CLASSIC_MODEL;
#else
          cdo_print("Number of remap links %zu exceeds maximum of %zu and NetCDF 4 is not available!", nlinks, maxlinks);
#endif
        }
      else
        {
#ifdef NC_64BIT_OFFSET
          writemode |= NC_64BIT_OFFSET;
          if (Options::cdoVerbose) cdo_print("Store weights and links to NetCDF2!");
#else
          cdo_print("Filesize for remap weights maybe too large!");
#endif
        }
    }
}

struct CDFgrid
{
  int dims_id;
  int cntrlat_id;
  int cntrlon_id;
  int crnrlat_id;
  int crnrlon_id;
  int imask_id;
  int area_id;
  int frac_id;
};

static CDFgrid
defineRemapGridScrip(const int ncfileid, const std::string &prefix, nc_type sizetype, bool lgridarea, const RemapGridW &grid)
{
  CDFgrid cdfGrid;

  // Define grid size dimension
  int nc_size_id = cdfDefDim(ncfileid, (prefix + "_size").c_str(), grid.ncells);
  // Define grid corner dimension
  int nc_corn_id = grid.ncorners ? cdfDefDim(ncfileid, (prefix + "_corners").c_str(), grid.ncorners) : -1;
  // Define grid rank dimension
  int nc_rank_id = cdfDefDim(ncfileid, (prefix + "_rank").c_str(), grid.rank);

  // Define grid dimensions
  cdfGrid.dims_id = cdfDefVar(ncfileid, (prefix + "_dims").c_str(), sizetype, 1, &nc_rank_id);
  // Define grid center latitude array
  cdfGrid.cntrlat_id = cdfDefVar(ncfileid, (prefix + "_center_lat").c_str(), NC_DOUBLE, 1, &nc_size_id);
  // Define grid center longitude array
  cdfGrid.cntrlon_id = cdfDefVar(ncfileid, (prefix + "_center_lon").c_str(), NC_DOUBLE, 1, &nc_size_id);

  // Define grid corner lat/lon arrays
  int nc_dims2_id[2] = { nc_size_id, nc_corn_id };
  cdfGrid.crnrlat_id = grid.ncorners ? cdfDefVar(ncfileid, (prefix + "_corner_lat").c_str(), NC_DOUBLE, 2, nc_dims2_id) : -1;
  cdfGrid.crnrlon_id = grid.ncorners ? cdfDefVar(ncfileid, (prefix + "_corner_lon").c_str(), NC_DOUBLE, 2, nc_dims2_id) : -1;

  // Define units for all coordinate arrays
  std::string gridunits = "radians";
  cdfWriteAttText(ncfileid, cdfGrid.cntrlat_id, "units", gridunits);
  cdfWriteAttText(ncfileid, cdfGrid.cntrlon_id, "units", gridunits);
  if (grid.ncorners)
    {
      cdfWriteAttText(ncfileid, cdfGrid.crnrlat_id, "units", gridunits);
      cdfWriteAttText(ncfileid, cdfGrid.crnrlon_id, "units", gridunits);
    }

  // Define grid mask
  cdfGrid.imask_id = cdfDefVar(ncfileid, (prefix + "_imask").c_str(), NC_INT, 1, &nc_size_id);
  cdfWriteAttText(ncfileid, cdfGrid.imask_id, "units", "unitless");

  // Define grid area array
  cdfGrid.area_id = -1;  // id for area of source grid cells
  if (lgridarea)
    {
      cdfGrid.area_id = cdfDefVar(ncfileid, (prefix + "_area").c_str(), NC_DOUBLE, 1, &nc_size_id);
      cdfWriteAttText(ncfileid, cdfGrid.area_id, "units", "square radians");
    }

  // Define grid fraction array
  cdfGrid.frac_id = cdfDefVar(ncfileid, (prefix + "_frac").c_str(), NC_DOUBLE, 1, &nc_size_id);
  cdfWriteAttText(ncfileid, cdfGrid.frac_id, "units", "unitless");

  return cdfGrid;
}

static void
writeRemapGridScrip(const int ncfileid, const CDFgrid &cdfGrid, bool lgridarea, RemapGridW &grid)
{
  int dims[2];
  dims[0] = (int) grid.dims[0];
  dims[1] = (int) grid.dims[1];
  cdfWriteVarInt(ncfileid, cdfGrid.dims_id, dims);

  cdfWriteVarInt(ncfileid, cdfGrid.imask_id, grid.mask.data());

  if (grid.cell_center_lat.size()) cdfWriteVarDouble(ncfileid, cdfGrid.cntrlat_id, grid.cell_center_lat.data());
  if (grid.cell_center_lon.size()) cdfWriteVarDouble(ncfileid, cdfGrid.cntrlon_id, grid.cell_center_lon.data());

  if (grid.ncorners)
    {
      cdfWriteVarDouble(ncfileid, cdfGrid.crnrlat_id, grid.cell_corner_lat.data());
      cdfWriteVarDouble(ncfileid, cdfGrid.crnrlon_id, grid.cell_corner_lon.data());
    }

  if (lgridarea) cdfWriteVarDouble(ncfileid, cdfGrid.area_id, grid.cell_area.data());

  cdfWriteVarDouble(ncfileid, cdfGrid.frac_id, grid.cell_frac.data());
}

static std::string
remapSetMapMethod(RemapMethod mapType, SubmapType submapType, int numNeighbors)
{
  std::string mapMethod;
  const bool submapLAF = (submapType == SubmapType::LAF);

  // clang-format off
  switch (mapType)
    {
    case RemapMethod::CONSERV_SCRIP:
      mapMethod = submapLAF ? "Largest area fraction" : "Conservative remapping";
      break;
    case RemapMethod::CONSERV:
      // mapMethod = submapLAF ? "Largest area fraction" : "Conservative remapping using clipping on sphere";
      mapMethod = "Conservative remapping using clipping on sphere";
      break;
    case RemapMethod::BILINEAR: mapMethod = "Bilinear remapping"; break;
    case RemapMethod::BICUBIC:  mapMethod = "Bicubic remapping"; break;
    case RemapMethod::DISTWGT:
      mapMethod = (numNeighbors == 1) ? "Nearest neighbor" : "Distance weighted avg of nearest neighbors";
      break;
    default:
      mapMethod = "unknown";
    }
  // clang-format on

  return mapMethod;
}

static void
writeRemapFileScrip(const char *remapFile, const RemapAttributes &remapAtts, RemapMethod mapType, SubmapType submapType,
                    int numNeighbors, int remapOrder, RemapGridW &src_grid, RemapGridW &tgt_grid, RemapVarsW &rv)
{
  // Writes remap data to a NetCDF file using SCRIP conventions

#ifdef HAVE_LIBNETCDF

  int nc_dims2_id[2];  // NetCDF ids for 2d array dims

  const bool lgridarea = mapType == RemapMethod::CONSERV_SCRIP || mapType == RemapMethod::CONSERV;

  // if ( rv.num_links == 0 ) cdo_abort("Number of remap links is 0, no remap weights found!");

  int writemode = NC_CLOBBER;
  nc_type sizetype = NC_INT;
  checkRemapFilesize(src_grid, tgt_grid, rv, writemode, sizetype);

  // Create NetCDF file for mapping and define some global attributes
  int ncfileid = -1;
  nce(nc_create(remapFile, writemode, &ncfileid));

  // Map name
  cdfWriteAttText(ncfileid, NC_GLOBAL, "title", remapAtts.map_name);

  // Normalization option
  std::string normalizeOptStr;  // character string for normalization option
  // clang-format off
  switch (rv.normOpt)
    {
    case NormOpt::NONE:     normalizeOptStr = "none";     break;
    case NormOpt::FRACAREA: normalizeOptStr = "fracarea"; break;
    case NormOpt::DESTAREA: normalizeOptStr = "destarea"; break;
    default: normalizeOptStr = "unknown";
    }
  // clang-format on
  cdfWriteAttText(ncfileid, NC_GLOBAL, "normalization", normalizeOptStr);

  // Map method
  std::string mapMethod = remapSetMapMethod(mapType, submapType, numNeighbors);
  cdfWriteAttText(ncfileid, NC_GLOBAL, "map_method", mapMethod);

  // Remap order
  if (mapType == RemapMethod::CONSERV_SCRIP && submapType == SubmapType::NONE)
    nce(nc_put_att_int(ncfileid, NC_GLOBAL, "remap_order", NC_INT, 1L, &remapOrder));

  // File convention
  cdfWriteAttText(ncfileid, NC_GLOBAL, "conventions", "SCRIP");

  // Source and destination grid names
  cdfWriteAttText(ncfileid, NC_GLOBAL, "source_grid", src_grid.name);
  cdfWriteAttText(ncfileid, NC_GLOBAL, "dest_grid", tgt_grid.name);

  // History
  cdfWriteAttText(ncfileid, NC_GLOBAL, "history", remapAtts.history);

  cdfWriteAttText(ncfileid, NC_GLOBAL, "CDO", remapAtts.cdo_version);

  // Define grids
  CDFgrid cdfSrcGrid = defineRemapGridScrip(ncfileid, "src_grid", sizetype, lgridarea, src_grid);
  CDFgrid cdfTgtGrid = defineRemapGridScrip(ncfileid, "dst_grid", sizetype, lgridarea, tgt_grid);

  // Define map size dimensions
  const int nc_numlinks_id = cdfDefDim(ncfileid, "num_links", rv.num_links);
  const int nc_numwgts_id = cdfDefDim(ncfileid, "num_wgts", rv.num_wts);

  // Define mapping arrays
  const int nc_srcadd_id = cdfDefVar(ncfileid, "src_address", sizetype, 1, &nc_numlinks_id);
  const int nc_dstadd_id = cdfDefVar(ncfileid, "dst_address", sizetype, 1, &nc_numlinks_id);

  nc_dims2_id[0] = nc_numlinks_id;
  nc_dims2_id[1] = nc_numwgts_id;
  const int nc_rmpmatrix_id = cdfDefVar(ncfileid, "remap_matrix", NC_DOUBLE, 2, nc_dims2_id);

  // End definition stage

  nce(nc_enddef(ncfileid));

  // Write mapping data

  writeRemapGridScrip(ncfileid, cdfSrcGrid, lgridarea, src_grid);
  writeRemapGridScrip(ncfileid, cdfTgtGrid, lgridarea, tgt_grid);

  for (size_t i = 0; i < rv.num_links; ++i) rv.src_cell_add[i]++;
  for (size_t i = 0; i < rv.num_links; ++i) rv.tgt_cell_add[i]++;

  cdfWriteVarSize(ncfileid, nc_srcadd_id, sizetype, rv.num_links, rv.src_cell_add.data());
  cdfWriteVarSize(ncfileid, nc_dstadd_id, sizetype, rv.num_links, rv.tgt_cell_add.data());
  cdfWriteVarDouble(ncfileid, nc_rmpmatrix_id, rv.wts.data());

  nce(nc_close(ncfileid));

#else
  cdo_abort("NetCDF support not compiled in!");
#endif

}  // remap_write_data_scrip

static void
checkAreas(size_t n_a, const Varray<double> &area_a, const Varray<double> &area_b, size_t n_s, const size_t *col, const size_t *row,
           const double *S)
{
  Varray<double> sum(n_a, 0.0);

  // sum weighted ratio of true areas
  //      ’a’ is source; ’b’ is destination
  for (size_t i = 0; i < n_s; ++i)  // loop over all elements of S (the weights)
    sum[col[i]] = sum[col[i]] + S[i] * area_a[col[i]] / area_b[row[i]];

  // check that sums are equal to 1 (within tolerance of 1.e-6)
  for (size_t i = 0; i < n_a; ++i)  // loop over all source cells
    if (std::fabs(sum[i] - 1.0) > 1.e-6) printf("ERROR\n");
  printf("OK\n");
}

static void
verifyWeights(const char *remapFile)
{
  RemapMethod mapType(RemapMethod::UNDEF);
  SubmapType submapType(SubmapType::NONE);
  int numNeighbors = 0;
  int remapOrder = 0;
  RemapGridW src_grid, tgt_grid;
  RemapVarsW remapVars;
  RemapAttributes remapAtts;

  readRemapFileScrip(remapFile, remapAtts, mapType, submapType, numNeighbors, remapOrder, src_grid, tgt_grid, remapVars);

  checkAreas(src_grid.ncells, src_grid.cell_area, tgt_grid.cell_area, remapVars.num_links, remapVars.src_cell_add.data(),
             remapVars.tgt_cell_add.data(), remapVars.wts.data());
}

static void
writeRemapScrip(const char *remapFileIn, const char *remapFileOut)
{
  RemapMethod mapType(RemapMethod::UNDEF);
  SubmapType submapType(SubmapType::NONE);
  int numNeighbors = 0;
  int remapOrder = 0;
  RemapGridW src_grid, tgt_grid;
  RemapVarsW remapVars;
  RemapAttributes remapAtts;

  readRemapFileScrip(remapFileIn, remapAtts, mapType, submapType, numNeighbors, remapOrder, src_grid, tgt_grid, remapVars);

  /*
  char history[1024] = "date and time";
  time_t date_and_time_in_sec = time(NULL);
  if (date_and_time_in_sec != -1)
    {
      struct tm *date_and_time = localtime(&date_and_time_in_sec);
      (void) strftime(history, 1024, "%d %b %Y : ", date_and_time);
      strcat(history, command_line());
    }
    */
  if (Options::VersionInfo) remapAtts.cdo_version = cdo_comment();
  writeRemapFileScrip(remapFileOut, remapAtts, mapType, submapType, numNeighbors, remapOrder, src_grid, tgt_grid, remapVars);
}
#endif

void *
Remapweights(void *argument)
{
  cdo_initialize(argument);

#ifdef HAVE_LIBNETCDF
  const auto VERIFYWEIGHTS = cdo_operator_add("verifyweights", 0, 1, "remap file name");
  const auto WRITEREMAPSCRIP = cdo_operator_add("writeremapscrip", 0, 2, "input and output remap file name");

  const auto operatorID = cdo_operator_id();

  operator_input_arg(cdo_operator_enter(operatorID));
  const auto nfiles = cdo_operator_f2(operatorID);
  operator_check_argc(nfiles);

  const char *remapFileIn = cdo_operator_argv(0).c_str();
  const char *remapFileOut = (nfiles == 2) ? cdo_operator_argv(1).c_str() : nullptr;

  // clang-format off
  if      (operatorID == VERIFYWEIGHTS) verifyWeights(remapFileIn);
  else if (operatorID == WRITEREMAPSCRIP) writeRemapScrip(remapFileIn, remapFileOut);
    // clang-format on
#else
  cdo_abort("NetCDF support not compiled in!");
#endif

  cdo_finish();

  return nullptr;
}
