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

  This file is a extension of CDO.

  Author: M. Koutek, 2012, KNMI (NL)

*/

/*
   This module contains the following operators:

      WindTrans       uvDestag   : destagger U and/or V wind (in place)
                      rotuvNorth : rotate grid-relative wind(u,v) to North_pole-relative
*/

#include <cdi.h>

#include <mpim_grid.h>
#include "grid_define.h"
#include "dmemory.h"
#include "process_int.h"
#include "cdo_zaxis.h"
#include "param_conversion.h"

#define MAXARG 10

int UVDESTAG;
int ROTUVNORTH;
int ROTUVN;  // Fixed version of rotuvb operator !
int PROJUVLATLON;

// NOTE: Variable with codes (3[3,4],105,10) will get from CDO typically a name:
// "10u", resp. "10v" Mostly u & v at level type 105 is not staggered, but test it to be sure..

#define CheckVarIsU(varID, varname, code)                                      \
  {                                                                            \
    VarIsU = 0;                                                                \
    if (lvar)                                                                  \
      {                                                                        \
        if (strcmp((char *) (varname), (char *) (chvars)[0]) == 0) VarIsU = 1; \
      }                                                                        \
    else if (code == chcodes[0])                                               \
      VarIsU = 1;                                                              \
  }
#define CheckVarIsV(varID, varname, code)                                      \
  {                                                                            \
    VarIsV = 0;                                                                \
    if (lvar)                                                                  \
      {                                                                        \
        if (strcmp((char *) (varname), (char *) (chvars)[1]) == 0) VarIsV = 1; \
      }                                                                        \
    else if (code == chcodes[1])                                               \
      VarIsV = 1;                                                              \
  }
#define CheckUVisStaggered(varID1, varID2, zaxisID1, zaxisID2) \
  {                                                            \
    VarsUVareStaggered = 1;                                    \
    auto gridID1tmp = vlistInqVarGrid(vlistID1, varID1);        \
    auto gridID2tmp = vlistInqVarGrid(vlistID1, varID2);        \
    if (zaxisID1 != zaxisID2)                                  \
      VarsUVareStaggered = 0;                                  \
    else if (gridID1tmp == gridID2tmp)                         \
      VarsUVareStaggered = 0;                                  \
  }

static void
destaggerUorV(double *fu, double *fuOut, int klev, long nlat, long nlon, long UorV, long offset)
{
  long lat0, lon0;
  double u0, u1;
  double u_dstg;
  long next;
  /* This does de-staggering (-0.5; -0.5) */

  long idx = offset;
  if (UorV == 0)
    {
      next = -1;  // U-wind
      lat0 = 0;
      lon0 = 1;
    }
  else
    {
      next = -nlon;  // V-wind: length of the row (2d array)
      lat0 = 1;
      lon0 = 0;
    }

  if (cdoDebugExt >= 20)
    cdo_print("destaggerUorV(): (nlon=%d, nlat=%d);               [lat0=%ld, lon0=%ld, next=%ld];  (default order destaggering)",
              nlon, nlat, lat0, lon0, next);

  for (int lev = 0; lev < klev; lev++)
    {
      if (lat0)  // the first row we let as it is
        for (int lon = 0; lon < nlon; lon++)
          {
            u0 = fu[idx];
            fuOut[idx] = u0;
            idx++;
          }
      for (int lat = lat0; lat < nlat; lat++)
        {
          if (lon0)  // the first column we let as it is
            {
              u0 = fu[idx];
              fuOut[idx] = u0;
              idx++;
            }
          for (int lon = lon0; lon < nlon; lon++)
            {
              u0 = fu[idx];
              u1 = fu[idx + next];
              u_dstg = 0.5 * (u0 + u1);
              fuOut[idx] = u_dstg;
              idx++;
            }
        }
    }
}

static void
destaggerUorV_positiveOrder(double *fu, double *fuOut, int klev, long nlat, long nlon, int UorV, long offset)
{
  long latE, lonE;
  double u0, u1;
  double u_dstg;
  long next;
  /* This does de-staggering (+0.5; +0.5) */

  long idx = offset;
  if (UorV == 0)  // U-wind: length of the row (2d array)
    {
      next = nlon;
      latE = nlat - 1;
      lonE = nlon;
    }
  else  // V-wind:
    {
      next = 1;
      latE = nlat;
      lonE = nlon - 1;
    }

  if (cdoDebugExt >= 20)
    cdo_print("destaggerUorV(): (nlon=%ld, nlat=%ld);               [latE=%d, lonE=%d, next=%d];  (positive order destaggering)",
              nlon, nlat, latE, lonE, next);

  for (int lev = 0; lev < klev; lev++)
    {
      for (long lat = 0; lat < latE; lat++)
        {
          for (long lon = 0; lon < lonE; lon++)
            {
              u0 = fu[idx];
              u1 = fu[idx + next];
              u_dstg = 0.5 * (u0 + u1);
              fuOut[idx] = u_dstg;
              idx++;
            }
          if (lonE < nlon)  // the last column we let as it is
            {
              u0 = fu[idx];
              fuOut[idx] = u0;
              idx++;
            }
        }

      if (latE < nlat)  // the last row we let as it is
        for (long lon = 0; lon < nlon; lon++)
          {
            u0 = fu[idx];
            fuOut[idx] = u0;
            idx++;
          }
    }
}

