#ifndef MUTABLE_DISCRETE_FUNCTION_D_PK_VARIANT_HPP
#define MUTABLE_DISCRETE_FUNCTION_D_PK_VARIANT_HPP

#include <scheme/DiscreteFunctionDPk.hpp>
#include <scheme/PolynomialReconstruction.hpp>

#include <variant>

class PolynomialReconstruction::MutableDiscreteFunctionDPkVariant
{
 public:
  using Variant = std::variant<DiscreteFunctionDPk<1, double>,
                               DiscreteFunctionDPk<1, TinyVector<1>>,
                               DiscreteFunctionDPk<1, TinyVector<2>>,
                               DiscreteFunctionDPk<1, TinyVector<3>>,
                               DiscreteFunctionDPk<1, TinyMatrix<1>>,
                               DiscreteFunctionDPk<1, TinyMatrix<2>>,
                               DiscreteFunctionDPk<1, TinyMatrix<3>>,

                               DiscreteFunctionDPk<2, double>,
                               DiscreteFunctionDPk<2, TinyVector<1>>,
                               DiscreteFunctionDPk<2, TinyVector<2>>,
                               DiscreteFunctionDPk<2, TinyVector<3>>,
                               DiscreteFunctionDPk<2, TinyMatrix<1>>,
                               DiscreteFunctionDPk<2, TinyMatrix<2>>,
                               DiscreteFunctionDPk<2, TinyMatrix<3>>,

                               DiscreteFunctionDPk<3, double>,
                               DiscreteFunctionDPk<3, TinyVector<1>>,
                               DiscreteFunctionDPk<3, TinyVector<2>>,
                               DiscreteFunctionDPk<3, TinyVector<3>>,
                               DiscreteFunctionDPk<3, TinyMatrix<1>>,
                               DiscreteFunctionDPk<3, TinyMatrix<2>>,
                               DiscreteFunctionDPk<3, TinyMatrix<3>>,

                               DiscreteFunctionDPkVector<1, double>,
                               DiscreteFunctionDPkVector<1, TinyVector<1>>,
                               DiscreteFunctionDPkVector<1, TinyVector<2>>,
                               DiscreteFunctionDPkVector<1, TinyVector<3>>,
                               DiscreteFunctionDPkVector<1, TinyMatrix<1>>,
                               DiscreteFunctionDPkVector<1, TinyMatrix<2>>,
                               DiscreteFunctionDPkVector<1, TinyMatrix<3>>,

                               DiscreteFunctionDPkVector<2, double>,
                               DiscreteFunctionDPkVector<2, TinyVector<1>>,
                               DiscreteFunctionDPkVector<2, TinyVector<2>>,
                               DiscreteFunctionDPkVector<2, TinyVector<3>>,
                               DiscreteFunctionDPkVector<2, TinyMatrix<1>>,
                               DiscreteFunctionDPkVector<2, TinyMatrix<2>>,
                               DiscreteFunctionDPkVector<2, TinyMatrix<3>>,

                               DiscreteFunctionDPkVector<3, double>,
                               DiscreteFunctionDPkVector<3, TinyVector<1>>,
                               DiscreteFunctionDPkVector<3, TinyVector<2>>,
                               DiscreteFunctionDPkVector<3, TinyVector<3>>,
                               DiscreteFunctionDPkVector<3, TinyMatrix<1>>,
                               DiscreteFunctionDPkVector<3, TinyMatrix<2>>,
                               DiscreteFunctionDPkVector<3, TinyMatrix<3>>>;

 private:
  Variant m_mutable_discrete_function_dpk;

 public:
  PUGS_INLINE
  const Variant&
  mutableDiscreteFunctionDPk() const
  {
    return m_mutable_discrete_function_dpk;
  }

  template <typename DiscreteFunctionDPkT>
  PUGS_INLINE auto&&
  get() const
  {
    static_assert(is_discrete_function_dpk_v<DiscreteFunctionDPkT>, "invalid template argument");
#ifndef NDEBUG
    if (not std::holds_alternative<DiscreteFunctionDPkT>(this->m_mutable_discrete_function_dpk)) {
      std::ostringstream error_msg;
      error_msg << "invalid discrete function type\n";
      error_msg << "- required " << rang::fgB::red << demangle<DiscreteFunctionDPkT>() << rang::fg::reset << '\n';
      error_msg << "- contains " << rang::fgB::yellow
                << std::visit([](auto&& f) -> std::string { return demangle<decltype(f)>(); },
                              this->m_mutable_discrete_function_dpk)
                << rang::fg::reset;
      throw NormalError(error_msg.str());
    }
#endif   // NDEBUG

    return std::get<DiscreteFunctionDPkT>(this->mutableDiscreteFunctionDPk());
  }

  template <size_t Dimension, typename DataType>
  MutableDiscreteFunctionDPkVariant(const DiscreteFunctionDPk<Dimension, DataType>& discrete_function_dpk)
    : m_mutable_discrete_function_dpk{discrete_function_dpk}
  {
    static_assert(std::is_same_v<DataType, double> or                       //
                    std::is_same_v<DataType, TinyVector<1, double>> or      //
                    std::is_same_v<DataType, TinyVector<2, double>> or      //
                    std::is_same_v<DataType, TinyVector<3, double>> or      //
                    std::is_same_v<DataType, TinyMatrix<1, 1, double>> or   //
                    std::is_same_v<DataType, TinyMatrix<2, 2, double>> or   //
                    std::is_same_v<DataType, TinyMatrix<3, 3, double>>,
                  "DiscreteFunctionDPk with this DataType is not allowed in variant");
  }

  template <size_t Dimension, typename DataType>
  MutableDiscreteFunctionDPkVariant(const DiscreteFunctionDPkVector<Dimension, DataType>& discrete_function_dpk)
    : m_mutable_discrete_function_dpk{discrete_function_dpk}
  {
    static_assert(std::is_same_v<DataType, double> or                       //
                    std::is_same_v<DataType, TinyVector<1, double>> or      //
                    std::is_same_v<DataType, TinyVector<2, double>> or      //
                    std::is_same_v<DataType, TinyVector<3, double>> or      //
                    std::is_same_v<DataType, TinyMatrix<1, 1, double>> or   //
                    std::is_same_v<DataType, TinyMatrix<2, 2, double>> or   //
                    std::is_same_v<DataType, TinyMatrix<3, 3, double>>,
                  "DiscreteFunctionDPkVector with this DataType is not allowed in variant");
  }

  MutableDiscreteFunctionDPkVariant& operator=(MutableDiscreteFunctionDPkVariant&&)      = default;
  MutableDiscreteFunctionDPkVariant& operator=(const MutableDiscreteFunctionDPkVariant&) = default;

  MutableDiscreteFunctionDPkVariant(const MutableDiscreteFunctionDPkVariant&) = default;
  MutableDiscreteFunctionDPkVariant(MutableDiscreteFunctionDPkVariant&&)      = default;

  MutableDiscreteFunctionDPkVariant()  = delete;
  ~MutableDiscreteFunctionDPkVariant() = default;
};

#endif   // MUTABLE_DISCRETE_FUNCTION_D_PK_VARIANT_HPP