/*
  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 <cstdlib>
#include <string>

#include "cdo_options.h"
#include "cdo_output.h"
#include "process_int.h"
#include "pmlist.h"
#include "convert_units.h"
#include "param_conversion.h"
#include "parse_literals.h"
#include "cdo_cmor.h"
#include "cdo_cdi_wrapper.h"

int stringToParam(const char *paramstr);

void
mapvar(int vlistID, int varID, const KeyValues &kv, const std::string &key, CmorVar *var, bool &lvalid_min, bool &lvalid_max,
       int ptab, bool isnPtmodeName)
{
  const char *value = (kv.nvalues > 0) ? kv.values[0].c_str() : nullptr;
  const bool lv1 = (kv.nvalues == 1);

  // printf("key=%s  value=%s\n", key, value ? value : "");

  // clang-format off
  if (!var)
    {
      if (key == "cn")
        {
          char name[CDI_MAX_NAME];
          vlistInqVarName(vlistID, varID, name);
          if (name[0] != 0) cdiDefAttTxt(vlistID, varID, "original_name", (int) strlen(name), parameter2word(name));
          cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, parameter2word(value));
        }
      else if (key == "u")
        cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, value);
      else if (key == "cm")
        cdiDefAttTxt(vlistID, varID, "cell_methods", (int) strlen(value), value);
      else if (key == "ca")
        cdiDefAttTxt(vlistID, varID, "character_axis", (int) strlen(value), value);
      else if (key == "za")
        cdiDefAttTxt(vlistID, varID, "z_axis", (int) strlen(value), value);
      else if (key == "vc")
        cdiDefAttTxt(vlistID, varID, "variable_comment", (int) strlen(value), value);
      else if (key == "p")
        {
          if (!isspace(value[0])) cdiDefAttTxt(vlistID, varID, "positive", (int) strlen(value), value);
        }
      else
        {
          if (Options::cdoVerbose) cdoPrint("In applying the mapping table:\n          Key: '%s' is ignored.", key);
        }
    }
  else
    {
      if      (lv1 && key == "standard_name") cdiDefKeyString(vlistID, varID, CDI_KEY_STDNAME, value);
      else if (lv1 && key == "long_name") cdiDefKeyString(vlistID, varID, CDI_KEY_LONGNAME, value);
      else if (lv1 && key == "units") cdiDefKeyString(vlistID, varID, CDI_KEY_UNITS, value);
      else if (lv1 && key == "name")
        {
          if (isnPtmodeName) cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, parameter2word(value));
        }
      else if (lv1 && key == "out_name")
        {
          const auto outname = parameter2word(value);
          if (!cstrIsEqual(var->name, outname))
            {
              cdiDefKeyString(vlistID, varID, CDI_KEY_NAME, outname);
              cdiDefAttTxt(vlistID, varID, "original_name", (int) strlen(var->name), var->name);
            }
        }
      else if (lv1 && key == "param")
        vlistDefVarParam(vlistID, varID, stringToParam(parameter2word(value)));
      else if (lv1 && key == "out_param")
        vlistDefVarParam(vlistID, varID, stringToParam(parameter2word(value)));
      else if (lv1 && key == "code")
        vlistDefVarParam(vlistID, varID, cdiEncodeParam(parameter2int(value), ptab, 255));
      else if (lv1 && key == "out_code")
        vlistDefVarParam(vlistID, varID, cdiEncodeParam(parameter2int(value), ptab, 255));
      else if (lv1 && key == "uvRelativeToGrid")
        cdiDefKeyInt(vlistID, varID, CDI_KEY_UVRELATIVETOGRID, parameter2bool(value));
      else if (lv1 && key == "comment")
        cdiDefAttTxt(vlistID, varID, key.c_str(), (int) strlen(value), value);
      else if (lv1 && key == "chunktype") ;
      else if (lv1 && key == "cell_methods")
        cdiDefAttTxt(vlistID, varID, key.c_str(), (int) strlen(value), value);
      else if (lv1 && key == "cell_measures")
        cdiDefAttTxt(vlistID, varID, key.c_str(), (int) strlen(value), value);
      else if (lv1 && key == "delete") var->remove = parameter2bool(value);
      else if (lv1 && key == "convert") var->convert = parameter2bool(value);
      else if (lv1 && key == "factor")
        {
          var->lfactor = true;
          var->factor = parameter2double(value);
          if (Options::cdoVerbose) cdoPrint("%s - scale factor %g", var->name, var->factor);
        }
      else if (lv1 && (key == "missval" || key == "missing_value"))
        {
          const auto missval = parameter2double(value);
          const auto missval_old = vlistInqVarMissval(vlistID, varID);
          if (!DBL_IS_EQUAL(missval, missval_old))
            {
              if (Options::cdoVerbose) cdoPrint("%s - change missval from %g to %g", var->name, missval_old, missval);
              var->changemissval = true;
              var->missval_old = missval_old;
              vlistDefVarMissval(vlistID, varID, missval);
            }
        }
      else if (lv1 && key == "valid_min")
        {
          lvalid_min = true;
          var->valid_min = parameter2double(value);
        }
      else if (lv1 && key == "valid_max")
        {
          lvalid_max = true;
          var->valid_max = parameter2double(value);
        }
      else if (lv1 && key == "ok_min_mean_abs")
        {
          var->check_min_mean_abs = true;
          var->ok_min_mean_abs = parameter2double(value);
        }
      else if (lv1 && key == "ok_max_mean_abs")
        {
          var->check_max_mean_abs = true;
          var->ok_max_mean_abs = parameter2double(value);
        }
      else if (lv1 && (key == "datatype" || key == "type"))
        {
          const auto datatype = cdi_str2datatype(parameter2word(value));
          if (datatype != -1) vlistDefVarDatatype(vlistID, varID, datatype);
        }
      else if (lv1 && key == "dimensions")
        {
        }
      else
        {
          const auto &values = kv.values;
          const auto &rvalue = kv.values[0];
          int nvalues = kv.nvalues;
          if (nvalues == 1 && rvalue.empty()) nvalues = 0;

          const int dtype = literalsFindDatatype(nvalues, values);

          if (dtype == CDI_DATATYPE_INT8 || dtype == CDI_DATATYPE_INT16 || dtype == CDI_DATATYPE_INT32)
            {
              std::vector<int> ivals(nvalues);
              for (int i = 0; i < nvalues; ++i) ivals[i] = literal2int(values[i]);
              cdiDefAttInt(vlistID, varID, key.c_str(), dtype, nvalues, ivals.data());
            }
          else if (dtype == CDI_DATATYPE_FLT32 || dtype == CDI_DATATYPE_FLT64)
            {
              std::vector<double> dvals(nvalues);
              for (int i = 0; i < nvalues; ++i) dvals[i] = literal2double(values[i]);
              cdiDefAttFlt(vlistID, varID, key.c_str(), dtype, nvalues, dvals.data());
            }
          else
            {
              const int len = (int)rvalue.size();
              cdiDefAttTxt(vlistID, varID, key.c_str(), len, rvalue.c_str());
            }
        }
    }
  // clang-format on
}