static void *
DestaggerUV()
{
  int varID, levelID;
  int varID1 = CDI_UNDEFID, varID2 = CDI_UNDEFID;
  int zaxisID1 = CDI_UNDEFID, zaxisID2 = CDI_UNDEFID;
  int varID1stg = CDI_UNDEFID, varID2stg = CDI_UNDEFID;
  size_t gridsize;
  int chcodes[MAXARG];
  const char *chvars[MAXARG];
  double destagGridOffsets[MAXARG];
  char varname[CDI_MAX_NAME];
  int gridID1 = CDI_UNDEFID, gridID2 = CDI_UNDEFID;
  int gridID0 = CDI_UNDEFID;
  int gridID;
  bool lcopy = false;
  int UorV;
  size_t nlon = 0, nlat = 0;
  Varray<double> ivar, ovar;
  double dxU = 0, dyU = 0, dxV = 0, dyV = 0;

  chcodes[0] = 0;
  chcodes[1] = 0;
  chvars[0] = 0;
  chvars[1] = 0;

  // Note: Already initialized by the caller! Don't call again:
  // cdo_initialize(process);

  operator_input_arg("Pair of u and v in the staggered system:\n\
    Usage: uvDestag,u,v -or- uvDestag,33,34 -or- uvDestag,u,v,-0.5,-0.5 -or- uvDestag,33,34,-0.5,-0.5\n \
    Destaggered grid offsets <,-/+0.5,-/+0.5> are optional.\n           \
    If file contains grid with temperature (name='t' or code=11) then grid_temp will be used for destaggered wind.");

  Debug(cdoDebugExt, "UVDESTAG (destaggering) requested)..");

  int nch = cdo_operator_argc();
  if (nch < 2) cdo_abort("Number of input arguments < 2; At least 2 arguments needed: uvDestag,33,34<,-0.5,-0.5> optional");
  if (nch >= MAXARG) cdo_abort("Number of input arguments >= %d", MAXARG);

  bool lvar = false;
  if (isdigit(cdo_operator_argv(0)[0]))
    {
      lvar = false;  // We have a list of codes
      for (int i = 0; i < 2; ++i) chcodes[i] = parameter_to_int(cdo_operator_argv(i));
    }
  else
    {
      lvar = true;  // We have a list of variables
      for (int i = 0; i < 2; ++i) chvars[i] = cdo_operator_argv(i).c_str();
    }

  destagGridOffsets[0] = -0.5;
  destagGridOffsets[1] = -0.5;

  if (nch > 2)
    {
      for (int i = 2; i < (2 + 2); ++i) destagGridOffsets[i - 2] = parameter_to_double(cdo_operator_argv(i));
    }

  Debug(cdoDebugExt, "destagGridOffsets = (%01.1f,%01.1f)", destagGridOffsets[0], destagGridOffsets[1]);

  const auto streamID1 = cdo_open_read(0);

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

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

  // Find the first occurance of staggered U and V variables (for example codes 33,34). We assume that one (grib-)file
  // contains only 1 sort of horizontal grids and at most 2 staggered grids.
  /*
    [Hirlam/hip_work] >cdo sinfo LAMH_D11_201302150000_00000_GB_only_UV
    File format: GRIB
    -1 : Institut Source   Param       Ttype   Dtype  Gridsize Num  Levels Num
     1 : KNMI     unknown  11.1        instant  P11    399300   1      19   1
     2 : KNMI     unknown  33.1        instant  P13    399300   1       6   2  ** non-staggered U  (leveltype=105)
     3 : KNMI     unknown  34.1        instant  P12    399300   1       6   2  ** non-staggered V  (leveltype=105)
     4 : KNMI     unknown  33.1        instant  P14    399300   2      60   3  ** STAGGERED U  (leveltype=109)
     5 : KNMI     unknown  34.1        instant  P14    399300   3      60   3  ** STAGGERED V  (leveltype=109)
     6 : KNMI     unknown  11.1        instant  P11    399300   1      60   3
     7 : KNMI     unknown  11.1        instant  P10    399300   1       1   4
     8 : KNMI     unknown  11.1        instant  P11    399300   1      11   5
     9 : KNMI     unknown  33.1        instant  P14    399300   2      11   5  ** STAGGERED U  (leveltype=100)
    10 : KNMI     unknown  34.1        instant  P14    399300   3      11   5  ** STAGGERED V  (leveltype=100)
   Horizontal grids :
     1 : lonlat   > size      : dim = 399300  nlon = 726  nlat = 550
                    rlon      : first = -30.2  last = 42.3  inc = 0.1  degrees
                    rlat      : first = -30.8  last = 24.1  inc = 0.1  degrees
                    northpole : lon = -195  lat = 30
     2 : lonlat       > size  : dim = 399300  nlon = 726  nlat = 550
                    rlon      : first = -30.15  last = 42.35  inc = 0.1  degrees
                    rlat      : first = -30.8  last = 24.1  inc = 0.1  degrees
                    northpole : lon = -195  lat = 30
     3 : lonlat       > size  : dim = 399300  nlon = 726  nlat = 550
                    rlon      : first = -30.2  last = 42.3  inc = 0.1  degrees
                    rlat      : first = -30.75  last = 24.15  inc = 0.1  degrees
                    northpole : lon = -195  lat = 30
   Vertical grids :
     1 : height                 m : 0 2 801 802 803 804 805 901 902 903 904 905 951 952 953 954 955 998 999
     2 : height                 m : 10 801 802 803 804 805
     3 : hybrid             level : 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
                                   19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
                                   44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
     4 : meansea            level : 0
     5 : pressure              Pa : 5000 10000 20000 25000 30000 40000 50000 70000 85000 92500 100000
  */
  int VarIsU, VarIsV;
  int VarsUVareStaggered;

  // Search for staggered u and v wind:
  int nvars = vlistNvars(vlistID1);
  for (varID = 0; varID < nvars; ++varID)
    {
      auto param = vlistInqVarParam(vlistID1, varID);
      int pnum, pcat, pdis;
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      auto code = pnum;
      auto zaxisID = vlistInqVarZaxis(vlistID1, varID);
      auto ltype = zaxis_to_ltype(zaxisID);
      auto nlevs = zaxisInqSize(zaxisID);
      vlistInqVarName(vlistID1, varID, varname);
      auto gridIDx = vlistInqVarGrid(vlistID1, varID);
      if (cdoDebugExt >= 20)
        cdo_print("Var.id [%4d] with grib code:%3d and has name: %6s; level "
                  "type: %3d; number of levels: %3d; gridID: %d; zaxisID: %d",
                  varID, code, varname, ltype, nlevs, gridIDx, zaxisID);

      CheckVarIsU(varID, varname, code);
      CheckVarIsV(varID, varname, code);
      if (VarIsU)
        {
          varID1 = varID;
          zaxisID1 = zaxisID;
        }
      else if (VarIsV)
        {
          varID2 = varID;
          zaxisID2 = zaxisID;
        }

      if ((varID1 != CDI_UNDEFID) && (varID2 != CDI_UNDEFID))
        {
          CheckUVisStaggered(varID1, varID2, zaxisID1, zaxisID2);
          if (VarsUVareStaggered)
            {
              gridID1 = vlistInqVarGrid(vlistID1, varID1);
              gridID2 = vlistInqVarGrid(vlistID1, varID2);
              if (cdoDebugExt)
                cdo_print("Found STAGGERED U & V: varID1=%d (gridID1=%d), varID2=%d (gridID2=%d)", varID1, gridID2, varID2,
                          gridID1);
              varID1stg = varID1;
              varID2stg = varID2;
              if (gridID0 != CDI_UNDEFID)
                {
                  vlistChangeVarGrid(vlistID2, varID1stg, gridID0);  // set the variable onto the non-staggered grid
                  vlistChangeVarGrid(vlistID2, varID2stg, gridID0);  // set the variable onto the non-staggered grid
                }
              // Allow a next level-type UV-pair to be found;
              // NOTE: There may be separate CDO staggerd variables for (33/34; 109; *) and (33/34; 100; *)
              varID1 = varID2 = CDI_UNDEFID;
            }
        }
      // search for a reference (non-staggered) grid
      // We take temperature for example as the new (horizontal) grid for
      // de-staggered uv If there will be no temperature field we will define grid the grid
      if (lvar)  // We have a list of variables
        {
          if (strcmp(varname, "t") == 0) gridID0 = vlistInqVarGrid(vlistID1, varID);
        }
      else
        {
          if (code == 11) gridID0 = vlistInqVarGrid(vlistID1, varID);
        }
    }  // end of for ( varID = 0; varID < nvars; ..

  if (gridID0 >= 0) Debug(cdoDebugExt, "Found DESTAGGERED grid for U, V: gridID0=%d", gridID0);

  if (varID1stg == -1 && varID2stg == -1)
    {
      cdo_print("NOTE: We did not find any staggered U,V wind components. Performing file-copy.");
      lcopy = true;
      gridID0 = gridID1;
    }

  int ngrids = vlistNgrids(vlistID1);

  double xincU = 0.0, yincU = 0.0, xincV = 0.0, yincV = 0.0;

  if (!lcopy)
    {
      if (gridInqXsize(gridID1) != gridInqXsize(gridID2)) cdo_abort("Xsize(gridID1) != Xsize(gridID2)");
      if (gridInqYsize(gridID1) != gridInqYsize(gridID2)) cdo_abort("Ysize(gridID1) != Ysize(gridID2)");

      nlon = gridInqXsize(gridID1);
      nlat = gridInqYsize(gridID1);

      xincU = gridInqXinc(gridID1);
      yincU = gridInqXinc(gridID1);
      xincV = gridInqXinc(gridID2);
      yincV = gridInqXinc(gridID2);

      const auto gridtype1 = gridInqType(gridID1);
      const auto gridtype2 = gridInqType(gridID2);

      if (!(gridtype1 == GRID_PROJECTION && gridInqProjType(gridID1) == CDI_PROJ_RLL && gridtype2 == GRID_PROJECTION
            && gridInqProjType(gridID2) == CDI_PROJ_RLL))
        {
          cdo_print("U - wind: Grid nr. %d is gridtype: %d (%s)", gridID1, gridtype1, gridNamePtr(gridtype1));
          cdo_print("V - wind: Grid nr. %d is gridtype: %d (%s)", gridID2, gridtype2, gridNamePtr(gridtype2));
          cdo_abort("Destaggering supports only grid type = 'lonlat' (GRID_LONLAT).");
        }

      // define output grid
      if (gridID0 == -1)
        {
          if (cdoDebugExt)
            cdo_print("Calling define_destagered_grid( destagGridOffsets = (%01.1f,%01.1f) )", destagGridOffsets[0],
                      destagGridOffsets[1]);
          gridID0 = cdo_define_destagered_grid(gridID1, gridID2, destagGridOffsets);
        }

      if (gridID0 == -1) cdo_abort("Cannot define DESTAGGERED grid for U, V.");

      if (cdoDebugExt >= 10) cdo_print_griddes(gridID0, 1);

      double xfirst_R = gridInqXval(gridID0, 0);  // reference grid for non-staggered fields
                                                  // (default: search for temperature; otherwise: create a new grid)
      double yfirst_R = gridInqYval(gridID0, 0);
      double xfirst_U = gridInqXval(gridID1, 0);  // grid of u-wind
      double yfirst_U = gridInqYval(gridID1, 0);
      double xfirst_V = gridInqXval(gridID2, 0);  // grid of v-wind
      double yfirst_V = gridInqYval(gridID2, 0);

      dxU = -xfirst_U + xfirst_R;
      dyU = -yfirst_U + yfirst_R;
      dxV = -xfirst_V + xfirst_R;
      dyV = -yfirst_V + yfirst_R;

      if (cdoDebugExt)
        {
          cdo_print("Grid info: (xfirst_R = %3.2f; yfirst_R = %3.2f); (xfirst_U "
                    "= %3.2f; yfirst_U = %3.2f); (xfirst_V = %3.2f; yfirst_V = %3.2f);",
                    xfirst_R, yfirst_R, xfirst_U, yfirst_U, xfirst_V, yfirst_V);
          cdo_print("Grid info: (dxU; dyU) = (%3.2f; %3.2f); (dxV; dyV) = (%3.2f; %3.2f) ", dxU, dyU, dxV, dyV);
          cdo_print("Grid info: nlon=%zu, nlat=%zu ", nlon, nlat);
        }

      if (cdoDebugExt)
        {
          auto order = (dxU < 0) ? "default" : "positive";
          cdo_print("About to perform destaggering (U-wind): (%3.2f; %3.2f) - %s order ", dxU, dyV, order);
        }

      if (cdoDebugExt)
        {
          auto order = (dyV < 0) ? "default" : "positive";
          cdo_print("About to perform destaggering (V-wind): (%3.2f; %3.2f) - %s order ", dxU, dyV, order);
        }

      for (int index = 0; index < ngrids; ++index)
        {
          gridID = vlistGrid(vlistID1, index);
          if (cdoDebugExt >= 10)
            cdo_print("Grid nr. %d is gridtype: %d (%s)", index, gridInqType(gridID), gridNamePtr(gridInqType(gridID)));
        }

      if (gridID0 == -1)
        {
          Debug(cdoDebugExt, "Last trial to find a reference grid for destaggered wind.");
          for (varID = 0; varID < nvars; ++varID)
            {
              gridID = vlistInqVarGrid(vlistID1, varID);
              Debug(cdoDebugExt, "Var.id %d has grid nr:%d", varID, gridID);
              if ((varID != varID1stg) && (varID != varID2stg))
                {
                  // this will the new (horizontal) grid for de-staggered
                  gridID0 = vlistInqVarGrid(vlistID1, varID);
                }
            }
        }
      if (gridID0 == -1) cdo_abort("Referencial horizontal grid not found!");

      gridsize = vlistGridsizeMax(vlistID1);

      if (gridInqSize(gridID2) != gridInqSize(gridID1))
        cdo_abort("gridSize of U-wind != gridSize of V-wind!  This should not happen!");

      if (cdoDebugExt)
        cdo_print("Allocating memory for maximum gridsize (for input) = %ld [%4.3f MB]", gridsize,
                  gridsize * sizeof(double) / (1024.0 * 1024));
      ivar.resize(gridsize);  // storage for other fields than

      gridsize = gridInqSize(gridID1);  // actual size of U-wind should be same as V-wind
      if (cdoDebugExt)
        cdo_print("Allocating memory for gridsize (destaggered output)= %ld; nlon=%zu, nlat=%zu", gridsize, nlon, nlat);
      ovar.resize(gridsize);
    }  // end of  if (!lcopy)

  const auto streamID2 = cdo_open_write(1);

  if (varID1stg != CDI_UNDEFID && varID2stg != CDI_UNDEFID)
    {
      vlistChangeVarGrid(vlistID2, varID1stg, gridID0);  // set the variable onto the non-staggered grid
      vlistChangeVarGrid(vlistID2, varID2stg, gridID0);  // set the variable onto the non-staggered grid
    }

  cdo_def_vlist(streamID2, vlistID2);  // from this point the stream is using a different vlistID !!!!!

  vlistID2 = cdo_stream_inq_vlist(streamID2);  // refresh it

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

      cdo_taxis_copy_timestep(taxisID2, taxisID1);
      cdo_def_timestep(streamID2, tsID);

      if (!lcopy && cdoDebugExt)
        {
          cdo_print("Processing timestep: %d", tsID);
          cdo_print("Starting destaggering. Total records to be processed: %05d", nrecs);
        }

      for (int recID = 0; recID < nrecs; ++recID)
        {
          cdo_inq_record(streamID1, &varID, &levelID);
          auto param = vlistInqVarParam(vlistID1, varID);
          int pnum, pcat, pdis;
          cdiDecodeParam(param, &pnum, &pcat, &pdis);
          auto code = pnum;
          auto zaxisID = vlistInqVarZaxis(vlistID1, varID);
          auto ltype = zaxis_to_ltype(zaxisID);
          auto level = (int) zaxisInqLevel(zaxisID, levelID);

          if (!lcopy)
            {
              // NOTE: There may be separate CDO staggerd variables for (33/34; 109; *) and (33/34; 100; *)
              //       You cannot use this way of U/V variable detection!
              // VarIsU = (varID == varID1stg); ** DON'T USE
              // VarIsV = (varID == varID2stg); ** DON'T USE
              vlistInqVarName(vlistID1, varID, varname);
              CheckVarIsU(varID, varname, code);
              CheckVarIsV(varID, varname, code);

              UorV = -1;  // -1: not U, neither V
              if (VarIsU)
                UorV = 0;  // 0: U-wind; 1: V-wind
              else if (VarIsV)
                UorV = 1;

              if (UorV >= 0)
                {
                  gridID = vlistInqVarGrid(vlistID1, varID);
                  if (UorV == 0)
                    {
                      if (gridID == gridID1)  // Has this variable the staggered grid?
                        {
                          if (cdoDebugExt >= 10)
                            cdo_print("Destaggering U-wind record: %05d (timestep:%d); Var.id [%4d]; (code=%3d; "
                                      "ltype=%3d; level=%4d; levelID=%3d); GridID %d => %d  *** <===",
                                      recID, tsID, varID, code, ltype, level, levelID, vlistInqVarGrid(vlistID1, varID),
                                      vlistInqVarGrid(vlistID2, varID));
                        }
                      else
                        UorV = -1;  // this U is not staggered, just copy the record..
                    }
                  if (UorV == 1)
                    {
                      if (gridID == gridID2)  // Has this variable the staggered grid?
                        {
                          if (cdoDebugExt >= 10)
                            cdo_print("Destaggering V-wind record: %05d (timestep:%d); Var.id [%4d]; (code=%3d; "
                                      "ltype=%3d; level=%4d; levelID=%3d); GridID %d => %d  *** <===",
                                      recID, tsID, varID, code, ltype, level, levelID, vlistInqVarGrid(vlistID1, varID),
                                      vlistInqVarGrid(vlistID2, varID));
                        }
                      else
                        UorV = -1;  // this V is not staggered, just copy the record..
                    }
                }  // end of: if (UorV>=0)

              if (UorV >= 0)  // re-check again since it could mean that current record with U or V is not staggered
                {
                  size_t nmiss;
                  cdo_read_record(streamID1, ivar.data(), &nmiss);

                  // void destaggerUorV(double *fu, double *fuOut, int klev, int nlat, int nlon, int UorV, long int offset);
                  // We handle one level at the time; klev=1;offset=0.
                  if ((dxU < 0.0) && (dyV < 0.0))
                    // UorV = 0: U-wind; 1: V-wind
                    destaggerUorV(ivar.data(), ovar.data(), 1, nlat, nlon, UorV, 0);
                  else if ((dyU > 0.0) && (dxV > 0.0))
                    // UorV = 0: U-wind; 1: V-wind
                    destaggerUorV_positiveOrder(ivar.data(), ovar.data(), 1, nlat, nlon, UorV, 0);
                  else
                    cdo_abort("Unsupported destaggering grid offset: (dxU; dyU) = (%3.2f; %3.2f); (dxV; dyV) = (%3.2f; %3.2f) "
                              "where: xincU=%3.2f, yincU=%3.2f, xincV=%3.2f, yincV=%3.2f",
                              dxU, dyU, dxV, dyV, xincU, yincU, xincV, yincV);

                  // Typical Hirlam LAMH_D11 situation with destaggering on (-0.5,-0.5)
                  // cdo uvDestag: Grid info: (xfirst_R = -30.20; yfirst_R = -30.80);
                  //                          (xfirst_U = -30.15; yfirst_U = -30.80);
                  //                          (xfirst_V = -30.20; yfirst_V = -30.75);
                  // cdo uvDestag: Grid info: (dxU; dyU) = (-0.05; 0.00); (dxV; dyV) = (0.00; -0.05)

                  // Less typical would be to choose destaggering on (+0.5,+0.5)
                  // cdo uvDestag: Grid info: (xfirst_R = -30.15; yfirst_R = -30.75);
                  //                          (xfirst_U = -30.15; yfirst_U = -30.80);
                  //                          (xfirst_V = -30.20; yfirst_V = -30.75);
                  // cdo uvDestag: Grid info: (dxU; dyU) = (0.00; 0.05); (dxV; dyV) = (0.05; 0.00)
                  if (cdoDebugExt >= 20)
                    cdo_print("Setting GRID id from: %d => to: %d", vlistInqVarGrid(vlistID1, varID),
                              vlistInqVarGrid(vlistID2, varID));

                  cdo_def_record(streamID2, varID, levelID);
                  cdo_write_record(streamID2, ovar.data(), nmiss);
                }
              else
                {  // copy the record to the output unchanged...
                  cdo_def_record(streamID2, varID, levelID);
                  if (cdoDebugExt >= 20)
                    cdo_print("Stream-copy data record:    %05d (timestep:%d); "
                              "Var.id [%4d]; (code=%3d; ltype=%3d; level=%4d; levelID=%3d)",
                              recID, tsID, varID, code, ltype, level, levelID);
                  cdo_copy_record(streamID2, streamID1);
                }
            }  // end of: if (!lcopy)
          else
            {  // copy the record to the output unchanged...
              cdo_def_record(streamID2, varID, levelID);
              if (cdoDebugExt >= 20)
                cdo_print("Stream-copy data record:    %05d (timestep:%d); "
                          "Var.id [%4d]; (code=%3d; ltype=%3d; level=%4d; levelID=%3d)",
                          recID, tsID, varID, code, ltype, level, levelID);
              cdo_copy_record(streamID2, streamID1);
            }

        }  // end of for ( recID = ...

      tsID++;
    }  // end of while ( (nrecs ...

  cdo_stream_close(streamID2);
  cdo_stream_close(streamID1);

  cdo_finish();

  return 0;
}

