/*
  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 <time.h>

#include <cdi.h>

#include "cdo_options.h"
#include "process_int.h"
#include "griddes.h"
#include <mpim_grid.h>
#include "remap.h"
#include "commandline.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 void
writeLinks(int nc_file_id, int nc_add_id, nc_type sizetype, size_t num_links, size_t *cell_add)
{
  if (num_links == 0) return;

  if (sizetype == NC_INT)
    {
      std::vector<int> intadd(num_links);
      for (size_t i = 0; i < num_links; ++i) intadd[i] = (int) cell_add[i];
      nce(nc_put_var_int(nc_file_id, nc_add_id, &intadd[0]));
    }
#ifdef HAVE_NETCDF4
  else
    {
      nce(nc_put_var_ulonglong(nc_file_id, nc_add_id, (unsigned long long *) cell_add));
    }
#endif
}

static void
readLinks(int nc_file_id, int nc_add_id, size_t num_links, size_t *cell_add)
{
  if (num_links < 0x7FFFFC00)  // 2GB
    {
      std::vector<int> intadd(num_links);
      nce(nc_get_var_int(nc_file_id, nc_add_id, &intadd[0]));
      for (size_t i = 0; i < num_links; ++i) cell_add[i] = (size_t) intadd[i];
    }
#ifdef HAVE_NETCDF4
  else
    {
      nce(nc_get_var_ulonglong(nc_file_id, nc_add_id, (unsigned long long *) cell_add));
    }
#endif
}
#endif

#ifdef HAVE_LIBNETCDF
static int
set_write_mode(bool need_src_cell_corners, size_t src_grid_size, size_t src_grid_nc, bool need_tgt_cell_corners,
               size_t tgt_grid_size, size_t tgt_grid_nc, size_t num_links, size_t num_wts, nc_type &sizetype)
{
  int writemode = NC_CLOBBER;

  size_t nlinks = num_links;
  size_t nele1 = 4 * 8 + 4;
  size_t nele2 = 4 * 8 + 4;
  if (need_src_cell_corners) nele1 += src_grid_nc * 2 * 8;
  if (need_tgt_cell_corners) nele2 += tgt_grid_nc * 2 * 8;
  size_t filesize = src_grid_size * nele1 + tgt_grid_size * nele2 + nlinks * (4 + 4 + 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)
    {
      size_t maxlinks = 0x3FFFFFFF;  // 1GB
      size_t gridsize_max = (src_grid_size > tgt_grid_size) ? src_grid_size : tgt_grid_size;
      if (nlinks > maxlinks || filesize > 8 * maxlinks || gridsize_max > 0x7FFFFC00)
        {
#ifdef HAVE_NETCDF4
          if (Options::cdoVerbose) cdo_print("Store weights and links to NetCDF4!");
          writemode |= NC_NETCDF4;
          if (gridsize_max > 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
        {
#if defined(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
        }
    }

  return writemode;
}

static const char *
set_map_method_cstr(RemapMethod mapType, SubmapType submapType, int numNeighbors, bool &needGridarea)
{
  const char *map_method = "unknown";

  switch (mapType)
    {
    case RemapMethod::CONSERV_SCRIP:
      needGridarea = true;
      map_method = "Conservative remapping";
      break;
    case RemapMethod::CONSERV:
      needGridarea = true;
      map_method = (submapType == SubmapType::LAF) ? "Largest area fraction" : "Conservative remapping using clipping on sphere";
      break;
    case RemapMethod::BILINEAR: map_method = "Bilinear remapping"; break;
    case RemapMethod::BICUBIC: map_method = "Bicubic remapping"; break;
    case RemapMethod::DISTWGT:
      map_method = (numNeighbors == 1) ? "Nearest neighbor" : "Distance weighted avg of nearest neighbors";
      break;
    case RemapMethod::UNDEF: break;
    }

  return map_method;
}
#endif

void
remap_write_data_scrip(const char *interp_file, RemapMethod mapType, SubmapType submapType, int numNeighbors, int remapOrder,
                       RemapGrid &src_grid, RemapGrid &tgt_grid, RemapVars &rv)
{
  // Writes remap data to a NetCDF file using SCRIP conventions

#ifdef HAVE_LIBNETCDF

  const char *normalize_opt = "unknown";
  switch (rv.normOpt)
    {
    case NormOpt::NONE: normalize_opt = "none"; break;
    case NormOpt::FRACAREA: normalize_opt = "fracarea"; break;
    case NormOpt::DESTAREA: normalize_opt = "destarea"; break;
    }

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

  nc_type sizetype = NC_INT;
  int writemode = set_write_mode(src_grid.lneed_cell_corners, src_grid.size, src_grid.num_cell_corners, tgt_grid.lneed_cell_corners,
                                 tgt_grid.size, tgt_grid.num_cell_corners, rv.num_links, rv.num_wts, sizetype);

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

  // Map name
  const char *map_name = "CDO remapping";
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "title", strlen(map_name), map_name));

  // Normalization option
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "normalization", strlen(normalize_opt), normalize_opt));

  // Map method
  bool needGridarea = false;
  auto map_method = set_map_method_cstr(mapType, submapType, numNeighbors, needGridarea);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "map_method", strlen(map_method), map_method));

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

  // File convention
  const char *tmp_string = "SCRIP";
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "conventions", strlen(tmp_string), tmp_string));

  // Source and destination grid names
  char src_grid_name[64] = "source grid";
  gridName(gridInqType(src_grid.gridID), src_grid_name);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "source_grid", strlen(src_grid_name), src_grid_name));

  char tgt_grid_name[64] = "dest grid";
  gridName(gridInqType(tgt_grid.gridID), tgt_grid_name);
  nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "dest_grid", strlen(tgt_grid_name), tgt_grid_name));

  // History
  time_t date_and_time_in_sec = time(NULL);
  if (date_and_time_in_sec != -1)
    {
      char history[1024] = "date and time";
      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());
      nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "history", strlen(history), history));
    }

  if (Options::VersionInfo) nce(nc_put_att_text(nc_file_id, NC_GLOBAL, "CDO", (int) strlen(cdo_comment()) + 1, cdo_comment()));

  // Prepare NetCDF dimension info

  // Define grid size dimensions
  int nc_srcgrdsize_id = -1, nc_dstgrdsize_id = -1;
  nce(nc_def_dim(nc_file_id, "src_grid_size", src_grid.size, &nc_srcgrdsize_id));
  nce(nc_def_dim(nc_file_id, "dst_grid_size", tgt_grid.size, &nc_dstgrdsize_id));

  // Define grid corner dimension
  int nc_srcgrdcorn_id = -1, nc_dstgrdcorn_id = -1;
  if (src_grid.lneed_cell_corners) nce(nc_def_dim(nc_file_id, "src_grid_corners", src_grid.num_cell_corners, &nc_srcgrdcorn_id));
  if (tgt_grid.lneed_cell_corners) nce(nc_def_dim(nc_file_id, "dst_grid_corners", tgt_grid.num_cell_corners, &nc_dstgrdcorn_id));

  // Define grid rank dimension
  int nc_srcgrdrank_id = -1, nc_dstgrdrank_id = -1;
  nce(nc_def_dim(nc_file_id, "src_grid_rank", src_grid.rank, &nc_srcgrdrank_id));
  nce(nc_def_dim(nc_file_id, "dst_grid_rank", tgt_grid.rank, &nc_dstgrdrank_id));

  // Define map size dimensions
  int nc_numlinks_id = -1, nc_numwgts_id = -1;
  nce(nc_def_dim(nc_file_id, "num_links", rv.num_links, &nc_numlinks_id));
  nce(nc_def_dim(nc_file_id, "num_wgts", rv.num_wts, &nc_numwgts_id));

  // Define grid dimensions
  int nc_srcgrddims_id = -1, nc_dstgrddims_id = -1;
  nce(nc_def_var(nc_file_id, "src_grid_dims", sizetype, 1, &nc_srcgrdrank_id, &nc_srcgrddims_id));
  nce(nc_def_var(nc_file_id, "dst_grid_dims", sizetype, 1, &nc_dstgrdrank_id, &nc_dstgrddims_id));

  // Define all arrays for NetCDF descriptors

  // Define grid center latitude array
  int nc_srcgrdcntrlat_id = -1, nc_dstgrdcntrlat_id = -1;
  nce(nc_def_var(nc_file_id, "src_grid_center_lat", NC_DOUBLE, 1, &nc_srcgrdsize_id, &nc_srcgrdcntrlat_id));
  nce(nc_def_var(nc_file_id, "dst_grid_center_lat", NC_DOUBLE, 1, &nc_dstgrdsize_id, &nc_dstgrdcntrlat_id));

  // Define grid center longitude array
  int nc_srcgrdcntrlon_id = -1, nc_dstgrdcntrlon_id = -1;
  nce(nc_def_var(nc_file_id, "src_grid_center_lon", NC_DOUBLE, 1, &nc_srcgrdsize_id, &nc_srcgrdcntrlon_id));
  nce(nc_def_var(nc_file_id, "dst_grid_center_lon", NC_DOUBLE, 1, &nc_dstgrdsize_id, &nc_dstgrdcntrlon_id));

  // Define grid corner lat/lon arrays

  int nc_dims2_id[2];  // NetCDF ids for 2d array dims
  nc_dims2_id[0] = nc_srcgrdsize_id;
  nc_dims2_id[1] = nc_srcgrdcorn_id;

  int nc_srcgrdcrnrlat_id = -1, nc_srcgrdcrnrlon_id = -1;
  if (src_grid.lneed_cell_corners)
    {
      nce(nc_def_var(nc_file_id, "src_grid_corner_lat", NC_DOUBLE, 2, nc_dims2_id, &nc_srcgrdcrnrlat_id));
      nce(nc_def_var(nc_file_id, "src_grid_corner_lon", NC_DOUBLE, 2, nc_dims2_id, &nc_srcgrdcrnrlon_id));
    }

  nc_dims2_id[0] = nc_dstgrdsize_id;
  nc_dims2_id[1] = nc_dstgrdcorn_id;

  int nc_dstgrdcrnrlat_id = -1, nc_dstgrdcrnrlon_id = -1;
  if (tgt_grid.lneed_cell_corners)
    {
      nce(nc_def_var(nc_file_id, "dst_grid_corner_lat", NC_DOUBLE, 2, nc_dims2_id, &nc_dstgrdcrnrlat_id));
      nce(nc_def_var(nc_file_id, "dst_grid_corner_lon", NC_DOUBLE, 2, nc_dims2_id, &nc_dstgrdcrnrlon_id));
    }

  // Define units for all coordinate arrays

  const char *src_grid_units = "radians";
  const char *tgt_grid_units = "radians";
  nce(nc_put_att_text(nc_file_id, nc_srcgrdcntrlat_id, "units", strlen(src_grid_units), src_grid_units));
  nce(nc_put_att_text(nc_file_id, nc_dstgrdcntrlat_id, "units", strlen(tgt_grid_units), tgt_grid_units));
  nce(nc_put_att_text(nc_file_id, nc_srcgrdcntrlon_id, "units", strlen(src_grid_units), src_grid_units));
  nce(nc_put_att_text(nc_file_id, nc_dstgrdcntrlon_id, "units", strlen(tgt_grid_units), tgt_grid_units));
  if (src_grid.lneed_cell_corners)
    {
      nce(nc_put_att_text(nc_file_id, nc_srcgrdcrnrlat_id, "units", strlen(src_grid_units), src_grid_units));
      nce(nc_put_att_text(nc_file_id, nc_srcgrdcrnrlon_id, "units", strlen(src_grid_units), src_grid_units));
    }
  if (tgt_grid.lneed_cell_corners)
    {
      nce(nc_put_att_text(nc_file_id, nc_dstgrdcrnrlat_id, "units", strlen(tgt_grid_units), tgt_grid_units));
      nce(nc_put_att_text(nc_file_id, nc_dstgrdcrnrlon_id, "units", strlen(tgt_grid_units), tgt_grid_units));
    }

  // Define grid mask

  int nc_srcgrdimask_id = -1;
  nce(nc_def_var(nc_file_id, "src_grid_imask", NC_INT, 1, &nc_srcgrdsize_id, &nc_srcgrdimask_id));
  nce(nc_put_att_text(nc_file_id, nc_srcgrdimask_id, "units", 8, "unitless"));

  int nc_dstgrdimask_id = -1;
  nce(nc_def_var(nc_file_id, "dst_grid_imask", NC_INT, 1, &nc_dstgrdsize_id, &nc_dstgrdimask_id));
  nce(nc_put_att_text(nc_file_id, nc_dstgrdimask_id, "units", 8, "unitless"));

  // Define grid area arrays

  int nc_srcgrdarea_id = -1, nc_dstgrdarea_id = -1;
  if (needGridarea)
    {
      nce(nc_def_var(nc_file_id, "src_grid_area", NC_DOUBLE, 1, &nc_srcgrdsize_id, &nc_srcgrdarea_id));
      nce(nc_put_att_text(nc_file_id, nc_srcgrdarea_id, "units", 14, "square radians"));

      nce(nc_def_var(nc_file_id, "dst_grid_area", NC_DOUBLE, 1, &nc_dstgrdsize_id, &nc_dstgrdarea_id));
      nce(nc_put_att_text(nc_file_id, nc_dstgrdarea_id, "units", 14, "square radians"));
    }

  // Define grid fraction arrays

  int nc_srcgrdfrac_id = -1;
  nce(nc_def_var(nc_file_id, "src_grid_frac", NC_DOUBLE, 1, &nc_srcgrdsize_id, &nc_srcgrdfrac_id));
  nce(nc_put_att_text(nc_file_id, nc_srcgrdfrac_id, "units", 8, "unitless"));

  int nc_dstgrdfrac_id = -1;
  nce(nc_def_var(nc_file_id, "dst_grid_frac", NC_DOUBLE, 1, &nc_dstgrdsize_id, &nc_dstgrdfrac_id));
  nce(nc_put_att_text(nc_file_id, nc_dstgrdfrac_id, "units", 8, "unitless"));

  // Define mapping arrays

  int nc_srcadd_id = -1, nc_dstadd_id = -1;
  nce(nc_def_var(nc_file_id, "src_address", sizetype, 1, &nc_numlinks_id, &nc_srcadd_id));
  nce(nc_def_var(nc_file_id, "dst_address", sizetype, 1, &nc_numlinks_id, &nc_dstadd_id));

  nc_dims2_id[0] = nc_numlinks_id;
  nc_dims2_id[1] = nc_numwgts_id;

  int nc_rmpmatrix_id = -1;
  nce(nc_def_var(nc_file_id, "remap_matrix", NC_DOUBLE, 2, nc_dims2_id, &nc_rmpmatrix_id));

  // End definition stage

  nce(nc_enddef(nc_file_id));

  // Write mapping data

  int dims[2];
  dims[0] = (int) src_grid.dims[0];
  dims[1] = (int) src_grid.dims[1];
  nce(nc_put_var_int(nc_file_id, nc_srcgrddims_id, dims));
  dims[0] = (int) tgt_grid.dims[0];
  dims[1] = (int) tgt_grid.dims[1];
  nce(nc_put_var_int(nc_file_id, nc_dstgrddims_id, dims));

  nce(nc_put_var_short(nc_file_id, nc_srcgrdimask_id, &src_grid.mask[0]));
  nce(nc_put_var_short(nc_file_id, nc_dstgrdimask_id, &tgt_grid.mask[0]));

  if (!src_grid.cell_center_lat.empty()) nce(nc_put_var_double(nc_file_id, nc_srcgrdcntrlat_id, src_grid.cell_center_lat.data()));
  if (!src_grid.cell_center_lon.empty()) nce(nc_put_var_double(nc_file_id, nc_srcgrdcntrlon_id, src_grid.cell_center_lon.data()));

  if (src_grid.lneed_cell_corners)
    {
      nce(nc_put_var_double(nc_file_id, nc_srcgrdcrnrlat_id, src_grid.cell_corner_lat.data()));
      nce(nc_put_var_double(nc_file_id, nc_srcgrdcrnrlon_id, src_grid.cell_corner_lon.data()));
    }

  if (!tgt_grid.cell_center_lat.empty()) nce(nc_put_var_double(nc_file_id, nc_dstgrdcntrlat_id, tgt_grid.cell_center_lat.data()));
  if (!tgt_grid.cell_center_lon.empty()) nce(nc_put_var_double(nc_file_id, nc_dstgrdcntrlon_id, tgt_grid.cell_center_lon.data()));

  if (tgt_grid.lneed_cell_corners)
    {
      nce(nc_put_var_double(nc_file_id, nc_dstgrdcrnrlat_id, tgt_grid.cell_corner_lat.data()));
      nce(nc_put_var_double(nc_file_id, nc_dstgrdcrnrlon_id, tgt_grid.cell_corner_lon.data()));
    }

  if (needGridarea) nce(nc_put_var_double(nc_file_id, nc_srcgrdarea_id, &src_grid.cell_area[0]));

  nce(nc_put_var_double(nc_file_id, nc_srcgrdfrac_id, &src_grid.cell_frac[0]));

  /*
  if (luse_cell_area)
    nce(nc_put_var_double(nc_file_id, nc_dstgrdarea_id, tgt_grid.cell_area_in));
  else
  */
  if (needGridarea) nce(nc_put_var_double(nc_file_id, nc_dstgrdarea_id, &tgt_grid.cell_area[0]));

  nce(nc_put_var_double(nc_file_id, nc_dstgrdfrac_id, &tgt_grid.cell_frac[0]));

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

  writeLinks(nc_file_id, nc_srcadd_id, sizetype, rv.num_links, &rv.src_cell_add[0]);
  writeLinks(nc_file_id, nc_dstadd_id, sizetype, rv.num_links, &rv.tgt_cell_add[0]);
  nce(nc_put_var_double(nc_file_id, nc_rmpmatrix_id, &rv.wts[0]));

  nce(nc_close(nc_file_id));

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

}  // remap_write_data_scrip

