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

  Copyright (C) 2003-2020 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.
*/

/*
   This module contains the following operators:

   Output field with grid cell center or cell bounds for plotting with GMT

    - outputcenter
    - outputbounds
    - outputboundscpt
    - outputvector
*/

#ifdef HAVE_CONFIG_H
#include "config.h" /* VERSION */
#endif

#include <cdi.h>

#include "array.h"
#include "cdo_options.h"
#include "process_int.h"
#include "param_conversion.h"
#include <mpim_grid.h>
#include "gridreference.h"
#include "color.h"
#include "printinfo.h"
#include "interpol.h"
#include "cdo_zaxis.h"

static int
check_ncorner(int ncorner, const double *lon_bounds, const double *lat_bounds)
{
  auto ncorner_new = ncorner;
  int k;

  for (k = ncorner - 1; k > 0; --k)
    if (IS_NOT_EQUAL(lon_bounds[k], lon_bounds[k - 1]) || IS_NOT_EQUAL(lat_bounds[k], lat_bounds[k - 1])) break;

  if (k < ncorner - 1) ncorner_new = k + 1;

  return ncorner_new;
}

static void
make_cyclic(double *array1, double *array2, int nlon, int nlat)
{
  int ij1, ij2;

  for (int j = 0; j < nlat; ++j)
    for (int i = 0; i < nlon; ++i)
      {
        ij1 = j * nlon + i;
        ij2 = j * (nlon + 1) + i;
        array2[ij2] = array1[ij1];
      }

  for (int j = 0; j < nlat; ++j)
    {
      ij2 = j * (nlon + 1);
      array2[ij2 + nlon] = array2[ij2];
    }
}

static void
output_zon(double levmin, double levmax, const double *cell_corner_lat)
{
  double xlev[4], xlat[4];
  auto latmin = cell_corner_lat[0];
  auto latmax = cell_corner_lat[0];
  for (int ic = 1; ic < 4; ic++)
    {
      if (cell_corner_lat[ic] < latmin) latmin = cell_corner_lat[ic];
      if (cell_corner_lat[ic] > latmax) latmax = cell_corner_lat[ic];
    }
  xlev[0] = levmin;
  xlev[1] = levmax;
  xlev[2] = levmax;
  xlev[3] = levmin;
  xlat[0] = latmin;
  xlat[1] = latmin;
  xlat[2] = latmax;
  xlat[3] = latmax;
  for (int ic = 0; ic < 4; ic++) fprintf(stdout, "   %g  %g\n", xlat[ic], xlev[ic]);
  fprintf(stdout, "   %g  %g\n", xlat[0], xlev[0]);
}

static void
output_mer(double levmin, double levmax, const double *cell_corner_lon)
{
  double xlev[4], xlon[4];
  auto lonmin = cell_corner_lon[0];
  auto lonmax = cell_corner_lon[0];
  for (int ic = 1; ic < 4; ic++)
    {
      if (cell_corner_lon[ic] < lonmin) lonmin = cell_corner_lon[ic];
      if (cell_corner_lon[ic] > lonmax) lonmax = cell_corner_lon[ic];
    }
  xlev[0] = levmin;
  xlev[1] = levmin;
  xlev[2] = levmax;
  xlev[3] = levmax;
  xlon[0] = lonmin;
  xlon[1] = lonmax;
  xlon[2] = lonmax;
  xlon[3] = lonmin;
  for (int ic = 0; ic < 4; ic++) fprintf(stdout, "   %g  %g\n", xlon[ic], xlev[ic]);
  fprintf(stdout, "   %g  %g\n", xlon[0], xlev[0]);
}

