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

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

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

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

/*
   This module contains the following operators:

      Showinfo   showparam       Show parameters
      Showinfo   showcode        Show code numbers
      Showinfo   showname        Show variable names
      Showinfo   showstdname     Show variable standard names
      Showinfo   showlevel       Show levels
      Showinfo   showyear        Show years
      Showinfo   showmon         Show months
      Showinfo   showdate        Show dates
      Showinfo   showtime        Show timesteps
      Showinfo   showltype       Show level types
      Showinfo   showformat      Show file format
*/

#include <cdi.h>

#include "process_int.h"
#include "Showattribute.h"
#include "printinfo.h"
#include "cdo_zaxis.h"

static void
show_year(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);
  const auto ntsteps = vlistNtsteps(vlistID);
  if (ntsteps == 0) return;

  constexpr int maxOut = 20;
  int nout = 0;
  int year0 = 0;

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

      const auto vdate = taxisInqVdate(taxisID);
      int year, month, day;
      cdiDecodeDate(vdate, &year, &month, &day);

      if (tsID == 0 || year0 != year)
        {
          nout++;
          year0 = year;
          fprintf(stdout, " %4d", year0);
          if ((nout % maxOut) == 0) fprintf(stdout, "\n");
        }

      tsID++;
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_mon(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);
  const auto ntsteps = vlistNtsteps(vlistID);
  if (ntsteps == 0) return;

  constexpr int maxOut = 36;
  int nout = 0;
  int month0 = 0;

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

      const auto vdate = taxisInqVdate(taxisID);
      int year, month, day;
      cdiDecodeDate(vdate, &year, &month, &day);

      if (tsID == 0 || month0 != month)
        {
          nout++;
          month0 = month;
          fprintf(stdout, " %2d", month0);
          if ((nout % maxOut) == 0) fprintf(stdout, "\n");
        }

      tsID++;
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_date(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);
  const auto ntsteps = vlistNtsteps(vlistID);
  if (ntsteps == 0) return;

  constexpr int maxOut = 12;
  int nout = 0;
  int64_t date0 = 0;

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

      const auto vdate = taxisInqVdate(taxisID);

      if (tsID == 0 || date0 != vdate)
        {
          nout++;
          date0 = vdate;
          fprintf(stdout, " %s", date_to_string(vdate).c_str());
          if ((nout % maxOut) == 0) fprintf(stdout, "\n");
        }

      tsID++;
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_time(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);
  const auto ntsteps = vlistNtsteps(vlistID);
  if (ntsteps == 0) return;

  constexpr int maxOut = 12;
  int nout = 0;

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

      const auto vtime = taxisInqVtime(taxisID);
      nout++;
      fprintf(stdout, " %s", time_to_string(vtime).c_str());
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");

      tsID++;
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_timestamp(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto taxisID = vlistInqTaxis(vlistID);
  const auto ntsteps = vlistNtsteps(vlistID);
  if (ntsteps == 0) return;

  constexpr int maxOut = 4;
  int nout = 0;

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

      const auto vdate = taxisInqVdate(taxisID);
      const auto vtime = taxisInqVtime(taxisID);
      nout++;
      fprintf(stdout, " %s", datetime_to_string(vdate, vtime).c_str());
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");

      tsID++;
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_code(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  constexpr int maxOut = 25;
  int nout = 0;

  for (int varID = 0; varID < nvars; varID++)
    {
      nout++;
      fprintf(stdout, " %d", vlistInqVarCode(vlistID, varID));
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_grid(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  fprintf(stdout, "# param nr | grid nr | z-axis nr:   /* Use in combination with operatores: griddes and zaxisdes */\n");
  for (int varID = 0; varID < nvars; varID++)
    {
      const auto gridID = vlistInqVarGrid(vlistID, varID);
      const auto zaxisID = vlistInqVarZaxis(vlistID, varID);

      fprintf(stdout, "      %3d     %3d      %3d\n", vlistInqVarCode(vlistID, varID), vlistGridIndex(vlistID, gridID) + 1,
              vlistZaxisIndex(vlistID, zaxisID) + 1);
    }
}

static void
show_unit(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  constexpr int maxOut = 10;
  int nout = 0;

  char varunits[CDI_MAX_NAME];
  for (int varID = 0; varID < nvars; varID++)
    {
      nout++;
      varunits[0] = 0;
      vlistInqVarUnits(vlistID, varID, varunits);
      if (strlen(varunits)) fprintf(stdout, " %s", varunits);
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_param(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  constexpr int maxOut = 10;
  int nout = 0;

  char paramstr[32];
  for (int varID = 0; varID < nvars; varID++)
    {
      nout++;
      const auto param = vlistInqVarParam(vlistID, varID);
      cdiParamToString(param, paramstr, sizeof(paramstr));

      fprintf(stdout, " %s", paramstr);
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_name(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  constexpr int maxOut = 10;
  int nout = 0;

  char varname[CDI_MAX_NAME];
  for (int varID = 0; varID < nvars; varID++)
    {
      nout++;
      vlistInqVarName(vlistID, varID, varname);
      fprintf(stdout, " %s", varname);
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_stdname(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  constexpr int maxOut = 1;
  int nout = 0;

  char stdname[CDI_MAX_NAME];
  for (int varID = 0; varID < nvars; varID++)
    {
      nout++;
      int length = CDI_MAX_NAME;
      cdiInqKeyString(vlistID, varID, CDI_KEY_STDNAME, stdname, &length);
      fprintf(stdout, " %s", stdname[0] != 0 ? stdname : "unknown");
      if ((nout % maxOut) == 0) fprintf(stdout, "\n");
    }
  if ((nout % maxOut) != 0) fprintf(stdout, "\n");
}

static void
show_level(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  for (int varID = 0; varID < nvars; varID++)
    {
      const auto zaxisID = vlistInqVarZaxis(vlistID, varID);
      const auto nlevs = zaxisInqSize(zaxisID);
      for (int levelID = 0; levelID < nlevs; levelID++) fprintf(stdout, " %.9g", cdo_zaxis_inq_level(zaxisID, levelID));
      fprintf(stdout, "\n");
    }
}

static void
show_ltype(CdoStreamID streamID)
{
  const auto vlistID = cdo_stream_inq_vlist(streamID);

  const auto nzaxis = vlistNzaxis(vlistID);
  for (int index = 0; index < nzaxis; index++)
    {
      const auto zaxisID = vlistZaxis(vlistID, index);
      const auto ltype = zaxis_to_ltype(zaxisID);

      if (ltype != -1) fprintf(stdout, " %d", ltype);
    }
  fprintf(stdout, "\n");
}

static void
show_atts(int vlistID)
{
  const auto nvars = vlistNvars(vlistID);

  char varname[CDI_MAX_NAME];
  for (int varID = 0; varID < nvars; varID++)
    {
      vlistInqVarName(vlistID, varID, varname);
      fprintf(stdout, "%s:\n", varname);

      int nattsvar;
      cdiInqNatts(vlistID, varID, &nattsvar);
      print_atts(vlistID, varID, nattsvar, nullptr);
    }
}

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

  // clang-format off
  const auto SHOWYEAR      = cdo_operator_add("showyear",      0, 0, nullptr);
  const auto SHOWMON       = cdo_operator_add("showmon",       0, 0, nullptr);
  const auto SHOWDATE      = cdo_operator_add("showdate",      0, 0, nullptr);
  const auto SHOWTIME      = cdo_operator_add("showtime",      0, 0, nullptr);
  const auto SHOWTIMESTAMP = cdo_operator_add("showtimestamp", 0, 0, nullptr);
  const auto SHOWCODE      = cdo_operator_add("showcode",      0, 0, nullptr);
  const auto SHOWUNIT      = cdo_operator_add("showunit",      0, 0, nullptr);
  const auto SHOWPARAM     = cdo_operator_add("showparam",     0, 0, nullptr);
  const auto SHOWNAME      = cdo_operator_add("showname",      0, 0, nullptr);
  const auto SHOWSTDNAME   = cdo_operator_add("showstdname",   0, 0, nullptr);
  const auto SHOWLEVEL     = cdo_operator_add("showlevel",     0, 0, nullptr);
  const auto SHOWLTYPE     = cdo_operator_add("showltype",     0, 0, nullptr);
  const auto SHOWFORMAT    = cdo_operator_add("showformat",    0, 0, nullptr);
  const auto SHOWGRID      = cdo_operator_add("showgrid",      0, 0, nullptr); 
  const auto SHOWATTS      = cdo_operator_add("showatts",      0, 0, nullptr);
  const auto SHOWATTSGLOB  = cdo_operator_add("showattsglob",  0, 0, nullptr);

  const auto operatorID = cdo_operator_id();

  operator_check_argc(0);

  const auto streamID = cdo_open_read(0);

  if      (operatorID == SHOWYEAR)      show_year(streamID);
  else if (operatorID == SHOWMON)       show_mon(streamID);
  else if (operatorID == SHOWDATE)      show_date(streamID);
  else if (operatorID == SHOWTIME)      show_time(streamID);
  else if (operatorID == SHOWTIMESTAMP) show_timestamp(streamID);
  else if (operatorID == SHOWCODE)      show_code(streamID);
  else if (operatorID == SHOWGRID)      show_grid(streamID);
  else if (operatorID == SHOWUNIT)      show_unit(streamID);
  else if (operatorID == SHOWPARAM)     show_param(streamID);
  else if (operatorID == SHOWNAME)      show_name(streamID);
  else if (operatorID == SHOWSTDNAME)   show_stdname(streamID);
  else if (operatorID == SHOWLEVEL)     show_level(streamID);
  else if (operatorID == SHOWLTYPE)     show_ltype(streamID);
  else if (operatorID == SHOWFORMAT)
    {
      const auto vlistID = cdo_stream_inq_vlist(streamID);
      print_filetype(streamID, vlistID);
    }
  else if (operatorID == SHOWATTS || operatorID == SHOWATTSGLOB)
    {
      const auto vlistID = cdo_stream_inq_vlist(streamID);
      if (operatorID == SHOWATTS) show_atts(vlistID);

      fprintf(stdout, "Global:\n");
      int natts;
      cdiInqNatts(vlistID, CDI_GLOBAL, &natts);
      print_atts(vlistID, CDI_GLOBAL, natts, nullptr);
    }
  // clang-format on

  cdo_stream_close(streamID);

  cdo_finish();

  return nullptr;
}