/*****************************************************************************/

#ifdef HAVE_LIBNETCDF
static std::string
get_text_attribute(int nc_file_id, int att_id, const char *att_name)
{
  char cstr[1024];
  nce(nc_get_att_text(nc_file_id, att_id, att_name, cstr));
  size_t attlen;
  nce(nc_inq_attlen(nc_file_id, att_id, att_name, &attlen));
  cstr[attlen] = 0;

  return std::string(cstr);
}

RemapMethod
getMapType(int nc_file_id, SubmapType &submapType, int &numNeighbors, int &remapOrder)
{
  // Map method
  size_t attlen;
  char map_method[64];
  nce(nc_get_att_text(nc_file_id, NC_GLOBAL, "map_method", map_method));
  nce(nc_inq_attlen(nc_file_id, NC_GLOBAL, "map_method", &attlen));
  map_method[attlen] = 0;

  submapType = SubmapType::NONE;
  remapOrder = 1;

  RemapMethod mapType = RemapMethod::UNDEF;
  if (cdo_cmpstrLenRhs(map_method, "Conservative"))
    {
      if (cdo_cmpstrLenRhs(map_method, "Conservative remapping using clipping on sphere"))
        mapType = RemapMethod::CONSERV;
      else
        mapType = RemapMethod::CONSERV_SCRIP;

      int iatt;
      const int status = nc_get_att_int(nc_file_id, NC_GLOBAL, "remap_order", &iatt);
      if (status == NC_NOERR) remapOrder = iatt;
    }
  else if (cdo_cmpstrLenRhs(map_method, "Bilinear"))
    mapType = RemapMethod::BILINEAR;
  else if (cdo_cmpstrLenRhs(map_method, "Bicubic"))
    mapType = RemapMethod::BICUBIC;
  else if (cdo_cmpstrLenRhs(map_method, "Distance"))
    {
      mapType = RemapMethod::DISTWGT;
      numNeighbors = 4;
    }
  else if (cdo_cmpstrLenRhs(map_method, "Nearest"))
    {
      mapType = RemapMethod::DISTWGT;
      numNeighbors = 1;
    }
  else if (cdo_cmpstrLenRhs(map_method, "Largest"))
    {
      mapType = RemapMethod::CONSERV;
      submapType = SubmapType::LAF;
    }
  else
    {
      cdo_print("mapType = %s", map_method);
      cdo_abort("Invalid Map Type");
    }

  if (Options::cdoVerbose) cdo_print("mapType = %s", map_method);

  return mapType;
}
#endif

