#ifndef TTR_PROPERTY_VECTOR
#define TTR_PROPERTY_VECTOR

#include "RcppArmadillo.h"
#include <vector>
#include <sstream>
#include "enum_functions.hpp"
#include <array>

//Utility structure to deal with changing parameter requirements.
template<typename ENUM, typename V>
struct EnumVector{
  std::array<V,enum_count<ENUM>()> values;
  static_assert(std::is_enum<ENUM>::value, "The enum template argument has to be an enum class");
  EnumVector() : values{} {}
  EnumVector(V init) : values{} {
    values.fill(init);
  }
  EnumVector(Rcpp::List const& L) {
    for(unsigned int item = 0; item < enum_count<ENUM>(); item++){
      ENUM property = enum_cast<ENUM>(item).value();
      this->operator[](property) = V{L[enum_name<ENUM>(property)]};
    }
  }
  EnumVector(Rcpp::NumericVector const& L){
    Rcpp::CharacterVector names = L.names();
    size_t ns = names.length();
    for(size_t i = 0; i < ns; i++){
      Rcpp::String s = names[i];
      auto maybe_val = enum_cast<ENUM>(s.get_cstring());
      if(maybe_val.has_value()){
        this->operator[](maybe_val.value()) = L[i];
      }
    }
  }
  V& operator[](ENUM const& enum_addr){
    return values[static_cast<std::underlying_type_t<ENUM>>(enum_addr)];
  }
  const V& operator[](ENUM const& enum_addr) const {
    return values[static_cast<std::underlying_type_t<ENUM>>(enum_addr)];
  }
  operator Rcpp::List(){
    return this->toList();
  }
  Rcpp::List toList(){
    auto L = Rcpp::List::create();
    for(unsigned int item = 0; item < enum_count<ENUM>(); item++){
      ENUM property = enum_cast<ENUM>(item).value();
      L.push_back(this->operator[](property), enum_name<ENUM>(property));
    }
    return L;
  }
  // each column is interpreted to be an EnumVector of the same kind
  static std::vector<EnumVector<ENUM,V>> fromMatrix(Rcpp::NumericMatrix const& M){
    size_t nc = M.ncol();
    size_t nr = M.nrow();
    auto fm = std::vector<EnumVector<ENUM,V>>(nc);
    Rcpp::CharacterVector propertyNames = Rcpp::rownames(M);
    for(size_t r = 0; r < nr; r ++){
      Rcpp::String ps = propertyNames[r];
      auto maybe_prop = enum_cast<ENUM>(ps.get_cstring());
      if(!maybe_prop.has_value()) continue;
      for(size_t c = 0; c < nc; c++){
        fm[c][maybe_prop.value()] = M(r,c);
      }
    }
    return fm;
  }
};

template<typename ENUM>
using PresenceVector = EnumVector<ENUM,char>;

template <typename ENUM>
using ParameterVector = EnumVector<ENUM,double>;

template <typename T>
Rcpp::StringVector varnames(){
  Rcpp::StringVector out_names(enum_count<T>());
  //assign output variable names
  size_t nit = 0;
  for(auto n : enum_names<T>){
    out_names[nit] = std::string{n};
    nit++;
  }
  return out_names;
}


#endif
