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

  Author: Uwe Schulzweida

*/
#ifndef SELECTKEY_H
#define SELECTKEY_H

#include <map>
#include <string>
#include <vector>
#include "pmlist.h"
#include "cdo_options.h"

template <typename T>
class SelectKey
{
  // Enforce supported specializations at compile time.
  static_assert(std::is_same_v<T, int> || std::is_same_v<T, double> || std::is_same_v<T, std::string>,
                "SelectKey only supports int, double, and std::string");

private:
  std::string m_key;
  std::string m_description;
  T m_value{};
  std::vector<bool> flag;
  int nvalues{ 0 };
  std::vector<T> valueList;

public:
  explicit SelectKey(KVList const &kvlist, std::map<std::string, bool> &validKeyMap, std::string const &key,
                     std::string const &description)
      : m_key(key), m_description(description)
  {
    for (auto const &kv : kvlist)
    {
      if (key == kv.key)
      {
        validKeyMap[key] = true;
        valueList = get_values(kv);
        nvalues = static_cast<int>(valueList.size());
        flag.assign(nvalues, false);
        if (Options::cdoVerbose) print_values();
        break;
      }
    }
  }

  void
  print_values()
  {
    constexpr auto typeName = []
    {
      if constexpr (std::is_same_v<T, int>) { return "integer"; }
      else if constexpr (std::is_same_v<T, double>) { return "double"; }
      else
        return "string";
    }();
    const auto printVal = [](T const &v)
    {
      if constexpr (std::is_same_v<T, std::string>) { std::printf(" %s", v.c_str()); }
      else if constexpr (std::is_same_v<T, double>) { std::printf(" %g", v); }
      else
      {
        std::printf(" %d", v);
      }
    };
    const auto display = std::min(nvalues, 12);
    std::printf("key=%-16s type=%-7s  nvalues=%-4d ", m_key.c_str(), typeName, nvalues);
    for (int i = 0; i < display; ++i) printVal(valueList[i]);
    if (display < nvalues)
    {
      std::printf(" ...");
      printVal(valueList[nvalues - 1]);
    }
    std::printf("\n");
  }

  void
  check_flag()
  {
    for (int i = 0; i < nvalues; ++i)
    {
      if (!flag[i])
      {
        if constexpr (std::is_same_v<T, std::string>)
          cdo_warning("%s >%s< not found!", m_description, valueList[i]);
        else if constexpr (std::is_same_v<T, double>)
          cdo_warning("%s >%g< not found!", m_description, valueList[i]);
        else
          cdo_warning("%s >%d< not found!", m_description, valueList[i]);
      }
    }
  }
  // clang-format off
  std::vector<T> get_values(KeyValues const &kv);
  void set(T const &value) { m_value = value;}
  [[nodiscard]] T const &get() const { return m_value; }
  [[nodiscard]] int size() const { return nvalues; }
  [[nodiscard]] bool empty() const { return (nvalues == 0); }
  [[nodiscard]] bool check();
  [[nodiscard]] bool check_index(int maxValues);
  [[nodiscard]] bool check_range(double value);
  void check_range_flag();
  [[nodiscard]] bool check_date();
  [[nodiscard]] bool check_season(int month);
  void set_flag_at(int index) { if (index >= 0 && index < nvalues) { flag[index] = true; } }
  [[nodiscard]] T get_value_at(int index) { return (index >= 0 && index < nvalues) ? valueList[index] : T{}; }
  void set_value_at(int index, T const &value) { if (index >= 0 && index < nvalues) { valueList[index] = value; } }
  // clang-format on
};

using KeyInt = SelectKey<int>;
using KeyFloat = SelectKey<double>;
using KeyWord = SelectKey<std::string>;

#endif