static void
rot_uv_north(int gridID, double *us, double *vs)
{
  // Function transforms grid-relative UV into north-pole relative UV in the (spherical) lat-lon coordinate space.
  // Input:  u,v : grid relative (u,v); NOT staggered
  //
  // This function needs that the gridpoint coordinates have been transformed
  // from modelspace into LatLon space using function gridToCurvilinear().
  //
  // xvals[], yvals[] contains the Lon-Lat coordinates.

  if (gridInqType(gridID) != GRID_CURVILINEAR)
    cdo_abort("%s(gridname=%s) grid must be GRID_CURVILINEAR!", __func__, gridNamePtr(gridInqType(gridID)));
  // this should never happen

  static double *rotationMatrixArray = nullptr;
  double lon_pnt0, lat_pnt0;
  double lon_pntEast = 0, lat_pntEast = 0;
  double lon_pntNorth, lat_pntNorth;
  double dLatEast = 0, dLonEast = 0;
  double dLatNorth, dLonNorth;
  double xpntEastSph, ypntEastSph, zpntEastSph = 0;
  double xpntNorthSph, ypntNorthSph, zpntNorthSph;
  double xpntNorthSphRot, ypntNorthSphRot, zpntNorthSphRot;
  double xpnt0Sph, ypnt0Sph, zpnt0Sph;
  double xnormSph, ynormSph, znormSph;
  double xncross, yncross, zncross;
  double vecAngle;
  size_t i, j;

  // The following "correction for the grid-step direction" from funtion
  // project_uv_latlon() cannot be used here! This cannot be done in the
  // geographic lon/lan (on the globe).
  // int signLon=( (xvals[1] - xvals[0]) < 0 )?-1:1;
  // int signLat=( (yvals[1] - yvals[0]) < 0 )?-1:1;
  /*
      Tested for scanning mode 64 LAMH_D11:
      -------------------------------------
      iScansNegatively = 0;
      jScansPositively = 1;
      jPointsAreConsecutive = 0;
      #-READ ONLY- alternativeRowScanning = 0;
      jDirectionIncrementInDegrees = 0.1;
      iDirectionIncrementInDegrees = 0.1;

      !!! When scanning mode 00 the lons & lats arrays: xvals[idx] & yvals[idx]; are upside-down !!!

        iScansNegatively = 0;
        jScansPositively = 0;
        jPointsAreConsecutive = 0;
        #-READ ONLY- alternativeRowScanning = 0;
        jDirectionIncrementInDegrees = 0.1;  // this should be -0.1 ??
        iDirectionIncrementInDegrees = 0.1;
  */

  const auto nx = gridInqXsize(gridID);
  const auto ny = gridInqYsize(gridID);

  Varray<double> xvals(nx * ny), yvals(nx * ny);
  gridInqXvals(gridID, xvals.data());
  gridInqYvals(gridID, yvals.data());

  int scanningMode = 0;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_SCANNINGMODE, &scanningMode);
  auto jScansPositively = (scanningMode == 64);
  if (scanningMode == 64)
    {
      if (cdoDebugExt > 1) cdo_print("NOTICE: Processing data with scanning mode(%d); gridID=%d", scanningMode, gridID);
    }
  else if (scanningMode == 0)
    {
      if (cdoDebugExt > 1) cdo_print("NOTICE: Processing data with scanning mode(%d); gridID=%d", scanningMode, gridID);
    }
  else
    {
      cdo_abort("\n***\n***\n WARNING! Unsupported data scanning mode(%d); gridID=%d; For this operation we support only: 64,00 \n"
                "RESULT will be probably incorrect!\n***\n***",
                scanningMode, gridID);
    }

  if (cdoDebugExt)
    cdo_print("%s(gridname=%s) .. processing grid with UV [nx*ny] (%zu * %zu)", __func__, gridNamePtr(gridInqType(gridID)), nx, ny);

  if (gridInqSize(gridID) != (nx * ny)) cdo_abort("Incorrect gridsize (%zu) != nx*ny (%zu * %zu)", gridInqSize(gridID), nx, ny);
    // this should never happen

