/*
  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:

      Sinfo      sinfo           Short dataset information
*/

#include <cdi.h>

#include <cstring>
#include "cdo_options.h"
#include "cdo_cdi_wrapper.h"
#include "printinfo.h"
#include "mpmo_color.h"
#include "process_int.h"
#include "compare.h"
#include "util_string.h"

const char *steptypeName(const int tsteptype);

static void
limitStringLength(char *string, size_t maxlen)
{
  string[maxlen - 1] = 0;
  const auto len = strlen(string);
  if (len > 10)
    {
      for (size_t i = 3; i < len; ++i)
        if (string[i] == ' ' || string[i] == ',' || (i > 10 && string[i] == '.'))
          {
            string[i] = 0;
            break;
          }
    }
}

enum
{
  func_generic,
  func_param,
  func_name,
  func_code
};

static void
addOperators(void)
{
  // clang-format off
  cdo_operator_add("sinfo",   func_generic, 0, nullptr);
  cdo_operator_add("sinfop",  func_param,   0, nullptr);
  cdo_operator_add("sinfon",  func_name,    0, nullptr);
  cdo_operator_add("sinfoc",  func_code,    0, nullptr);
  cdo_operator_add("seinfo",  func_generic, 1, nullptr);
  cdo_operator_add("seinfop", func_param,   1, nullptr);
  cdo_operator_add("seinfon", func_name,    1, nullptr);
  cdo_operator_add("seinfoc", func_code,    1, nullptr);
  // clang-format on
}

