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

#include <cdi.h>

#include "cdo_options.h"
#include "process_int.h"
#include "util_wildcards.h"
#include "util_string.h"

void
printAtts(int vlistID, int varOrGlobal, int natts, char *argument)
{
  if (varOrGlobal != CDI_GLOBAL)
    {
      char stdname[CDI_MAX_NAME], longname[CDI_MAX_NAME], units[CDI_MAX_NAME];
      int length = CDI_MAX_NAME;
      cdiInqKeyString(vlistID, varOrGlobal, CDI_KEY_STDNAME, stdname, &length);
      vlistInqVarLongname(vlistID, varOrGlobal, longname);
      vlistInqVarUnits(vlistID, varOrGlobal, units);
      const double missval = vlistInqVarMissval(vlistID, varOrGlobal);

      const auto addoffset = vlistInqVarAddoffset(vlistID, varOrGlobal);
      const auto scalefactor = vlistInqVarScalefactor(vlistID, varOrGlobal);
      const bool laddoffset = IS_NOT_EQUAL(addoffset, 0);
      const bool lscalefactor = IS_NOT_EQUAL(scalefactor, 1);

      if (argument)
        {
          if (stdname[0] && wildcardmatch(argument, "standard_name") == 0)
            fprintf(stdout, "   standard_name = \"%s\"\n", stdname);
          if (longname[0] && wildcardmatch(argument, "long_name") == 0)
            fprintf(stdout, "   long_name = \"%s\"\n", longname);
          if (units[0] && wildcardmatch(argument, "units") == 0)
            fprintf(stdout, "   units = \"%s\"\n", units);
          if (wildcardmatch(argument, "missing_value") == 0)
            fprintf(stdout, "   missing_value = %g\n", missval);
          if (laddoffset && wildcardmatch(argument, "add_offset") == 0)
            fprintf(stdout, "   add_offset = %g\n", addoffset);
          if (lscalefactor && wildcardmatch(argument, "scale_factor") == 0)
            fprintf(stdout, "   scale_factor = %g\n", scalefactor);
        }
      else
        {
          if (stdname[0]) fprintf(stdout, "   standard_name = \"%s\"\n", stdname);
          if (longname[0]) fprintf(stdout, "   long_name = \"%s\"\n", longname);
          if (units[0]) fprintf(stdout, "   units = \"%s\"\n", units);
          fprintf(stdout, "   missing_value = %g\n", missval);
          if (laddoffset) fprintf(stdout, "   add_offset = %g\n", addoffset);
          if (lscalefactor) fprintf(stdout, "   scale_factor = %g\n", scalefactor);
        }
    }

  for (int ia = 0; ia < natts; ia++)
    {
      char attname[CDI_MAX_NAME];
      int atttype, attlen;
      cdiInqAtt(vlistID, varOrGlobal, ia, attname, &atttype, &attlen);

      if (argument && wildcardmatch(argument, attname) != 0) continue;

      if (atttype == CDI_DATATYPE_TXT)
        {
          std::vector<char> atttxt(attlen);
          cdiInqAttTxt(vlistID, varOrGlobal, attname, attlen, atttxt.data());
          atttxt[attlen] = '\0';
          fprintf(stdout, "   %s = \"", attname);
          for (int i = 0; i < attlen; ++i)
            {
              if (atttxt[i] == '\n')
                {
                  printf("\\n");
                  if (atttxt[i+1] != 0)
                    {
                      printf("\"\n");
                      printf("             \"");
                    }
                }
              else
                printf("%c", atttxt[i]);
            }
          printf("\"\n");
        }
      else if (atttype == CDI_DATATYPE_INT32)
        {
          std::vector<int> attint(attlen);
          cdiInqAttInt(vlistID, varOrGlobal, attname, attlen, attint.data());
          fprintf(stdout, "   %s = ", attname);
          for (int i = 0; i < attlen; ++i)
            {
              if (i) printf(", ");
              printf("%d", attint[i]);
            }
          printf("\n");
        }
      else if (atttype == CDI_DATATYPE_FLT32 || atttype == CDI_DATATYPE_FLT64)
        {
          char fltstr[128];
          std::vector<double> attflt(attlen);
          cdiInqAttFlt(vlistID, varOrGlobal, attname, attlen, attflt.data());
          fprintf(stdout, "   %s = ", attname);
          for (int i = 0; i < attlen; ++i)
            {
              if (i) printf(", ");
              if (atttype == CDI_DATATYPE_FLT32)
                printf("%sf", doubleToAttstr(Options::CDO_flt_digits, fltstr, sizeof(fltstr), attflt[i]));
              else
                printf("%s", doubleToAttstr(Options::CDO_dbl_digits, fltstr, sizeof(fltstr), attflt[i]));
            }
          printf("\n");
        }
      else
        {
          cdoWarning("Unsupported type %i name %s", atttype, attname);
        }
    }
}