#define OPTrotuvNorth 1  // ACTIVATE SPEED - OPTIMIZATION

#define radians(aDeg) (DEG2RAD * aDeg)
#define NormVector(vec0, vec1, vec2)                                    \
  {                                                                     \
    double vecLen = std::sqrt(vec0 * vec0 + vec1 * vec1 + vec2 * vec2); \
    vec0 = vec0 / vecLen;                                               \
    vec1 = vec1 / vecLen;                                               \
    vec2 = vec2 / vecLen;                                               \
  }

#define CrossProd(vecx0, vecx1, vecx2, vecy0, vecy1, vecy2, vecz0, vecz1, vecz2) \
  {                                                                              \
    vecz0 = vecx1 * vecy2 - vecy1 * vecx2;                                       \
    vecz1 = vecx2 * vecy0 - vecy2 * vecx0;                                       \
    vecz2 = vecx0 * vecy1 - vecy0 * vecx1;                                       \
  }

  if (rotationMatrixArray == nullptr)
    {
      if (cdoDebugExt > 0) cdo_print("About to compute rotationMatrixArray for the whole grid [%zu x %zu]", nx, ny);

      rotationMatrixArray = (double *) Malloc(4 * nx * ny * sizeof(double));
      for (j = 0; j < ny; ++j)
        for (i = 0; i < nx; ++i)
          {
            const auto idx = j * nx + i;
            lon_pnt0 = xvals[idx];
            lat_pnt0 = yvals[idx];
#ifndef OPTrotuvNorth
            // For speed - optimization not used and not needed. Kept only for clarity.
            if ((i + 1) < nx)
              {
                lon_pntEast = xvals[idx + 1];  // longitude of grid point towards east
                lat_pntEast = yvals[idx + 1];  //  latitude of grid point towards east
              }
            else
              {
                lon_pntEast = lon_pnt0 + 1.0 * (lon_pnt0 - xvals[idx - 1]);  // at the grid border define extended gridpoint
                lat_pntEast = lat_pnt0 + 1.0 * (lat_pnt0 - yvals[idx - 1]);
              }
#endif
            if ((j + 1) < ny)
              {
                lon_pntNorth = xvals[idx + nx];  // longitude of grid point towards north
                lat_pntNorth = yvals[idx + nx];  //  latitude of grid point towards north
              }
            else
              {
                lon_pntNorth = lon_pnt0 + 1.0 * (lon_pnt0 - xvals[idx - nx]);  // at the grid border define extended gridpoint
                lat_pntNorth = lat_pnt0 + 1.0 * (lat_pnt0 - yvals[idx - nx]);
              }

            // (lon_pntNorth, lat_pntNorth)
            //     ^
            //     |       (lon_pntCenter, lat_pntCenter)   center of the
            //     cell-diagonal
            //     |
            // (lon_pnt0,lat_pnt0) ----> (lon_pntEast,lat_pntEast)

            // lon_pntCenter = 0.5*(lon_pntNorth + lon_pntEast);
            // lat_pntCenter = 0.5*(lat_pntNorth + lat_pntEast);
            // lon_pnt0 -= lon_pntCenter; lon_pntEast -= lon_pntCenter;
            // lon_pntNorth -= lon_pntCenter;  lat_pnt0 -= lat_pntCenter;
            // lat_pntEast -= lat_pntCenter; lat_pntNorth -= lat_pntCenter;

            // This is the local coordinate system of a grid cell where we have
            // (u,v) at location (xpnt0,ypnt0).

            // The local coordinate system is now centered around
            // (lon_pnt0,lat_pnt0) The vector towards north pole (UP-VECTOR) at
            // this location will be (0,1,0) The tangent plane at this location
            // is XY wil a normal (0, 0, 1)

            // Nummerical approach using projection onto a unit sphere
            lon_pnt0 = radians(lon_pnt0);
            lat_pnt0 = radians(lat_pnt0);
            xpnt0Sph = std::cos(lat_pnt0) * std::cos(lon_pnt0);
            ypnt0Sph = std::cos(lat_pnt0) * std::sin(lon_pnt0);  // # Get // [lon_pnt0,lat_pnt0] on the unit sphere.
            zpnt0Sph = std::sin(lat_pnt0);                       // # Only XY plane is needed.
            dLonNorth = radians(lon_pntNorth);
            dLatNorth = radians(lat_pntNorth);
            xpntNorthSph = std::cos(dLatNorth) * std::cos(dLonNorth);
            ypntNorthSph = std::cos(dLatNorth) * std::sin(dLonNorth);  // # Get [dLonNorth,dLatNorth] on the unit sphere.
            zpntNorthSph = std::sin(dLatNorth);                        //# Only XY plane is needed.
            xpntNorthSph -= xpnt0Sph, ypntNorthSph -= ypnt0Sph;
            zpntNorthSph -= zpnt0Sph;
            NormVector(xpntNorthSph, ypntNorthSph, zpntNorthSph);  // vecy

#ifndef OPTrotuvNorth
            // For speed - optimization not used and not needed. Kept only for clarity.
            dLonEast = radians(lon_pntEast);
            dLatEast = radians(lat_pntEast);
            xpntEastSph = std::cos(dLatEast) * std::cos(dLonEast);
            ypntEastSph = std::cos(dLatEast) * std::sin(dLonEast);  // # Get [dLonEast,dLatEast] on the unit sphere.
            zpntEastSph = std::sin(dLatEast);                       // # Only XY plane is needed.
            xpntEastSph -= xpnt0Sph;
            ypntEastSph -= ypnt0Sph;
            zpntEastSph -= zpnt0Sph;                            // make vectors from points
            NormVector(xpntEastSph, ypntEastSph, zpntEastSph);  // vecx
            // vecz = CrossProd(vecx,vecy)
            CrossProd(xpntEastSph, ypntEastSph, zpntEastSph, xpntNorthSph, ypntNorthSph, zpntNorthSph, xnormSph, ynormSph,
                      znormSph);  // vec z
#else
            xnormSph = xpnt0Sph;
            ynormSph = ypnt0Sph;
            znormSph = zpnt0Sph;
            NormVector(xnormSph, ynormSph, znormSph);  // vec z ... normal vector to the sphere at pnt0 (lon_pnt0,lat_pnt0)
#endif  //#ifdef OPTrotuvNorth

            //# vecUP = (0.0,0.0,1.0) .. up-vector in a global coordinate system
            //#   ^^ up-vector & vector-towards-north-pole & shere-normal-vector
            // belong to ONE plane #      that crosses north pole and point pnt0
            //(lon_pnt0,lat_pnt0) # Project vecUP onto plane XY, where
            // plane-normal is vecz # vecnProjXY = vecUP - D*vecz;   D=
            // a*x1+b*y1+c*z1;  vecz=(a,b,c); vecUP = (x1,y1,z1)=(0,0,1) #
            // D= vecz[2]*1; # vecyRot = NormVector( (0.0 - vecz[2]*vecz[0],0.0
            //- vecz[2]*vecz[1], 1.0  - vecz[2]*vecz[2]) )

            // double Dist =  xnormSph * 0.0 +  ynormSph * 0.0 + znormSph * 1.0;
            // // Left out for optimization
            xpntNorthSphRot = -znormSph * xnormSph;       // xpntNorthSphRot = 0.0 - Dist*xnormSph;
            ypntNorthSphRot = -znormSph * ynormSph;       // ypntNorthSphRot = 0.0 - Dist*ynormSph;
            zpntNorthSphRot = 1.0 - znormSph * znormSph;  // zpntNorthSphRot = 1.0 - Dist*znormSph;
            NormVector(xpntNorthSphRot, ypntNorthSphRot, zpntNorthSphRot);

            // This would create in 3D the rotated Easting vector; but we don't
            // need it in this routine. Left out to optimize the computation..
            // CrossProd( xpntNorthSphRot, ypntNorthSphRot, zpntNorthSphRot,
            // xnormSph, ynormSph, znormSph,
            //            xpntEastSph,  ypntEastSphRot,  zpntEastSphRot );
            //            //vecxRot = CrossProd(vecy,vecz)

            vecAngle
                = std::acos((xpntNorthSph * xpntNorthSphRot + ypntNorthSph * ypntNorthSphRot + zpntNorthSph * zpntNorthSphRot));
            // Determine the sign of the angle
            CrossProd(xpntNorthSphRot, ypntNorthSphRot, zpntNorthSphRot, xpntNorthSph, ypntNorthSph, zpntNorthSph, xncross, yncross,
                      zncross);
            if ((xncross * xnormSph + yncross * ynormSph + zncross * znormSph) > 0.0)  // dotProduct
              vecAngle *= -1.0;

            if (!jScansPositively) vecAngle += radians(180.0);  // this is needed in scanning mode 00

            xpntNorthSph = std::sin(vecAngle);  // Rotate the point/vector (0,1) around Z-axis with vecAngle
            ypntNorthSph = std::cos(vecAngle);
            xpntEastSph = ypntNorthSph;  // Rotate the same point/vector around Z-axis with 90 degrees
            ypntEastSph = -xpntNorthSph;

            // zpntNorthSph = 0; zpntEastSph = 0;  // not needed in 2D

            // 1) Build the rotation matrix and put the axes-base vectors into the matrix
            const auto VJaa = xpntEastSph;
            const auto VJab = xpntNorthSph;
            const auto VJba = ypntEastSph;
            const auto VJbb = ypntNorthSph;

            auto idx4 = 4 * idx;
            // Caching the rotation matrix for later usage ..
            rotationMatrixArray[idx4++] = VJaa;
            rotationMatrixArray[idx4++] = VJab;
            rotationMatrixArray[idx4++] = VJba;
            rotationMatrixArray[idx4++] = VJbb;

            if (cdoDebugExt >= 20)
              if (((i < 3) && (j < 3)) || ((i > (nx - 3)) && (j > (ny - 3))))
                {
                  cdo_print("grid point [%zu,%zu] with latlon[%3.6f,%3.6f]; (lon_pntNorth, lat_pntNorth) = [%3.6f,%3.6f]; "
                            "dLonNorth=%3.6f; dLatNorth=%3.6f (Northing grid relative) ",
                            i, j, lon_pnt0, lat_pnt0, lon_pntNorth, lat_pntNorth, RAD2DEG * dLonNorth, RAD2DEG * dLatNorth);
                  cdo_print("grid point [%zu,%zu] with latlon[%3.6f,%3.6f]; (lon_pntEast,lat_pntEast    )= [%3.6f,%3.6f]; "
                            "dLonEast =%3.6f; dLatEast =%3.6f (Easting grid relative ) ",
                            i, j, lon_pnt0, lat_pnt0, lon_pntEast, lat_pntEast, RAD2DEG * dLonEast, RAD2DEG * dLatEast);
                  // cdo_print("(xpntNorthSph, ypntNorthSph)= [%3.6f,%3.6f];
                  // (xpntEastSph,ypntEastSph) = [%3.6f,%3.6f];",
                  //         xpntNorthSph, ypntNorthSph, xpntEastSph,ypntEastSph
                  //         );
                  // vecAngle = RAD2DEG * std::acos( (xpntEastSph*xpntNorthSph +
                  // ypntEastSph*ypntNorthSph + zpntEastSph*zpntNorthSph) );
                  // vecAngle = RAD2DEG * std::acos( (xpntEastSph*xpntNorthSph +
                  // ypntEastSph*ypntNorthSph) );
                  cdo_print("(xpntNorthSph, ypntNorthSph, zpntNorthSph)= [%3.6f,%3.6f,%3.6f]; (xpntEastSph,ypntEastSph, "
                            "zpntEastSph) = [%3.6f,%3.6f,%3.6f]; vecAngle= %3.6f",
                            xpntNorthSph, ypntNorthSph, zpntNorthSph, xpntEastSph, ypntEastSph, zpntEastSph, vecAngle);
                  cdo_print("rotation matrix for grid point [%zu,%zu] with latlon[%3.6f,%3.6f]: (VJaa, VJab, VJba, VJbb) = "
                            "(%3.6f,%3.6f,%3.6f,%3.6f)",
                            i, j, lon_pnt0, lat_pnt0, VJaa, VJab, VJba, VJbb);
                }

          }  // end of for ( i = 0; i < nx; i++ )
    }        // end of if (rotationMatrixArray== nullptr)

  // Take the rotation matrix from the cache
  for (j = 0; j < ny; ++j)
    for (i = 0; i < nx; ++i)
      {
        const auto idx = (j * nx + i);
        auto idx4 = 4 * idx;
        const auto VJaa = rotationMatrixArray[idx4++];
        const auto VJab = rotationMatrixArray[idx4++];
        const auto VJba = rotationMatrixArray[idx4++];
        const auto VJbb = rotationMatrixArray[idx4++];

        // 2) Transform the UV vector with jacobian matrix
        const auto u = us[idx];
        const auto v = vs[idx];
        // u = 6.0;  v = 0.0; // test: 6 m/s along the easting direction of the grid
        const auto magnitude = hypot(u, v);  // old vector magnitude in the model space
        //(uu) =   (VJaa VJab) * ( u )
        //(vv)     (VJba VJbb)   ( v )
        const auto uu = VJaa * u + VJab * v;
        const auto vv = VJba * u + VJbb * v;
        //(uu) =   (VJaa VJab VJac) * ( u )
        //(vv)     (VJba VJbb VJbc)   ( v )
        //(ww)     (VJba VJbb VJcc)   ( w )

        // 3) Apply scaling of the vector so that the vector keeps the original length (model space)
        const auto newMagnitude = hypot(uu, vv);
        us[idx] = uu * magnitude / newMagnitude;
        vs[idx] = vv * magnitude / newMagnitude;
      }

  Debug(cdoDebugExt >= 20, "%s(gridname=%s) finished.", __func__, gridNamePtr(gridInqType(gridID)));
}