static void
output_boundscpt(double value, double missval, const CPT &cpt)
{
  int r = 0, g = 0, b = 0, n;

  if (!DBL_IS_EQUAL(value, missval))
    {
      for (n = 0; n < cpt.ncolors; n++)
        if (value > cpt.lut[n].z_low && value <= cpt.lut[n].z_high) break;

      if (n == cpt.ncolors)
        {
          r = cpt.bfn[0].rgb[0];
          g = cpt.bfn[0].rgb[1];
          b = cpt.bfn[0].rgb[2];
        }
      else
        {
          r = cpt.lut[n].rgb_high[0];
          g = cpt.lut[n].rgb_high[1];
          b = cpt.lut[n].rgb_high[2];
        }
    }
  else
    {
      r = cpt.bfn[2].rgb[0];
      g = cpt.bfn[2].rgb[1];
      b = cpt.bfn[2].rgb[2];
    }

  fprintf(stdout, " -G%d/%d/%d", r, g, b);
}
                 
static void
output_vrml(int nlon, int nlat, int ngp, const Varray<double> &array, double missval, const CPT &cpt)
{
  const auto mm = varrayMinMaxMV(ngp, array, missval);

  double dx = 10. / nlon;

  printf("Viewpoint {\n");
  printf("  description \"viewpoint1\"\n");
  printf("  orientation 0 0 1 0\n");
  printf("  position 0.0 0.0 10.0\n");
  printf("}\n");
  printf("\n");
  printf("Background {\n");
  printf("  skyColor [\n");
  printf("    0.0 0.1 0.8,\n");
  printf("    0.0 0.5 1.0,\n");
  printf("    1.0 1.0 1.0\n");
  printf("  ]\n");
  printf("  skyAngle [0.785, 1.571]\n");
  printf("\n");
  printf("  groundColor [\n");
  printf("    0.0 0.0 0.0,\n");
  printf("    0.3 0.3 0.3,\n");
  printf("    0.5 0.5 0.5\n");
  printf("  ]\n");
  printf("  groundAngle [0.785, 1.571]\n");
  printf("}\n");
  printf("\n");
  printf("Transform {\n");
  printf("  children [\n");
  printf("    Shape {\n");
  printf("      appearance Appearance {\n");
  printf("        material Material {}\n");
  printf("      }\n");
  printf("      geometry ElevationGrid {\n");
  printf("        colorPerVertex true\n");
  printf("        solid false\n");
  printf("        xDimension %d\n", nlon);
  printf("        zDimension %d\n", nlat);
  printf("        xSpacing %g\n", dx);
  printf("        zSpacing %g\n", dx);
  printf("        color Color {\n");
  printf("          color [\n");
  for (int j = nlat - 1; j >= 0; --j)
    for (int i = 0; i < nlon; ++i)
      {
        int r = 0, g = 0, b = 0;
        const auto val = array[j * nlon + i];

        if (!DBL_IS_EQUAL(val, missval))
          {
            int n;
            for (n = 0; n < cpt.ncolors; n++)
              if (val > cpt.lut[n].z_low && val <= cpt.lut[n].z_high) break;

            if (n == cpt.ncolors)
              {
                r = cpt.bfn[0].rgb[0];
                g = cpt.bfn[0].rgb[1];
                b = cpt.bfn[0].rgb[2];
              }
            else
              {
                //  r = cpt.lut[n].rgb_high[0];  g = cpt.lut[n].rgb_high[1];
                //  b = cpt.lut[n].rgb_high[2];
                r = intlin(val, cpt.lut[n].rgb_low[0], cpt.lut[n].z_low, cpt.lut[n].rgb_high[0], cpt.lut[n].z_high);
                g = intlin(val, cpt.lut[n].rgb_low[1], cpt.lut[n].z_low, cpt.lut[n].rgb_high[1], cpt.lut[n].z_high);
                b = intlin(val, cpt.lut[n].rgb_low[2], cpt.lut[n].z_low, cpt.lut[n].rgb_high[2], cpt.lut[n].z_high);
              }
          }
        else
          {
            r = cpt.bfn[2].rgb[0];
            g = cpt.bfn[2].rgb[1];
            b = cpt.bfn[2].rgb[2];
          }
        printf(" %.3g %.3g %.3g,\n", r / 255., g / 255., b / 255.);
      }
  printf("          ]\n");
  printf("        }\n");
  printf("        height [\n");

  for (int j = nlat - 1; j >= 0; --j)
    for (int i = 0; i < nlon; ++i) printf("%g,\n", array[j * nlon + i]);

  printf("        ]\n");
  printf("      }\n");
  printf("    }\n");
  printf("  ]\n");
  printf("  translation -5 0 %g\n", -5. * nlat / nlon);
  printf("  rotation 0.0 0.0 0.0 0.0\n");
  printf("  scale 1.0 %g 1.0\n", 0.5 / (mm.max - mm.min));
  printf("}\n");
}