void *
Sinfo(void *process)
{
  char tmpname[CDI_MAX_NAME];
  char paramstr[32];

  cdo_initialize(process);

  addOperators();

  const auto operatorID = cdo_operator_id();

  const auto operfunc = cdo_operator_f1(operatorID);
  const auto lensemble = cdo_operator_f2(operatorID);

  operator_check_argc(0);

  for (int indf = 0; indf < cdo_stream_cnt(); indf++)
    {
      const auto streamID = cdo_open_read(indf);
      const auto vlistID = cdo_stream_inq_vlist(streamID);

      VarList varList;
      varListInit(varList, vlistID);

      set_text_color(stdout, BRIGHT);
      fprintf(stdout, "   File format");
      reset_text_color(stdout);
      fprintf(stdout, " : ");
      print_filetype(streamID, vlistID);

      const auto nvars = vlistNvars(vlistID);
      const auto nsubtypes = vlistNsubtypes(vlistID);

      set_text_color(stdout, BRIGHT);
      fprintf(stdout, "%6d : Institut Source   T Steptype", -(indf + 1));
      if (lensemble) fprintf(stdout, " Einfo");
      if (nsubtypes > 1) fprintf(stdout, " Subtypes");
      fprintf(stdout, " Levels Num    Points Num Dtype : ");
      fprintf(stdout, "%s", (operfunc == func_name) ? "Parameter name" : ((operfunc == func_code) ? "Table Code" : "Parameter ID"));

      if (Options::cdoVerbose) fprintf(stdout, " : Extra");
      reset_text_color(stdout);
      fprintf(stdout, "\n");

      for (int varID = 0; varID < nvars; varID++)
        {
          const auto tabnum = tableInqNum(vlistInqVarTable(vlistID, varID));

          fprintf(stdout, "%6d", varID + 1);
          fprintf(stdout, " : ");

          set_text_color(stdout, BLUE);
          // institute info
          auto instptr = institutInqNamePtr(vlistInqVarInstitut(vlistID, varID));
          strcpy(tmpname, "unknown");
          if (instptr) strncpy(tmpname, instptr, CDI_MAX_NAME - 1);
          limitStringLength(tmpname, 32);
          fprintf(stdout, "%-8s ", tmpname);

          // source info
          auto modelptr = modelInqNamePtr(vlistInqVarModel(vlistID, varID));
          strcpy(tmpname, "unknown");
          if (modelptr) strncpy(tmpname, modelptr, CDI_MAX_NAME - 1);
          limitStringLength(tmpname, 32);
          fprintf(stdout, "%-8s ", tmpname);

          // timetype
          fprintf(stdout, "%c ", varList[varID].timetype == TIME_CONSTANT ? 'c' : 'v');

          // tsteptype
          fprintf(stdout, "%-8s ", steptypeName(varList[varID].tsteptype));

          // ensemble information
          if (lensemble)
            {
              int perturbationNumber, numberOfForecastsInEnsemble;
              const auto r1 = cdiInqKeyInt(vlistID, varID, CDI_KEY_PERTURBATIONNUMBER, &perturbationNumber);
              const auto r2 = cdiInqKeyInt(vlistID, varID, CDI_KEY_NUMBEROFFORECASTSINENSEMBLE, &numberOfForecastsInEnsemble);
              if (r1 == 0 && r2 == 0)
                fprintf(stdout, "%2d/%-2d ", perturbationNumber, numberOfForecastsInEnsemble);
              else
                fprintf(stdout, "--/-- ");
            }

          if (nsubtypes > 1)
            {
              const auto subtypeID = vlistInqVarSubtype(vlistID, varID);
              const auto subtypesize = subtypeInqSize(subtypeID);
              fprintf(stdout, " %6d  ", subtypesize);
              fprintf(stdout, "%3d ", vlistSubtypeIndex(vlistID, subtypeID) + 1);
            }
          reset_text_color(stdout);

          // layer info
          set_text_color(stdout, GREEN);
          fprintf(stdout, "%6d ", varList[varID].nlevels);
          reset_text_color(stdout);
          fprintf(stdout, "%3d ", vlistZaxisIndex(vlistID, varList[varID].zaxisID) + 1);

          // grid info
          set_text_color(stdout, GREEN);
          fprintf(stdout, "%9zu ", varList[varID].gridsize);
          reset_text_color(stdout);
          fprintf(stdout, "%3d ", vlistGridIndex(vlistID, varList[varID].gridID) + 1);

          // datatype
          const auto datatypestr = cdi_datatype_to_str(vlistInqVarDatatype(vlistID, varID));
          set_text_color(stdout, BLUE);
          fprintf(stdout, " %-3s", datatypestr);

          const auto comptype = vlistInqVarCompType(vlistID, varID);
          if (comptype == CDI_COMPRESS_NONE)
            fprintf(stdout, "  ");
          else
            fprintf(stdout, "%c ", (int) comptype_to_name(comptype)[0]);

          reset_text_color(stdout);

          fprintf(stdout, ": ");

          // parameter info
          cdiParamToString(varList[varID].param, paramstr, sizeof(paramstr));

          // set_text_color(stdout, GREEN);
          if (operfunc == func_name)
            fprintf(stdout, "%-14s", varList[varID].name);
          else if (operfunc == func_code)
            fprintf(stdout, "%4d %4d   ", tabnum, varList[varID].code);
          else
            fprintf(stdout, "%-14s", paramstr);
          // reset_text_color(stdout);

          if (Options::cdoVerbose)
            {
              char varextra[CDI_MAX_NAME];
              vlistInqVarExtra(vlistID, varID, varextra);
              fprintf(stdout, " : %s", varextra);
            }

          fprintf(stdout, "\n");
        }

      set_text_color(stdout, BRIGHT);
      fprintf(stdout, "   Grid coordinates");
      reset_text_color(stdout);
      fprintf(stdout, " :\n");

      print_grid_info(vlistID);

      set_text_color(stdout, BRIGHT);
      fprintf(stdout, "   Vertical coordinates");
      reset_text_color(stdout);
      fprintf(stdout, " :\n");

      print_zaxis_info(vlistID);

      if (nsubtypes > 1)
        {
          fprintf(stdout, "   Subtypes");
          fprintf(stdout, " :\n");

          print_subtype_info(vlistID);
        }

      const auto taxisID = vlistInqTaxis(vlistID);
      const auto ntsteps = vlistNtsteps(vlistID);

      if (ntsteps != 0)
        {
          set_text_color(stdout, BRIGHT);
          fprintf(stdout, "   Time coordinate");
          reset_text_color(stdout);
          fprintf(stdout, " :\n");

          auto taxis_name = taxisNamePtr(taxisID);
          auto tname = taxis_name ? taxis_name : "time";
          fprintf(stdout, "%33s : ", tname);

          set_text_color(stdout, GREEN);
          if (ntsteps == CDI_UNDEFID)
            fprintf(stdout, "unlimited steps\n");
          else
            fprintf(stdout, "%d step%s\n", ntsteps, ntsteps == 1 ? "" : "s");
          reset_text_color(stdout);

          if (taxisID != CDI_UNDEFID)
            {
              if (taxisInqType(taxisID) != TAXIS_ABSOLUTE)
                {
                  auto vdate = taxisInqRdate(taxisID);
                  auto vtime = taxisInqRtime(taxisID);
                  fprintf(stdout, "     RefTime = %s %s", date_to_string(vdate).c_str(), time_to_string(vtime).c_str());

                  const auto tunits = taxisInqTunit(taxisID);
                  if (tunits != CDI_UNDEFID) fprintf(stdout, "  Units = %s", tunit_to_cstr(tunits));

                  const auto calendar = taxisInqCalendar(taxisID);
                  if (calendar != CDI_UNDEFID) fprintf(stdout, "  Calendar = %s", calendar_to_cstr(calendar));

                  if (taxisHasBounds(taxisID)) fprintf(stdout, "  Bounds = true");

                  fprintf(stdout, "\n");

                  if (taxisInqType(taxisID) == TAXIS_FORECAST)
                    {
                      vdate = taxisInqFdate(taxisID);
                      vtime = taxisInqFtime(taxisID);
                      fprintf(stdout, "     ForecastRefTime = %s %s", date_to_string(vdate).c_str(), time_to_string(vtime).c_str());
                      fprintf(stdout, "\n");
                    }
                }
            }

          fprintf(stdout, "  YYYY-MM-DD hh:mm:ss  YYYY-MM-DD hh:mm:ss  YYYY-MM-DD hh:mm:ss  YYYY-MM-DD hh:mm:ss\n");

          set_text_color(stdout, MAGENTA);
          print_timesteps(streamID, taxisID, Options::cdoVerbose);
          reset_text_color(stdout);
          fprintf(stdout, "\n");
        }

      cdo_stream_close(streamID);
    }

  cdo_finish();

  return nullptr;
}