static void
rot_uv_back_mode64(int gridID, double *us, double *vs)
{
  // This function is partially based on rot_uv_back() of the CDO rotuv
  // operator. This routine expects the data to be in scanning-mode 64. This
  // routine gives comparable (not numerically same) results as rot_uv_north().
  // rot_uv_back_mode64() is significantly slower than rot_uv_north().

  int scanningMode = 0;
  cdiInqKeyInt(gridID, CDI_GLOBAL, CDI_KEY_SCANNINGMODE, &scanningMode);
  if (scanningMode == 64)
    {
      if (cdoDebugExt > 1) cdo_print("NOTICE: Processing data with scanning mode(%d); gridID=%d", scanningMode, gridID);
    }
  else
    {
      cdo_print("\n***\n***\n WARNING! Unsupported data scanning mode(%d); gridID=%d; For this operation we support only: 64\n"
                "RESULT will be probably incorrect!\n***\n***",
                scanningMode, gridID);
    }

  double xpole = 0, ypole = 0, angle = 0;
  if (gridInqType(gridID) == GRID_PROJECTION && gridInqProjType(gridID) == CDI_PROJ_RLL)
    gridInqParamRLL(gridID, &xpole, &ypole, &angle);

  const auto nlon = gridInqXsize(gridID);
  const auto nlat = gridInqYsize(gridID);

  Varray<double> xvals(nlon), yvals(nlat);
  gridInqXvals(gridID, xvals.data());
  gridInqYvals(gridID, yvals.data());

  // Convert lat/lon units if required
  cdo_grid_to_degree(gridID, CDI_XAXIS, 1, &xpole, "xpole");
  cdo_grid_to_degree(gridID, CDI_XAXIS, nlon, xvals.data(), "grid center lon");
  cdo_grid_to_degree(gridID, CDI_YAXIS, 1, &ypole, "ypole");
  cdo_grid_to_degree(gridID, CDI_YAXIS, nlat, yvals.data(), "grid center lat");

  double u, v;
  for (size_t ilat = 0; ilat < nlat; ilat++)
    for (size_t ilon = 0; ilon < nlon; ilon++)
      {
        const auto i = ilat * nlon + ilon;

        const auto xval = lamrot_to_lam(yvals[ilat], xvals[ilon], ypole, xpole, angle);
        const auto yval = phirot_to_phi(yvals[ilat], xvals[ilon], ypole, angle);

        usvs_to_uv(us[i], vs[i], yval, xval, ypole, xpole, &u, &v);

        us[i] = u;
        vs[i] = v;
      }
}