void *
Outputgmt(void *process)
{
  int varID0;
  size_t gridsize2 = 0;
  int nrecs;
  int levelID;
  size_t nmiss;
  int ninc = 1;
  bool lzon = false, lmer = false, lhov = false;
  bool luse_grid_corner = false;
  char varname[CDI_MAX_NAME];
  Varray<double> grid_center_lat2, grid_center_lon2;
  Varray<double> grid_corner_lat, grid_corner_lon;
  std::vector<int> grid_mask;
  CPT cpt;

  cdoInitialize(process);

  // clang-format off
  const auto OUTPUTCENTER    = cdoOperatorAdd("gmtxyz",          0, 0, nullptr);
  const auto OUTPUTCENTER2   = cdoOperatorAdd("outputcenter2",   0, 0, nullptr);
  const auto OUTPUTCENTERCPT = cdoOperatorAdd("outputcentercpt", 0, 0, nullptr);
  const auto OUTPUTBOUNDS    = cdoOperatorAdd("gmtcells",        0, 0, nullptr);
  const auto OUTPUTBOUNDSCPT = cdoOperatorAdd("outputboundscpt", 0, 0, nullptr);
  const auto OUTPUTVECTOR    = cdoOperatorAdd("outputvector",    0, 0, nullptr);
  const auto OUTPUTTRI       = cdoOperatorAdd("outputtri",       0, 0, nullptr);
  const auto OUTPUTVRML      = cdoOperatorAdd("outputvrml",      0, 0, nullptr);
  // clang-format on

  const auto operatorID = cdoOperatorID();

  if (operatorID == OUTPUTVECTOR)
    {
      operatorInputArg("increment");
      operatorCheckArgc(1);
      ninc = parameter2int(cdoOperatorArgv(0));
      if (ninc < 1) cdoAbort("Increment must be greater than 0!");
    }

  if (operatorID == OUTPUTBOUNDS || operatorID == OUTPUTBOUNDSCPT) luse_grid_corner = true;

  if (operatorID == OUTPUTCENTERCPT || operatorID == OUTPUTBOUNDSCPT || operatorID == OUTPUTVRML)
    {
      operatorCheckArgc(1);
      auto cpt_file = cdoOperatorArgv(0).c_str();

      auto cpt_fp = fopen(cpt_file, "r");
      if (cpt_fp == nullptr) cdoAbort("Open failed on color palette table %s", cpt_file);

      const auto status = cptRead(cpt_fp, &cpt);
      if (status != 0) cdoAbort("Error during read of color palette table %s", cpt_file);

      if (Options::cdoVerbose) cptWrite(stderr, cpt);
    }

  const auto streamID = cdoOpenRead(0);

  const auto vlistID = cdoStreamInqVlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);

  int varID = 0;
  vlistInqVarName(vlistID, varID, varname);
  const auto code = vlistInqVarCode(vlistID, varID);
  auto gridID = vlistInqVarGrid(vlistID, varID);
  const auto zaxisID = vlistInqVarZaxis(vlistID, varID);
  const auto missval = vlistInqVarMissval(vlistID, varID);

  const auto gridsize = gridInqSize(gridID);

  int gridtype = gridInqType(gridID);
  if (gridtype == GRID_GME || gridtype == GRID_GAUSSIAN_REDUCED)
    {
      gridID = gridToUnstructured(gridID, 1);
      gridtype = gridInqType(gridID);
    }

  if (gridtype != GRID_UNSTRUCTURED && gridtype != GRID_CURVILINEAR)
    {
      gridID = gridToCurvilinear(gridID, 1);
      gridtype = gridInqType(gridID);
    }

  if (gridtype == GRID_UNSTRUCTURED && !gridHasCoordinates(gridID))
    {
      const auto reference = dereferenceGrid(gridID);
      if (reference.isValid) gridID = reference.gridID;
      if (reference.notFound) cdoAbort("Reference to source grid not found!");
    }

  if (!gridHasCoordinates(gridID)) cdoAbort("Cell center coordinates missing!");

  int nlon = gridInqXsize(gridID);
  int nlat = gridInqYsize(gridID);
  int nlev = zaxisInqSize(zaxisID);

  if (gridInqMaskGME(gridID, nullptr))
    {
      grid_mask.resize(gridsize);
      gridInqMaskGME(gridID, grid_mask.data());
    }

  if (gridInqType(gridID) != GRID_UNSTRUCTURED)
    {
      if (nlon == 1 && nlat > 1 && nlev == 1) lhov = true;
      if (nlon == 1 && nlat > 1 && nlev > 1) lzon = true;
      if (nlon > 1 && nlat == 1 && nlev > 1) lmer = true;
    }
  else
    {
      nlat = 1;
    }

  if (Options::cdoVerbose && lhov) cdoPrint("Process hovmoeller data");
  if (Options::cdoVerbose && lzon) cdoPrint("Process zonal data");
  if (Options::cdoVerbose && lmer) cdoPrint("Process meridional data");
  /*
  if ( lzon || lmer )
    {
      if ( operatorID == OUTPUTBOUNDS || operatorID == OUTPUTBOUNDSCPT )
        cdoAbort("Bounds not available for zonal/meridional data!");
    }
  */
  if (lhov)
    {
      if (operatorID == OUTPUTBOUNDS || operatorID == OUTPUTBOUNDSCPT) cdoAbort("Bounds not available hovmoeller data!");
    }

  const int ncorner = (gridInqType(gridID) == GRID_UNSTRUCTURED) ? gridInqNvertex(gridID) : 4;

  const bool grid_is_circular = gridIsCircular(gridID);

  Varray<double> grid_center_lat(gridsize), grid_center_lon(gridsize);
  gridInqYvals(gridID, grid_center_lat.data());
  gridInqXvals(gridID, grid_center_lon.data());

  // Convert lat/lon units if required
  cdo_grid_to_degree(gridID, CDI_XAXIS, gridsize, grid_center_lon.data(), "grid center lon");
  cdo_grid_to_degree(gridID, CDI_YAXIS, gridsize, grid_center_lat.data(), "grid center lat");

  int nvals = gridsize;
  double *plon = grid_center_lon.data();
  double *plat = grid_center_lat.data();

  if (operatorID == OUTPUTCENTER2 && grid_is_circular)
    {
      gridsize2 = nlat * (nlon + 1);

      grid_center_lat2.resize(gridsize2);
      grid_center_lon2.resize(gridsize2);

      make_cyclic(grid_center_lat.data(), grid_center_lat2.data(), nlon, nlat);
      make_cyclic(grid_center_lon.data(), grid_center_lon2.data(), nlon, nlat);

      for (int j = 0; j < nlat; ++j)
        {
          const int ij2 = j * (nlon + 1);
          grid_center_lon2[ij2 + nlon] += 360;
        }

      nvals = gridsize2;
      plon = grid_center_lon2.data();
      plat = grid_center_lat2.data();
    }

  Varray<double> zaxis_center_lev(nlev), zaxis_lower_lev(nlev), zaxis_upper_lev(nlev);

  cdoZaxisInqLevels(zaxisID, zaxis_center_lev.data());

  if (luse_grid_corner)
    {
      if (ncorner == 0) cdoAbort("Number of cell corners undefined!");
      const size_t nalloc = ncorner * gridsize;
      grid_corner_lat.resize(nalloc);
      grid_corner_lon.resize(nalloc);

      if (!gridHasBounds(gridID)) cdoAbort("Cell corner coordinates missing!");

      gridInqYbounds(gridID, grid_corner_lat.data());
      gridInqXbounds(gridID, grid_corner_lon.data());

      cdo_grid_to_degree(gridID, CDI_XAXIS, ncorner * gridsize, grid_corner_lon.data(), "grid corner lon");
      cdo_grid_to_degree(gridID, CDI_YAXIS, ncorner * gridsize, grid_corner_lat.data(), "grid corner lat");

      if (zaxisInqLbounds(zaxisID, nullptr) && zaxisInqUbounds(zaxisID, nullptr))
        {
          zaxisInqLbounds(zaxisID, zaxis_lower_lev.data());
          zaxisInqUbounds(zaxisID, zaxis_upper_lev.data());
        }
      else
        {
          zaxis_lower_lev[0] = zaxis_center_lev[0];
          for (int i = 1; i < nlev; ++i) zaxis_lower_lev[i] = 0.5 * (zaxis_center_lev[i] + zaxis_center_lev[i - 1]);

          zaxis_upper_lev[nlev - 1] = zaxis_center_lev[nlev - 1];
          for (int i = 0; i < nlev - 1; ++i) zaxis_upper_lev[i] = zaxis_lower_lev[i + 1];

          if (Options::cdoVerbose)
            for (int i = 0; i < nlev; ++i)
              fprintf(stderr, "level: %d %g %g %g\n", i + 1, zaxis_lower_lev[i], zaxis_center_lev[i], zaxis_upper_lev[i]);
        }
    }

  Varray<double> array(gridsize);
  double *parray = array.data();

  Varray<double> array2;
  if (operatorID == OUTPUTCENTER2 && grid_is_circular)
    {
      array2.resize(nlat * (nlon + 1));
      parray = array2.data();
    }

  Varray<double> uf, vf, alpha, auv;
  if (operatorID == OUTPUTVECTOR)
    {
      uf.resize(gridsize);
      vf.resize(gridsize);
      alpha.resize(gridsize);
      auv.resize(gridsize);
    }

  int tsID = 0;
  while ((nrecs = cdoStreamInqTimestep(streamID, tsID)))
    {
      const auto vdate = taxisInqVdate(taxisID);
      const auto vtime = taxisInqVtime(taxisID);
      const auto vdateString = dateToString(vdate);
      const auto vtimeString = timeToString(vtime);

      if (tsID == 0 && operatorID != OUTPUTTRI)
        {
          if (operatorID == OUTPUTVRML) printf("#VRML V2.0 utf8\n\n");
#if defined(VERSION)
          fprintf(stdout, "# Generated by CDO version %s\n", VERSION);
          fprintf(stdout, "#\n");
#endif
          fprintf(stdout, "# Operator = %s\n", cdoOperatorName(operatorID));
          if (lhov)
            fprintf(stdout, "# Mode     = hovmoeller\n");
          else if (lzon)
            fprintf(stdout, "# Mode     = zonal\n");
          else if (lmer)
            fprintf(stdout, "# Mode     = meridional\n");
          else
            fprintf(stdout, "# Mode     = horizonal\n");

          if (operatorID == OUTPUTVECTOR) fprintf(stdout, "# Increment = %d\n", ninc);
          fprintf(stdout, "#\n");
          fprintf(stdout, "# Stream = %s\n", cdoGetStreamName(0));
          fprintf(stdout, "# Date   = %s\n", vdateString.c_str());
          fprintf(stdout, "# Time   = %s\n", vtimeString.c_str());
          fprintf(stdout, "# Name   = %s\n", varname);
          fprintf(stdout, "# Code   = %d\n", code);
        }

      varID0 = varID;

      for (int recID = 0; recID < nrecs; recID++)
        {
          cdoInqRecord(streamID, &varID, &levelID);

          if (varID != varID0) continue;
          if (recID > 0 && !lzon && !lmer) continue;

          cdoReadRecord(streamID, array.data(), &nmiss);

          if (operatorID == OUTPUTCENTER2 && grid_is_circular) make_cyclic(array.data(), array2.data(), nlon, nlat);

          const double level = zaxis_center_lev[levelID];

          if ((tsID == 0 || lzon || lmer) && operatorID != OUTPUTTRI) fprintf(stdout, "# Level  = %g\n", level);
          if (lhov) fprintf(stdout, "# Timestep = %d\n", tsID + 1);

          if (operatorID != OUTPUTTRI) fprintf(stdout, "#\n");

          if (operatorID == OUTPUTCENTER || operatorID == OUTPUTCENTER2 || operatorID == OUTPUTCENTERCPT)
            {
              if (Options::cdoVerbose)
                {
                  const auto mm = varrayMinMaxMV(gridsize, array, missval);
                  const auto range = mm.max - mm.min;
                  fprintf(stderr, "makecpt -T%g/%g/%g -Crainbow > gmt.cpt\n", mm.min, mm.max, range / 20);
                  fprintf(stderr, "pscontour -K -JQ0/10i -Rd -I -Cgmt.cpt data.gmt > gmtplot.ps\n");
                  fprintf(stderr, "pscoast -O -J -R -Dc -W -B40g20 >> gmtplot.ps\n");
                }

              for (int i = 0; i < nvals; i++)
                {
                  if (grid_mask.size() && grid_mask[i] == 0) continue;

                  if (operatorID == OUTPUTCENTER)
                    {
                      if (lzon)
                        fprintf(stdout, " %g  %g  %g\n", grid_center_lat[i], level, array[i]);
                      else if (lmer)
                        fprintf(stdout, " %g  %g  %g\n", grid_center_lon[i], level, array[i]);
                      else if (lhov)
                        fprintf(stdout, " %d  %g  %g\n", tsID + 1, grid_center_lat[i], array[i]);
                      else
                        fprintf(stdout, " %g  %g  %g\n", grid_center_lon[i], grid_center_lat[i], array[i]);
                    }
                  else if (operatorID == OUTPUTCENTER2)
                    {
                      fprintf(stdout, " %g  %g  %g\n", plon[i], plat[i], parray[i]);
                    }
                  else
                    {
                      if (lzon)
                        fprintf(stdout, " %g  %g  %g\n", grid_center_lat[i], level, array[i]);
                      else if (lmer)
                        fprintf(stdout, " %g  %g  %g\n", grid_center_lon[i], level, array[i]);
                      else
                        fprintf(stdout, " %g  %g  %g  %g\n", grid_center_lon[i], grid_center_lat[i], array[i], array[i]);
                    }
                }
              fprintf(stdout, "#\n");
            }
          else if (operatorID == OUTPUTTRI)
            {
              if (gridInqType(gridID) != GRID_CURVILINEAR) cdoAbort("Unsupported grid!");

              const int mlon = nlon - 1;
              // if ( gridIsCircular(gridID) ) mlon = nlon;
              for (int j = 0; j < nlat - 1; ++j)
                for (int i = 0; i < mlon; ++i)
                  {
                    int ip1 = i + 1;
                    if (i == nlon - 1) ip1 = 0;
                    int c1 = (j) *nlon + ip1;
                    int c2 = (j) *nlon + i;
                    int c3 = (j + 1) * nlon + i;
                    fprintf(stdout, "%d   %d   %d\n", c1, c2, c3);
                    c1 = (j) *nlon + i + 1;
                    c2 = (j + 1) * nlon + i;
                    c3 = (j + 1) * nlon + ip1;
                    fprintf(stdout, "%d   %d   %d\n", c1, c2, c3);
                  }
            }
          else if (operatorID == OUTPUTVECTOR)
            {
              if (nrecs < 2) cdoAbort("Too few fields!");

              varrayCopy(gridsize, array, uf);
              cdoInqRecord(streamID, &varID, &levelID);
              cdoReadRecord(streamID, vf.data(), &nmiss);

              for (int j = 0; j < nlat; j += ninc)
                for (int i = 0; i < nlon; i += ninc)
                  {
                    // compute length of velocity vector
                    auv[IX2D(j, i, nlon)]
                        = std::sqrt(uf[IX2D(j, i, nlon)] * uf[IX2D(j, i, nlon)] + vf[IX2D(j, i, nlon)] * vf[IX2D(j, i, nlon)]);

                    alpha[IX2D(j, i, nlon)] = atan2(vf[IX2D(j, i, nlon)], uf[IX2D(j, i, nlon)]);
                    alpha[IX2D(j, i, nlon)] = 90. - alpha[IX2D(j, i, nlon)] * RAD2DEG;

                    if (alpha[IX2D(j, i, nlon)] < 0) alpha[IX2D(j, i, nlon)] += 360;
                    if (alpha[IX2D(j, i, nlon)] > 360) alpha[IX2D(j, i, nlon)] -= 360;

                    if (std::fabs(auv[IX2D(j, i, nlon)]) > 0)
                      fprintf(stdout, " %g  %g  %g  %g\n", grid_center_lon[IX2D(j, i, nlon)], grid_center_lat[IX2D(j, i, nlon)],
                              alpha[IX2D(j, i, nlon)], auv[IX2D(j, i, nlon)]);
                  }

              fprintf(stdout, "#\n");
              break;
            }
          else if (operatorID == OUTPUTVRML)
            {
              output_vrml(nlon, nlat, gridsize, array, missval, cpt);
            }
          else if (operatorID == OUTPUTBOUNDS || operatorID == OUTPUTBOUNDSCPT)
            {
              if (Options::cdoVerbose)
                {
                  const auto mm = varrayMinMaxMV(gridsize, array, missval);
                  const auto range = mm.max - mm.min;
                  fprintf(stderr, "makecpt -T%g/%g/%g -Crainbow > gmt.cpt\n", mm.min, mm.max, range / 20);
                  fprintf(stderr, "psxy -K -JQ0/10i -Rd -L -Cgmt.cpt -m data.gmt > gmtplot.ps\n");
                  // fprintf(stderr, "psxy -K -Jx0.028id -Rd -L -Cgmt.cpt -m
                  // data.gmt > gmtplot.ps\n"); fprintf(stderr, "psxy -K
                  // -JN0/10i -Rd -L -Cgmt.cpt -m data.gmt > gmtplot.ps\n");
                  fprintf(stderr, "pscoast -O -J -R -Dc -W -B40g20 >> gmtplot.ps\n");
                  fprintf(stderr, "ps2pdf gmtplot.ps\n");
                }

              for (size_t i = 0; i < gridsize; i++)
                {
                  if (grid_mask.size() && grid_mask[i] == 0) continue;

                  if (!DBL_IS_EQUAL(array[i], missval))
                    fprintf(stdout, "> -Z%g", array[i]);
                  else
                    fprintf(stdout, "> -ZNaN");

                  if (operatorID == OUTPUTBOUNDSCPT)
                    {
                      output_boundscpt(array[i], missval, cpt);
                    }

                  fprintf(stdout, "\n");

                  if (lzon)
                    {
                      output_zon(zaxis_lower_lev[levelID], zaxis_upper_lev[levelID], &grid_corner_lat[i * 4]);
                    }
                  else if (lmer)
                    {
                      output_mer(zaxis_lower_lev[levelID], zaxis_upper_lev[levelID], &grid_corner_lon[i * 4]);
                    }
                  else if (lhov)
                    {
                      cdoAbort("Implementation for hovmoeller data missing!");
                    }
                  else
                    {
                      const double *lon_bounds = grid_corner_lon.data() + i * ncorner;
                      const double *lat_bounds = grid_corner_lat.data() + i * ncorner;
                      const int ncorner_new = check_ncorner(ncorner, lon_bounds, lat_bounds);

                      for (int ic = 0; ic < ncorner_new; ic++) fprintf(stdout, "   %g  %g\n", lon_bounds[ic], lat_bounds[ic]);
                      fprintf(stdout, "   %g  %g\n", lon_bounds[0], lat_bounds[0]);
                    }
                }
              fprintf(stdout, "\n");
            }
        }

      if (!lhov) break;

      tsID++;
    }

  cdoStreamClose(streamID);

  cdoFinish();

  return nullptr;
}