void
check_varname_and_print(int vlistID, int nvars, char *checkvarname, char *attname)
{
  auto lfound = false;
  for (int varID = 0; varID < nvars; varID++)
    {
      char filevarname[CDI_MAX_NAME];
      vlistInqVarName(vlistID, varID, filevarname);
      if (!checkvarname || (wildcardmatch(checkvarname, filevarname) == 0))
        {
          lfound = true;
          fprintf(stdout, "%s:\n", filevarname);
          int natts;
          cdiInqNatts(vlistID, varID, &natts);
          printAtts(vlistID, varID, natts, attname);
          if (!checkvarname) break;
        }
    }
  if (!lfound && checkvarname) cdoAbort("Could not find variable %s!", checkvarname);
}

void *
Showattribute(void *process)
{
  constexpr int delim = '@';
  cdoInitialize(process);

  const auto SHOWATTRIBUTE = cdoOperatorAdd("showattribute", 0, 0, nullptr);
  const auto SHOWATTSVAR = cdoOperatorAdd("showattsvar", 0, 0, nullptr);

  const auto operatorID = cdoOperatorID();

  const auto streamID = cdoOpenRead(0);
  const auto vlistID = cdoStreamInqVlist(streamID);
  const auto nvars = vlistNvars(vlistID);

  const auto nargs = operatorArgc();
  if (nargs == 0)
    {
      if (operatorID == SHOWATTSVAR)
        check_varname_and_print(vlistID, nvars, nullptr, nullptr);
      else
        {
          for (int varID = 0; varID < nvars; varID++)
            {
              char varname[CDI_MAX_NAME];
              vlistInqVarName(vlistID, varID, varname);
              fprintf(stdout, "%s:\n", varname);

              int nattsvar;
              cdiInqNatts(vlistID, varID, &nattsvar);
              printAtts(vlistID, varID, nattsvar, nullptr);
            }
          
          int natts;
          cdiInqNatts(vlistID, CDI_GLOBAL, &natts);
          if (natts) fprintf(stdout, "Global:\n");
          printAtts(vlistID, CDI_GLOBAL, natts, nullptr);

        }
    }
  else
    {
      auto params = cdoGetOperArgv();
      char buffer[CDI_MAX_NAME];
      for (int i = 0; i < nargs; i++)
        {
          strcpy(buffer, params[i].c_str());
          char *result = strrchr(buffer, delim);
          char *input = buffer;
          if (result == nullptr)
            {
              if (operatorID == SHOWATTRIBUTE)
                {
                  int natts;
                  cdiInqNatts(vlistID, CDI_GLOBAL, &natts);
                  if (natts) fprintf(stdout, "Global:\n");
                  printAtts(vlistID, CDI_GLOBAL, natts, input);
                }
              else if (operatorID == SHOWATTSVAR)
                check_varname_and_print(vlistID, nvars, input, nullptr);
            }
          else
            {
              if (operatorID == SHOWATTRIBUTE)
                {
                  input = result + 1;
                  if (*input == 0) input = nullptr;
                  *result = 0;
                  char *varname = buffer;
                  if (*varname == 0) cdoAbort("Variable name not specified!");
                  check_varname_and_print(vlistID, nvars, varname, input);
                }
              else if (operatorID == SHOWATTSVAR)
                check_varname_and_print(vlistID, nvars, input, nullptr);
            }
        }
    }

  cdoStreamClose(streamID);

  cdoFinish();

  return nullptr;
}