static void
project_uv_latlon(int gridID, double *us, double *vs)
{
  // The function project_uv_latlon() is WARPING the UV-vector from
  // grid-relative to North-pole-relative definition in LAT-LON projection,
  // flatten into 2D space. The resulting vectors do NOT exit in a SPHERICAL
  // LAT-LON space BUT in a strongly WARPED (2D) LAT-LON projection space.
  // Imagine what happens to the grid-points and grid-cells close to the poles.
  // And what affect it has on the "grid-cell relative UV-vectors" in
  // NON-rectangular grid-cells. This routine is VERY fast as it uses JACOBIANs.
  // Depending on shape distorsion of each cell we see the "flow" vectors to
  // adapt. The resulting UV-vectors can by directly plot with (2D) plotting
  // tools in LAT-LON. Even (2D) streamlines can be used on this way transformed
  // vectors. This function expectes scaning-mode 64.

  if (gridInqType(gridID) != GRID_CURVILINEAR)
    cdo_abort("%s(gridname=%s) transformation grid must be GRID_CURVILINEAR!", __func__, gridNamePtr(gridInqType(gridID)));
  // this should never happen

  const auto nx = gridInqXsize(gridID);
  const auto ny = gridInqYsize(gridID);

  Varray<double> xvals(nx * ny), yvals(nx * ny);
  gridInqXvals(gridID, xvals.data());
  gridInqYvals(gridID, yvals.data());

  int signLon = ((xvals[1] - xvals[0]) < 0) ? -1 : 1;
  int signLat = ((yvals[1] - yvals[0]) < 0) ? -1 : 1;

  if (cdoDebugExt)
    cdo_print("%s(gridname=%s) .. processing grid with UV [nx*ny] (%zu * %zu)", __func__, gridNamePtr(gridInqType(gridID)), nx, ny);

  if (gridInqSize(gridID) != (nx * ny)) cdo_abort("Incorrect gridsize (%zu) != nx*ny (%zu * %zu)", gridInqSize(gridID), nx, ny);
  // this should never happen

  for (size_t j = 0; j < ny; ++j)
    for (size_t i = 0; i < nx; ++i)
      {
        const auto idx = j * nx + i;
        const auto xpnt0 = xvals[idx];
        const auto ypnt0 = yvals[idx];
        double xpntEast, ypntEast;
        double xpntNorth, ypntNorth;
        if ((i + 1) < nx)
          {
            xpntEast = xvals[idx + 1];  // longitude of grid point towards east
            ypntEast = yvals[idx + 1];  //  latitude of grid point towards east
          }
        else
          {
            xpntEast = xpnt0 + 0.01 * (xpnt0 - xvals[idx - 1]);  // at the grid border define extended gridpoint
            ypntEast = ypnt0 + 0.01 * (ypnt0 - yvals[idx - 1]);
          }
        if ((j + 1) < ny)
          {
            xpntNorth = xvals[idx + nx];  // longitude of grid point towards north
            ypntNorth = yvals[idx + nx];  //  latitude of grid point towards north
          }
        else
          {
            xpntNorth = xpnt0 + 0.01 * (xpnt0 - xvals[idx - nx]);  // at the grid border define extended gridpoint
            ypntNorth = ypnt0 + 0.01 * (ypnt0 - yvals[idx - nx]);
          }
        /*
            This is the local coordinate system of a grid cell where we have
           (u,v) at location (xpnt0,ypnt0). Gridpoint coordinates have been
           transformed from modelspace into LatLon space using function gridToCurvilinear().

            (xpntNorth, ypntNorth)
                ^
                |
                |
            (xpnt0,ypnt0) ----> (xpntEast,ypntEast)

            modelXLon=modelX+deltaX;    xpntEast
            modelYLon=modelY;           ypntEast
            modelXLat=modelX;           xpntNorth
            modelYLat=modelY+deltaY;    ypntNorth

            distLon = hypot(modelXLon-lo, modelYLon-la);
            distLat = hypot(modelXLat-lo, modelYLat-la);
        */
        ///  Basically transforms grid-relative UV into north pole relative UV
        ///  (~ lat-lon space) u,v : grid relative (u,v) not staggered get(u); get(v)
        // 1) Build the jacobian matrix
        const auto distLon = hypot(xpntEast - xpnt0, ypntEast - ypnt0);
        const auto distLat = hypot(xpntNorth - xpnt0, ypntNorth - ypnt0);
        const auto VJaa = signLon * (xpntEast - xpnt0) / distLon;
        const auto VJab = signLon * (xpntNorth - xpnt0) / distLat;
        const auto VJba = signLat * (ypntEast - ypnt0) / distLon;
        const auto VJbb = signLat * (ypntNorth - ypnt0) / distLat;
        if (cdoDebugExt >= 20)
          if (((i < 3) && (j < 3)) || ((i > (nx - 3)) && (j > (ny - 3))))
            cdo_print("Jacobian for grid point [%zu,%zu] with latlon[%3.6f,%3.6f]: (VJaa, VJab, VJba, VJbb) = "
                      "(%3.6f,%3.6f,%3.6f,%3.6f)",
                      i, j, xpnt0, ypnt0, VJaa, VJab, VJba, VJbb);
        // 2) Transform the UV vector with jacobian matrix
        const auto u = us[idx];
        const auto v = vs[idx];
        // u = 6.0;  v = 0.0; // test: 6 m/s along the easting direction of the grid
        const auto magnitude = hypot(u, v);  // old vector magnitude in the model space
        const auto uu = VJaa * u + VJab * v;
        const auto vv = VJba * u + VJbb * v;
        // 3) Apply scaling of the vector so that the vector keeps the original length (model space)
        const auto newMagnitude = hypot(uu, vv);
        us[idx] = uu * magnitude / newMagnitude;
        vs[idx] = vv * magnitude / newMagnitude;
      }

  Debug(cdoDebugExt >= 20, "%s(gridname=%s) finished.", __func__, gridNamePtr(gridInqType(gridID)));
}