void
remap_read_data_scrip(const char *interp_file, int gridID1, int gridID2, RemapMethod &mapType, SubmapType &submapType,
                      int &numNeighbors, int &remapOrder, RemapGrid &src_grid, RemapGrid &tgt_grid, RemapVars &rv)
{
  // The routine reads a NetCDF file to extract remapping info in SCRIP format

#ifdef HAVE_LIBNETCDF

  int status;
  size_t dimlen;

  int gridID1_gme_c = -1;

  // Open file and read some global information

  const auto nc_file_id = cdo_cdf_openread(interp_file);

  // Map name
  const auto map_name = get_text_attribute(nc_file_id, NC_GLOBAL, "title");

  if (Options::cdoVerbose)
    {
      cdo_print("Reading remap weights: %s", map_name.c_str());
      cdo_print("From file: %s", interp_file);
    }

  // Map Tyoe
  mapType = getMapType(nc_file_id, submapType, numNeighbors, remapOrder);

  const auto needGridarea = (mapType == RemapMethod::CONSERV_SCRIP);

  remap_vars_init(mapType, remapOrder, rv);

  rv.mapType = mapType;
  rv.links_per_value = -1;
  rv.sort_add = false;

  // Normalization option
  const auto normalize_opt = get_text_attribute(nc_file_id, NC_GLOBAL, "normalization");

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

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

  // File convention
  const auto convention = get_text_attribute(nc_file_id, NC_GLOBAL, "conventions");

  if (convention != "SCRIP")
    {
      cdo_print("convention = %s", convention.c_str());
      if (convention == "NCAR-CSM")
        cdo_abort("Unsupported file convention!");
      else
        cdo_abort("Unknown file convention!");
    }

  // Read some additional global attributes

  // Source and destination grid names

  const auto src_grid_name = get_text_attribute(nc_file_id, NC_GLOBAL, "source_grid");
  const auto tgt_grid_name = get_text_attribute(nc_file_id, NC_GLOBAL, "dest_grid");

  if (Options::cdoVerbose) cdo_print("Remapping between: %s and %s", src_grid_name.c_str(), tgt_grid_name.c_str());

  // Initialize remapgrid structure
  remap_grid_init(src_grid);
  remap_grid_init(tgt_grid);

  // Read dimension information
  int nc_srcgrdsize_id;
  nce(nc_inq_dimid(nc_file_id, "src_grid_size", &nc_srcgrdsize_id));
  nce(nc_inq_dimlen(nc_file_id, nc_srcgrdsize_id, &dimlen));
  src_grid.size = dimlen;
  // if (src_grid.size != gridInqSize(gridID1)) cdo_abort("Source grids have different size!");

  int nc_dstgrdsize_id;
  nce(nc_inq_dimid(nc_file_id, "dst_grid_size", &nc_dstgrdsize_id));
  nce(nc_inq_dimlen(nc_file_id, nc_dstgrdsize_id, &dimlen));
  tgt_grid.size = dimlen;
  // if (tgt_grid.size != gridInqSize(gridID2)) cdo_abort("Target grids have different size!");

  int nc_srcgrdcorn_id;
  status = nc_inq_dimid(nc_file_id, "src_grid_corners", &nc_srcgrdcorn_id);
  if (status == NC_NOERR)
    {
      nce(nc_inq_dimlen(nc_file_id, nc_srcgrdcorn_id, &dimlen));
      src_grid.num_cell_corners = dimlen;
      src_grid.luse_cell_corners = true;
      src_grid.lneed_cell_corners = true;
    }

  int nc_dstgrdcorn_id;
  status = nc_inq_dimid(nc_file_id, "dst_grid_corners", &nc_dstgrdcorn_id);
  if (status == NC_NOERR)
    {
      nce(nc_inq_dimlen(nc_file_id, nc_dstgrdcorn_id, &dimlen));
      tgt_grid.num_cell_corners = dimlen;
      tgt_grid.luse_cell_corners = true;
      tgt_grid.lneed_cell_corners = true;
    }

  int nc_srcgrdrank_id;
  nce(nc_inq_dimid(nc_file_id, "src_grid_rank", &nc_srcgrdrank_id));
  nce(nc_inq_dimlen(nc_file_id, nc_srcgrdrank_id, &dimlen));
  src_grid.rank = dimlen;

  int nc_dstgrdrank_id;
  nce(nc_inq_dimid(nc_file_id, "dst_grid_rank", &nc_dstgrdrank_id));
  nce(nc_inq_dimlen(nc_file_id, nc_dstgrdrank_id, &dimlen));
  tgt_grid.rank = dimlen;

  int nc_numlinks_id;
  nce(nc_inq_dimid(nc_file_id, "num_links", &nc_numlinks_id));
  nce(nc_inq_dimlen(nc_file_id, nc_numlinks_id, &dimlen));
  rv.num_links = dimlen;
  // if (rv.num_links == 0) cdo_abort("Number of remap links is 0, no remap weights found!");

  int nc_numwgts_id;
  nce(nc_inq_dimid(nc_file_id, "num_wgts", &nc_numwgts_id));
  nce(nc_inq_dimlen(nc_file_id, nc_numwgts_id, &dimlen));
  rv.num_wts = dimlen;

  src_grid.gridID = gridID1;
  tgt_grid.gridID = gridID2;

  if (gridInqType(gridID1) == GRID_GME)
    {
      src_grid.nvgp = gridInqSize(gridID1);
      gridID1_gme_c = gridToUnstructured(gridID1, 1);
    }

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

  if (gridInqType(gridID1) == GRID_GME) gridInqMaskGME(gridID1_gme_c, &src_grid.vgpm[0]);

  // Allocate address and weight arrays for mapping 1
  if (rv.num_links > 0)
    {
      rv.max_links = rv.num_links;
      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);
    }

  // Get variable ids

  int nc_srcgrddims_id, nc_srcgrdimask_id;
  int nc_srcgrdcntrlat_id, nc_srcgrdcntrlon_id;
  nce(nc_inq_varid(nc_file_id, "src_grid_dims", &nc_srcgrddims_id));
  nce(nc_inq_varid(nc_file_id, "src_grid_imask", &nc_srcgrdimask_id));
  nce(nc_inq_varid(nc_file_id, "src_grid_center_lat", &nc_srcgrdcntrlat_id));
  nce(nc_inq_varid(nc_file_id, "src_grid_center_lon", &nc_srcgrdcntrlon_id));

  int nc_srcgrdcrnrlat_id = -1, nc_srcgrdcrnrlon_id = -1;
  if (src_grid.num_cell_corners)
    {
      nce(nc_inq_varid(nc_file_id, "src_grid_corner_lat", &nc_srcgrdcrnrlat_id));
      nce(nc_inq_varid(nc_file_id, "src_grid_corner_lon", &nc_srcgrdcrnrlon_id));
    }

  int nc_srcgrdarea_id = -1, nc_srcgrdfrac_id;
  if (needGridarea) nce(nc_inq_varid(nc_file_id, "src_grid_area", &nc_srcgrdarea_id));
  nce(nc_inq_varid(nc_file_id, "src_grid_frac", &nc_srcgrdfrac_id));

  int nc_dstgrddims_id, nc_dstgrdimask_id;
  int nc_dstgrdcntrlat_id, nc_dstgrdcntrlon_id;
  nce(nc_inq_varid(nc_file_id, "dst_grid_dims", &nc_dstgrddims_id));
  nce(nc_inq_varid(nc_file_id, "dst_grid_imask", &nc_dstgrdimask_id));
  nce(nc_inq_varid(nc_file_id, "dst_grid_center_lat", &nc_dstgrdcntrlat_id));
  nce(nc_inq_varid(nc_file_id, "dst_grid_center_lon", &nc_dstgrdcntrlon_id));

  int nc_dstgrdcrnrlat_id = -1, nc_dstgrdcrnrlon_id = -1;
  if (tgt_grid.num_cell_corners)
    {
      nce(nc_inq_varid(nc_file_id, "dst_grid_corner_lat", &nc_dstgrdcrnrlat_id));
      nce(nc_inq_varid(nc_file_id, "dst_grid_corner_lon", &nc_dstgrdcrnrlon_id));
    }

  int nc_dstgrdarea_id = -1, nc_dstgrdfrac_id;
  if (needGridarea) nce(nc_inq_varid(nc_file_id, "dst_grid_area", &nc_dstgrdarea_id));
  nce(nc_inq_varid(nc_file_id, "dst_grid_frac", &nc_dstgrdfrac_id));

  int nc_srcadd_id, nc_dstadd_id;
  int nc_rmpmatrix_id;
  nce(nc_inq_varid(nc_file_id, "src_address", &nc_srcadd_id));
  nce(nc_inq_varid(nc_file_id, "dst_address", &nc_dstadd_id));
  nce(nc_inq_varid(nc_file_id, "remap_matrix", &nc_rmpmatrix_id));

  // Read all variables

  int dims[2];
  nce(nc_get_var_int(nc_file_id, nc_srcgrddims_id, dims));
  src_grid.dims[0] = dims[0];
  src_grid.dims[1] = dims[1];

  nce(nc_get_var_short(nc_file_id, nc_srcgrdimask_id, &src_grid.mask[0]));

  nce(nc_get_var_double(nc_file_id, nc_srcgrdcntrlat_id, src_grid.cell_center_lat.data()));
  nce(nc_get_var_double(nc_file_id, nc_srcgrdcntrlon_id, src_grid.cell_center_lon.data()));

  auto src_grid_units = get_text_attribute(nc_file_id, nc_srcgrdcntrlat_id, "units");
  grid_to_radian(src_grid_units.c_str(), src_grid.size, src_grid.cell_center_lon.data(), "source grid center lon");
  grid_to_radian(src_grid_units.c_str(), src_grid.size, src_grid.cell_center_lat.data(), "source grid center lat");

  if (src_grid.num_cell_corners)
    {
      nce(nc_get_var_double(nc_file_id, nc_srcgrdcrnrlat_id, src_grid.cell_corner_lat.data()));
      nce(nc_get_var_double(nc_file_id, nc_srcgrdcrnrlon_id, src_grid.cell_corner_lon.data()));

      src_grid_units = get_text_attribute(nc_file_id, nc_srcgrdcrnrlat_id, "units");
      const auto len = src_grid.num_cell_corners * src_grid.size;
      grid_to_radian(src_grid_units.c_str(), len, src_grid.cell_corner_lon.data(), "source grid corner lon");
      grid_to_radian(src_grid_units.c_str(), len, src_grid.cell_corner_lat.data(), "source grid corner lat");
    }

  if (needGridarea) nce(nc_get_var_double(nc_file_id, nc_srcgrdarea_id, &src_grid.cell_area[0]));

  nce(nc_get_var_double(nc_file_id, nc_srcgrdfrac_id, &src_grid.cell_frac[0]));

  nce(nc_get_var_int(nc_file_id, nc_dstgrddims_id, dims));
  tgt_grid.dims[0] = dims[0];
  tgt_grid.dims[1] = dims[1];

  nce(nc_get_var_short(nc_file_id, nc_dstgrdimask_id, &tgt_grid.mask[0]));

  nce(nc_get_var_double(nc_file_id, nc_dstgrdcntrlat_id, tgt_grid.cell_center_lat.data()));
  nce(nc_get_var_double(nc_file_id, nc_dstgrdcntrlon_id, tgt_grid.cell_center_lon.data()));

  auto tgt_grid_units = get_text_attribute(nc_file_id, nc_dstgrdcntrlat_id, "units");
  grid_to_radian(tgt_grid_units.c_str(), tgt_grid.size, tgt_grid.cell_center_lon.data(), "target grid center lon");
  grid_to_radian(tgt_grid_units.c_str(), tgt_grid.size, tgt_grid.cell_center_lat.data(), "target grid center lat");

  if (tgt_grid.num_cell_corners)
    {
      nce(nc_get_var_double(nc_file_id, nc_dstgrdcrnrlat_id, tgt_grid.cell_corner_lat.data()));
      nce(nc_get_var_double(nc_file_id, nc_dstgrdcrnrlon_id, tgt_grid.cell_corner_lon.data()));

      tgt_grid_units = get_text_attribute(nc_file_id, nc_dstgrdcrnrlat_id, "units");
      const auto len = tgt_grid.num_cell_corners * tgt_grid.size;
      grid_to_radian(tgt_grid_units.c_str(), len, tgt_grid.cell_corner_lon.data(), "target grid corner lon");
      grid_to_radian(tgt_grid_units.c_str(), len, tgt_grid.cell_corner_lat.data(), "target grid corner lat");
    }

  if (needGridarea) nce(nc_get_var_double(nc_file_id, nc_dstgrdarea_id, &tgt_grid.cell_area[0]));

  nce(nc_get_var_double(nc_file_id, nc_dstgrdfrac_id, &tgt_grid.cell_frac[0]));

  if (rv.num_links > 0)
    {
      readLinks(nc_file_id, nc_srcadd_id, rv.num_links, &rv.src_cell_add[0]);
      readLinks(nc_file_id, nc_dstadd_id, rv.num_links, &rv.tgt_cell_add[0]);

      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]--;

      nce(nc_get_var_double(nc_file_id, nc_rmpmatrix_id, &rv.wts[0]));
    }

  // Close input file
  cdo_cdf_close(nc_file_id);

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

}  // remap_read_data_scrip
