#ifndef DATA_VARIANT_HPP
#define DATA_VARIANT_HPP

#include <algebra/TinyVector.hpp>
#include <language/utils/EmbeddedData.hpp>
#include <language/utils/FunctionSymbolId.hpp>
#include <utils/PugsTraits.hpp>

#include <iostream>
#include <tuple>
#include <variant>
#include <vector>

class AggregateDataVariant;

using DataVariant = std::variant<std::monostate,
                                 bool,
                                 uint64_t,
                                 int64_t,
                                 double,
                                 std::string,
                                 TinyVector<1>,
                                 TinyVector<2>,
                                 TinyVector<3>,
                                 EmbeddedData,
                                 std::vector<bool>,
                                 std::vector<uint64_t>,
                                 std::vector<int64_t>,
                                 std::vector<double>,
                                 std::vector<std::string>,
                                 std::vector<TinyVector<1>>,
                                 std::vector<TinyVector<2>>,
                                 std::vector<TinyVector<3>>,
                                 std::vector<EmbeddedData>,
                                 AggregateDataVariant,
                                 FunctionSymbolId>;

template <typename T, typename...>
inline constexpr bool is_data_variant_v = is_variant<T, DataVariant>::value;

std::ostream& operator<<(std::ostream& os, const DataVariant& v);

class AggregateDataVariant   // LCOV_EXCL_LINE
{
 private:
  std::vector<DataVariant> m_data_vector;
  bool m_is_flattenable = true;

 public:
  friend std::ostream& operator<<(std::ostream& os, const AggregateDataVariant& compound);

  PUGS_INLINE
  void
  setIsFlattenable(bool is_flattenable)
  {
    m_is_flattenable = is_flattenable;
  }

  PUGS_INLINE
  bool
  isFlattenable() const
  {
    return m_is_flattenable;
  }

  PUGS_INLINE
  size_t
  size() const
  {
    return m_data_vector.size();
  }

  PUGS_INLINE
  DataVariant&
  operator[](size_t i)
  {
    Assert(i < m_data_vector.size());
    return m_data_vector[i];
  }

  PUGS_INLINE
  const DataVariant&
  operator[](size_t i) const
  {
    Assert(i < m_data_vector.size());
    return m_data_vector[i];
  }

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

  AggregateDataVariant(std::vector<DataVariant>&& data_vector) : m_data_vector{data_vector} {}

  AggregateDataVariant(const AggregateDataVariant&) = default;
  AggregateDataVariant(AggregateDataVariant&&)      = default;

  AggregateDataVariant() = default;
};

#endif   // DATA_VARIANT_HPP