void *
TransformUV(int operatorID)
{
  int varID, levelID;
  int varID1, varID2, nlevel1, nlevel2;
  size_t gridsize = 0;
  int code, gridID;
  int param, ltype, level, nlevs, zaxisID;
  int pnum, pcat, pdis;
  int chcodes[MAXARG];
  const char *chvars[MAXARG];
  char varname[CDI_MAX_NAME];
  int gridIDcurvl = -1;
  int gridIDlastused = -1;

  // Note: Already initialized by the caller! Don't call again: cdo_initialize(process);

  operator_input_arg("Pairs of u and v in the rotated system;\n usage: rotuvNorth,u,v  -or- rotuvNorth,33,34");

  int nch = cdo_operator_argc();
  if (nch != 2) cdo_abort("Number of input arguments != 2");
  if (nch >= MAXARG) cdo_abort("Number of input arguments >= %d", MAXARG);

  bool lvar = false;  // We have a list of codes
  int len = (int) cdo_operator_argv(0).size();
  int ix = (cdo_operator_argv(0)[0] == '-') ? 1 : 0;
  for (int i = ix; i < len; ++i)
    if (!isdigit(cdo_operator_argv(0)[i]))
      {
        lvar = true;  // We have a list of variables
        break;
      }

  if (lvar)
    {
      for (int i = 0; i < nch; ++i) chvars[i] = cdo_operator_argv(i).c_str();
    }
  else
    {
      for (int i = 0; i < nch; ++i) chcodes[i] = parameter_to_int(cdo_operator_argv(i));
    }

  const auto streamID1 = cdo_open_read(0);

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

  const auto nvars = vlistNvars(vlistID1);
  const auto num_recs = vlistNrecs(vlistID1);

  std::vector<RecordInfo> recList(num_recs);

  std::vector<std::vector<size_t>> varnmiss(nvars);
  Varray3D<double> vardata(nvars);

  // U & V are NOT grid relative
  for (varID = 0; varID < nvars; ++varID) cdiDefKeyInt(vlistID2, varID, CDI_KEY_UVRELATIVETOGRID, 0);

  bool lfound[MAXARG];
  for (int i = 0; i < nch; ++i) lfound[i] = false;

  if (lvar)
    {
      for (varID = 0; varID < nvars; ++varID)
        {
          vlistInqVarName(vlistID2, varID, varname);
          for (int i = 0; i < nch; ++i)
            if (strcmp(varname, chvars[i]) == 0) lfound[i] = true;
        }
      for (int i = 0; i < nch; ++i)
        if (!lfound[i]) cdo_abort("Variable %s not found!", chvars[i]);
    }
  else
    {
      for (varID = 0; varID < nvars; ++varID)
        {
          code = vlistInqVarCode(vlistID2, varID);
          for (int i = 0; i < nch; ++i)
            if (code == chcodes[i]) lfound[i] = true;
        }
      for (int i = 0; i < nch; ++i)
        if (!lfound[i]) cdo_abort("Code %d not found!", chcodes[i]);
    }

  int VarIsU, VarIsV;

  // NOTE: Variable with codes (3[3,4],105,10) will get from CDO typically a name: "10u", resp. "10v"

  for (varID = 0; varID < nvars; ++varID)
    {
      param = vlistInqVarParam(vlistID1,
                               varID); /*  vlistInqVarParam(int vlistID, int varID): Get the parameter number of a Variable */
      cdiDecodeParam(param, &pnum, &pcat, &pdis);
      code = pnum;
      zaxisID = vlistInqVarZaxis(vlistID1, varID);
      ltype = zaxis_to_ltype(zaxisID);
      nlevs = zaxisInqSize(zaxisID);
      vlistInqVarName(vlistID1, varID,
                      varname); /* vlistInqVarName(int vlistID, int varID, char *name): Get the name of a Variable */

      gridID = vlistInqVarGrid(vlistID1, varID);
      if (cdoDebugExt >= 20)
        cdo_print(
            "Var.id [%4d] with grib code:%3d and has name: %6s; level type: %3d; number of levels: %3d; gridID: %d; zaxisID: %d",
            varID, code, varname, ltype, nlevs, gridID, zaxisID);

      if (!(gridInqType(gridID) == GRID_PROJECTION && gridInqProjType(gridID) == CDI_PROJ_RLL))
        cdo_abort("Only rotated lon/lat grids supported!");

      CheckVarIsU(varID, varname, code);
      CheckVarIsV(varID, varname, code);
      if (VarIsU || VarIsV)
        {
          gridsize = gridInqSize(gridID);
          if (cdoDebugExt)
            cdo_print("Allocating memory for variableID %4d (code=%3d): gridsize(%zu)*nlevels(%d) = %zu [%4.3f MB]", varID,
                      vlistInqVarCode(vlistID2, varID), gridsize, nlevs, gridsize * nlevs,
                      gridsize * nlevs * sizeof(double) / (1024.0 * 1024));
          varnmiss[varID].resize(nlevs);
          vardata[varID].resize(nlevs);
          for (levelID = 0; levelID < nlevs; ++levelID) vardata[varID][levelID].resize(gridsize);
        }
    }

  Debug(cdoDebugExt, "Neccessary memory has been allocated.");

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

  const auto streamID2 = cdo_open_write(1);

  cdo_def_vlist(streamID2, vlistID2);          // from this point the stream is using a different vlistID !!!!!
  vlistID2 = cdo_stream_inq_vlist(streamID2);  // refresh it

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

      cdo_taxis_copy_timestep(taxisID2, taxisID1);
      cdo_def_timestep(streamID2, tsID);

      if (cdoDebugExt) cdo_print("About to read U & V data to memory. Other data will be stream-copied to the output file.");

      for (int recID = 0; recID < nrecs; ++recID)
        {
          cdo_inq_record(streamID1, &varID, &levelID);
          code = vlistInqVarCode(vlistID1, varID);
          zaxisID = vlistInqVarZaxis(vlistID1, varID);
          ltype = zaxis_to_ltype(zaxisID);
          level = zaxisInqLevel(zaxisID, levelID);

          if (vardata[varID].empty())
            {                             // This means that it is not eighter U neither V.
              recList[recID].varID = -1;  // We will NOT record/store this field in memory
              recList[recID].levelID = -1;
              // We will stream-copy this data
              cdo_def_record(streamID2, varID, levelID);
              // if ( cdoDebugExt>10 ) cdo_print("Copying data record.. %05d (timestep:%05d)", recID, tsID);
              if (cdoDebugExt >= 20)
                cdo_print("Stream-copy data record:    %05d (timestep:%d); Var.id [%4d]; (code=%3d; ltype=%3d; level=%4d; "
                          "levelID=%3d)",
                          recID, tsID, varID, code, ltype, level, levelID);
              cdo_copy_record(streamID2, streamID1);  // cannot do this ! We have to set the flag uvGridRelative = 0
            }
          else
            {
              recList[recID].varID = varID;
              recList[recID].levelID = levelID;
              if (cdoDebugExt >= 10)
                cdo_print("Memmory-read data record:   %05d (timestep:%d); Var.id [%4d]; (code=%3d; ltype=%3d; level=%4d; "
                          "levelID=%3d)",
                          recID, tsID, varID, code, ltype, level, levelID);
              cdo_read_record(streamID1, vardata[varID][levelID].data(), &varnmiss[varID][levelID]);
              if (varnmiss[varID][levelID]) cdo_abort("Missing values unsupported for this operator!");
            }
        }  // end of for ( recID = 0; recID < nrecs; ..

      Debug(cdoDebugExt, "All neccessary U & V data are in memory. About to transform the windvectors...");

      int code1, zaxisID1, ltype1;
      int code2, zaxisID2, ltype2;
      Debug(cdoDebugExt, "Looping over %d variables to look for U-wind..", nvars);

      // find u-variables:
      for (varID1 = 0; varID1 < nvars; ++varID1)
        if (vardata[varID1].empty())
          {
            // if ( cdoDebugExt ) cdo_print("Checking U-wind: vardata[%d]== nullptr ",varID1);
          }
        else  // This means that it is U or V.
          {
            code1 = vlistInqVarCode(vlistID2, varID1);
            zaxisID1 = vlistInqVarZaxis(vlistID2, varID1);
            ltype1 = zaxis_to_ltype(zaxisID1);
            nlevel1 = zaxisInqSize(zaxisID1);
            vlistInqVarName(vlistID2, varID1, varname);
            CheckVarIsV(varID1, varname, code1);
            if (VarIsV) continue;
            if (cdoDebugExt >= 20)
              cdo_print("Checking U-wind: Var.id [%4d] with grib code:%3d; name: %6s; level type: %3d; number of levels: %3d; "
                        "zaxisID: %d",
                        varID1, code1, varname, ltype1, nlevel1, zaxisID1);
            CheckVarIsU(varID1, varname, code1);
            if (!VarIsU) continue;
            if (cdoDebugExt >= 10)
              cdo_print("** FOUND U-wind; Var.id [%4d] with grib code:%3d; name: %6s; level type: %3d; number of levels: %3d; "
                        "zaxisID: %d",
                        varID1, code1, varname, ltype1, nlevel1, zaxisID1);
            const auto usvarID = varID1;
            // find corresponding v-variable to u-variable:
            for (varID2 = 0; varID2 < nvars; ++varID2)
              if (vardata[varID2].empty())
                {
                  // if ( cdoDebugExt ) cdo_print("Checking V-wind: vardata[%d]== nullptr ",varID1);
                }
              else  // This means that it is U or V.
                {
                  code2 = vlistInqVarCode(vlistID2, varID2);
                  zaxisID2 = vlistInqVarZaxis(vlistID2, varID2);
                  ltype2 = zaxis_to_ltype(zaxisID2);
                  nlevel2 = zaxisInqSize(zaxisID2);
                  vlistInqVarName(vlistID2, varID2, varname);
                  CheckVarIsU(varID2, varname, code2);
                  if (VarIsU) continue;
                  if (cdoDebugExt >= 20)
                    cdo_print("Checking V-wind: Var.id [%4d] with grib code:%3d; name: %6s; level type: %3d; number of "
                              "levels: %3d; zaxisID: %d",
                              varID2, code2, varname, ltype2, nlevel2, zaxisID2);
                  CheckVarIsV(varID2, varname, code2);
                  if (!VarIsV) continue;
                  if (!((ltype1 == ltype2) && (nlevel1 == nlevel2) && (zaxisID1 == zaxisID2))) continue;
                  if (cdoDebugExt >= 10)
                    cdo_print("** FOUND V-wind; Var.id [%4d] with grib code:%3d; name: %6s; level type: %3d; number of "
                              "levels: %3d; zaxisID: %d",
                              varID2, code2, varname, ltype1, nlevel2, zaxisID2);
                  const auto vsvarID = varID2;
                  if (cdoDebugExt >= 20)
                    cdo_print("Using code %d [%d](u) and code %d [%d](v)", vlistInqVarCode(vlistID1, varID1), code1,
                              vlistInqVarCode(vlistID1, varID2), code2);
                  gridID = vlistInqVarGrid(vlistID1, varID1);
                  if (operatorID != ROTUVN)  // ROTUVN operator does not need creation of gridIDcurvl ...
                    {
                      if ((gridIDcurvl != -1) && (gridIDlastused != gridID))
                        cdo_abort("The gridID (%d) used just previously for uv-wind tranformation is not same this "
                                  "time(%d)!",
                                  gridIDlastused, gridID);

                      if (gridIDcurvl == -1)
                        {
                          if (cdoDebugExt) cdo_print("Building LAT-LON grid for the direction to the North. (First time only).");
                          gridIDlastused = gridID;
                          // Compute 2D array with latlons only once. We expect that all horizontal grids for UV are same.
                          // NOTE: At this stage U and V cannot be staggered!
                          gridIDcurvl = gridToCurvilinear(gridID, 1);
                          if (cdoDebugExt)
                            cdo_print("Transformed rotated-latLon grid (id:%d) to curvilinear (id:%d) with true lat-lon "
                                      "coordinates.",
                                      gridID, gridIDcurvl);
                          // Grid definition with id: "gridIDcurvl" contains latlons of every gridpoint..
                          // For details see: ./libcdi/src/cdi.h; Setgridtype to GRID_CURVILINEAR

                          if (gridIDcurvl == -1) cdo_abort("Creation of curvilinear grid definition failed!");

                          if (gridInqType(gridIDcurvl) != GRID_CURVILINEAR)
                            {
                              gridDestroy(gridIDcurvl);
                              cdo_abort("Creation of curvilinear grid definition failed: type != GRID_CURVILINEAR");
                            }
                          if (cdoDebugExt)
                            {
                              double xpole = 0, ypole = 0, angle = 0;
                              if (gridInqType(gridID) == GRID_PROJECTION && gridInqProjType(gridID) == CDI_PROJ_RLL)
                                gridInqParamRLL(gridID, &xpole, &ypole, &angle);

                              cdo_print("GRID_PROJECTION(id: %d) && CDI_PROJ_RLL:", gridID);
                              cdo_print("grid Xsize   %zu, grid Ysize   %zu", gridInqXsize(gridID), gridInqYsize(gridID));
                              cdo_print("grid Xfirst  %4.3f, grid Yfirst  %4.3f", gridInqXval(gridID, 0), gridInqYval(gridID, 0));
                              cdo_print("grid Xinc   %4.3f, grid Yinc   %4.3f", gridInqXinc(gridID), gridInqYinc(gridID));
                              cdo_print("grid Xpole   %4.3f, grid Ypole   %4.3f", xpole, ypole);
                              cdo_print("GRID_CURVILINEAR (id: %d):", gridIDcurvl);
                              cdo_print("grid Xsize   %zu, grid Ysize   %zu", gridInqXsize(gridIDcurvl), gridInqYsize(gridIDcurvl));
                              cdo_print("grid Xfirst  %4.3f, grid Yfirst  %4.3f", gridInqXval(gridIDcurvl, 0),
                                        gridInqYval(gridIDcurvl, 0));
                              cdo_print("grid Xlast   %4.3f, grid Ylast   %4.3f",
                                        gridInqXval(gridIDcurvl, gridInqSize(gridIDcurvl) - 1),
                                        gridInqYval(gridIDcurvl, gridInqSize(gridIDcurvl) - 1));
                              if (cdoDebugExt >= 20)
                                {
                                  printf("Xvals (size=%zu):\n", gridInqSize(gridIDcurvl));
                                  size_t ii;
                                  for (ii = 0; ii < 10; ++ii) printf("%4.3f ", gridInqXval(gridIDcurvl, ii));
                                  printf("\n...\n");
                                  for (ii = gridInqSize(gridIDcurvl) - 10; ii < gridInqSize(gridIDcurvl); ++ii)
                                    printf("%4.3f ", gridInqXval(gridIDcurvl, ii));
                                  printf("\n");
                                  printf("Yvals (size=%zu):\n", gridInqSize(gridIDcurvl));
                                  for (ii = 0; ii < 10; ++ii) printf("%4.3f ", gridInqYval(gridIDcurvl, ii));
                                  printf("\n...\n");
                                  for (ii = gridInqSize(gridIDcurvl) - 10; ii < gridInqSize(gridIDcurvl); ++ii)
                                    printf("%4.3f ", gridInqYval(gridIDcurvl, ii));
                                  printf("\n");
                                }
                            }  // end of if (cdoDebugExt)
                          Debug(cdoDebugExt, "LAT-LON grid created.");
                        }  // end of if (gridIDcurvl==-1)
                    }      // end of if (operatorID != ROTUVN)

                  int uRelativeToGrid = -1, vRelativeToGrid = -1;
                  cdiInqKeyInt(vlistID1, varID1, CDI_KEY_UVRELATIVETOGRID, &uRelativeToGrid);
                  cdiInqKeyInt(vlistID1, varID2, CDI_KEY_UVRELATIVETOGRID, &vRelativeToGrid);

                  if (uRelativeToGrid != 1)
                    {
                      cdo_warning("U component is NOT relative to grid. No transformation to north-pole takes place!");
                    }
                  else if (vRelativeToGrid != 1)
                    {
                      cdo_warning("V component is NOT relative to grid. No transformation to north-pole takes place!");
                    }
                  else
                    {
                      for (levelID = 0; levelID < nlevel1; ++levelID)
                        {
                          if (cdoDebugExt)
                            cdo_print("RotuvNorth(): processing  level type: %d; level %d (out of [0:%d])", ltype1, levelID,
                                      nlevel1 - 1);
                          auto usvar = vardata[usvarID][levelID].data();
                          auto vsvar = vardata[vsvarID][levelID].data();
                          if (operatorID == ROTUVNORTH)
                            {
                              rot_uv_north(gridIDcurvl, usvar, vsvar);
                              // rot_uv_north(gridIDlastused, usvar, vsvar);
                              // transform "in-place" the uv from grid relative into north-pole related
                            }
                          else if (operatorID == PROJUVLATLON)
                            project_uv_latlon(gridIDcurvl, usvar, vsvar);
                          else if (operatorID == ROTUVN)
                            rot_uv_back_mode64(gridID, usvar, vsvar);
                        }
                    }

                  Debug(cdoDebugExt, "Finished processing level type: %d", ltype1);
                  break;
                }  // end  for varID2
          }        // end  for varID1

      for (int recID = 0; recID < nrecs; ++recID)
        {
          varID = recList[recID].varID;
          levelID = recList[recID].levelID;
          if (varID != -1)
            {
              code = vlistInqVarCode(vlistID1, varID);
              zaxisID = vlistInqVarZaxis(vlistID1, varID);
              ltype = zaxis_to_ltype(zaxisID);
              level = zaxisInqLevel(zaxisID, levelID);
              if (cdoDebugExt >= 10)
                cdo_print("Write modified data record: %05d (timestep:%d); Var.id [%4d]; (code=%3d; ltype=%3d; level=%4d; "
                          "levelID=%3d)",
                          recID, tsID, varID, code, ltype, level, levelID);

              cdo_def_record(streamID2, varID, levelID);
              cdo_write_record(streamID2, vardata[varID][levelID].data(), varnmiss[varID][levelID]);
            }
        }

      tsID++;
    }  // end of while (true)

  cdo_stream_close(streamID2);
  cdo_stream_close(streamID1);

  if (gridIDcurvl != -1) gridDestroy(gridIDcurvl);  // at the end must Free the allocated curvilinear grid definition...

  cdo_finish();

  return 0;
}

void *
WindTrans(void *process)
{
  cdo_initialize(process);

  // clang-format off
  UVDESTAG     = cdo_operator_add("uvDestag",     0, 0, nullptr);
  ROTUVNORTH   = cdo_operator_add("rotuvNorth",   0, 0, nullptr);
  ROTUVN       = cdo_operator_add("rotuvN",       0, 0, nullptr);
  PROJUVLATLON = cdo_operator_add("projuvLatLon", 0, 0, nullptr);  // Cylindrical Equidistant projection
  // clang-format on

  const int operatorID = cdo_operator_id();

  // will be calling: rot_uv_north(int gridID, double *us, double *vs);
  if (operatorID == ROTUVNORTH) return TransformUV(operatorID);

  // will be calling: rot_uv_back_mode64(int gridID, double *us, double *vs);
  if (operatorID == ROTUVN) return TransformUV(operatorID);

  // will be calling: project_uv_latlon(int gridID, double *us, double *vs)
  if (operatorID == PROJUVLATLON) return TransformUV(operatorID);

  if (operatorID == UVDESTAG) return DestaggerUV();

  cdo_abort("Unexpected operatorID %d", operatorID);

  return nullptr;
}
