#ifndef KA_PRODUCTITER_HPP
#define KA_PRODUCTITER_HPP
#pragma once
#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <tuple>
#include <type_traits>
#include <boost/iterator/iterator_facade.hpp>
#include "integersequence.hpp"
#include "macro.hpp"
#include "macroregular.hpp"
#include "typetraits.hpp"
#include "utility.hpp"

/// @file
///
/// Contains a type template that implements the product of iterators, that is
/// an iterator that groups sub-iterators and advances, dereferences, etc., them
/// all at once.
///
/// # Added components
///
/// - Type contructors:
///     + `product_iter_t: InputIterator... -> InputIterator`
///
/// - Constructor functions (with InputIterator... I, Linearizable... L):
///     + `product_iter: I... -> product_iter_t<I...>`
///     + `product_begin: L... -> product_iter_t<Iterator<L>...>`
///     + `product_end: L... -> product_iter_t<Iterator<L>...>`
///     + `product_end_same_size: L... -> product_iter_t<Iterator<L>...>`
///
/// - Functions (with InputIterator... I, InputIterator J, K):
///     + `proj<n>: product_iter_t<I...> -> I_n` (i.e. nth type in I...)
///     + `iterator_cat_ops::operator*`: J * K -> product_iter_t<J, K>
///
/// - Types:
///     + `unit_iter_t: InputIterator`
///
/// - Constants:
///     + `unit_iter: unit_iter_t`

namespace ka {

namespace product_iter_detail {
  // The iterator category of a product of iterators is the lowest of all these
  // iterators. The order is defined as: input < forward < bidirectional <
  // random access.
  //
  // We need to implement a compile-time minimum algorithm, returning the
  // minimum iterator category from a compile-time list. To do this, we first
  // convert iterator categories to integers, then find the minimum integer, and
  // finally convert back this integer to the corresponding iterator category.
  // It is easier to order integers than to define the ordering for all pairs of
  // iterator categories.

  // Alias to represent a compile-time integer.
  template<int i>
  using Int = int_constant_t<i>;

  // Alias to represent a compile-time boolean.
  template<bool b>
  using Bool = bool_constant_t<b>;

  // Converts a type to a compile-time integer.
  template<typename A>
  struct ToIntImpl;

  // Converts a compile-time integer to a type.
  template<typename A>
  struct FromIntImpl;

  template<typename T>
  using ToInt = typename ToIntImpl<T>::type;

  template<typename T>
  using FromInt = typename FromIntImpl<T>::type;

// Maps a type to a compile-time integer, and vice-versa.
#define KA_DEFINE_TYPE_TO_FROM_INT(T, i) \
    template<>                           \
    struct ToIntImpl<T> {                \
      using type = Int<i>;               \
    };                                   \
    template<>                           \
    struct FromIntImpl<Int<i>> {         \
      using type = T;                    \
    };

  // Maps iterator categories to compile-time integer, and vice-versa.
  KA_DEFINE_TYPE_TO_FROM_INT(std::input_iterator_tag, 0)
  KA_DEFINE_TYPE_TO_FROM_INT(std::forward_iterator_tag, 1)
  KA_DEFINE_TYPE_TO_FROM_INT(std::bidirectional_iterator_tag, 2)
  KA_DEFINE_TYPE_TO_FROM_INT(std::random_access_iterator_tag, 3)
#undef KA_DEFINE_TYPE_TO_FROM_INT

  // Minimum of two integers.
  template<typename A, typename B, typename ALessThanB>
  struct MinInt2Impl;

  template<int i, int j>
  struct MinInt2Impl<Int<i>, Int<j>, true_t> {
    using type = Int<i>;
  };

  template<int i, int j>
  struct MinInt2Impl<Int<i>, Int<j>, false_t> {
    using type = Int<j>;
  };

  template<typename A, typename B, typename ALessThanB>
  using MinInt2 = typename MinInt2Impl<A, B, ALessThanB>::type;

  // Minimum of n integers.
  // Algorithm:
  //  Incrementally replace the two head integers by the minimum one.
  //  When the list contains one element only, it is the minimum.
  template<typename... T>
  struct MinIntNImpl;

  template<typename T>
  struct MinIntNImpl<T> {
    using type = T;
  };

  template<int i, int j, typename... T>
  struct MinIntNImpl<Int<i>, Int<j>, T...>
    : MinIntNImpl<MinInt2<Int<i>, Int<j>, Bool<(i < j)>>, T...> {
  };

  template<typename... T>
  using MinIntN = typename MinIntNImpl<T...>::type;

  // Returns the lowest iterator category.
  template<typename... T>
  struct MinIterCatImpl {
    using type = FromInt<MinIntN<ToInt<T>...>>;
  };

  // Returns the smallest of two types.
  template<typename T, typename U, typename TLessThanU>
  struct MinSize2 {
    using type = T;
  };

  template<typename T, typename U>
  struct MinSize2<T, U, false_t> {
    using type = U;
  };

  // Returns the smallest of n types, according to `sizeof`.
  // The algorithm is similar to `MinInt`.
  template<typename... T>
  struct MinSize;

  template<typename T>
  struct MinSize<T> {
    using type = T;
  };

  template<typename T, typename U, typename... V>
  struct MinSize<T, U, V...>
    : MinSize<typename MinSize2<T, U, Bool<(sizeof(T) < sizeof(U))>>::type, V...> {
  };

  template<typename... T>
  using MinIterCat = typename MinIterCatImpl<T...>::type;

  // Convenient aliases to iterator affiliated types.
  template<typename T>
  using Value = typename std::iterator_traits<T>::value_type;

  template<typename T>
  using Reference = typename std::iterator_traits<T>::reference;

  template<typename T>
  using Difference = typename std::iterator_traits<T>::difference_type;

  template<typename T>
  using Category = typename std::iterator_traits<T>::iterator_category;
} // namespace product_iter_detail

// Base of `product_iter_t`. This macro avoids manual duplication of the code.
// It is undefined below.
#define KA_PRODUCT_ITER_BASE(I)                                                        \
  boost::iterator_facade<                                                              \
    product_iter_t<I...>,                                                              \
    std::tuple<product_iter_detail::Value<I>...>,                                      \
    product_iter_detail::MinIterCat<product_iter_detail::Category<I>...>,              \
    std::tuple<product_iter_detail::Reference<I>...>,                                  \
    typename product_iter_detail::MinSize<product_iter_detail::Difference<I>...>::type \
  >

/// Product of iterators: groups sub-iterators and advances, dereferences, etc.,
/// them all at once.
///
/// This type is a product in the categorical sense: it strictly groups
/// sub-iterators with no extra-information, and comes with projections to get
/// them back. The underlying category is constructed over those of discrete
/// dynamical systems (sets equipped with a transformation), with the additional
/// structure of the dereferencement.
/// See [Conceptual Mathematics, (Lawvere-Schanuel, 2009)](https://www.cambridge.org/core/books/conceptual-mathematics/00772F4CC3D4268200C5EC86B39D415A), article III
///
/// This iterator has the lowest iterator category of its sub-iterators, the
/// order being: input < forward < bidirectional < random access.
///
/// To construct product of iterators, use the dedicated constructor functions:
/// `product_iter`, `product_begin`, `product_end`, `product_end_same_size`
///
/// Note: The operations of the product of iterators are specified in terms of
///   laws. The following notation is used:
///     - `i * j` denotes product of iterators `i` and `j`
///     - `t * u` denotes product of values `t` and `u`, i.e. tuple of `t` and `u`
///     - `n` denotes a signed integer
///     - `i + n` denotes iterator `i` moved forward by `n` steps
///     - `j - i` denotes distance from iterator `i` to iterator `j`
///     - `1i` denotes iterator unit
///     - `1` denotes unit
///     - `=` denotes equality
///     - `≅` denotes isomorphism
///
/// Laws:
///     *(i * ...)  =  ((*i) * ...)             (dereferencement)
///  && (i * ...) + n  =  (i + n) * ...         (forward / backward move)
///  && (j * ...) - (i * ...)  =  n, such that
///       (j * ...)  =  (i * ...) + n, that is
///       + is a right group action.            (difference)
///  && i * 1i  ≅  1i * i  ≅  i                 (multiplication by unit)
///  && 1i + n  =  1i                           (rest state 1)
///  && 1i - 1i  =  0                           (rest state 2)
///  && *1i  =  1                               (dereferencement of unit)
///
/// Note: Output iterators are not supported as their behavior differs too much
///   from other iterator categories (`void` value type, etc.).
///
/// Example: Iterating through several containers at once.
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// // Containers are assumed to be large enough.
/// // std::vector<X> v;
/// // std::array<Y, 10> a;
/// auto b = product_iter(v.begin(), a.begin());
/// auto e = product_iter(v.begin() + 2, a.begin() + 2);
/// while (b != e) {
///   auto values = *b; // `values` has type `std::tuple<X, Y>`.
///   ... // Use `values` (`ka::apply` can be used to call non-tuple input functions.)
///   ++b;
/// }
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
///
/// Example: Iterating through several containers with different sizes.
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// // Computing the end product iterator is O(n) since we have to determine the
/// // smallest container first.
/// // std::vector<X> v;  // size = 4
/// // std::list<Y> l;    // size = 2
/// // std::deque<Z> d;   // size = 10
/// // `product_begin(v, l, d) == product_iter(v.begin(), l.begin(), d.begin())`
/// auto b = product_begin(v, l, d);
/// auto e = product_end(v, l, d); // Ends depends on the smallest container.
/// // Will loop twice since the smallest container has size 2.
/// while (b != e) {
///   auto values = *b; // `values` has type `std::tuple<X, Y, Z>`.
///   ... // Use `values` (`ka::apply` can be used to call non-tuple input functions.)
///   ++b;
/// }
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
///
/// Example: Iterating through several containers with same size.
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// // Computing the end product iterator is O(1) since we know all containers
/// // have same size.
/// // std::vector<X> v;  // size = 4
/// // std::list<Y> l;    // size = 4
/// // std::deque<Z> d;   // size = 4
/// auto b = product_begin(v, l, d);
/// auto e = product_end_same_size(v, l, d); // Takes end iterator of each container.
/// // Will loop four times.
/// while (b != e) {
///   auto values = *b; // `values` has type `std::tuple<X, Y, Z>`.
///   ... // Use `values` (`ka::apply` can be used to call non-tuple input functions.)
///   ++b;
/// }
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
///
/// InputIterator... I
template<typename... I>
struct product_iter_t : KA_PRODUCT_ITER_BASE(I) {
  using base = KA_PRODUCT_ITER_BASE(I);
#undef KA_PRODUCT_ITER_BASE
  using typename base::reference;
  using typename base::difference_type;
  using indexes_type = index_sequence_for<I...>;
  std::tuple<I...> iters;

  explicit
  product_iter_t(std::tuple<I...> const& t) : iters(t) {
  }
  explicit
  product_iter_t(std::tuple<I...>&& t) : iters(std::move(t)) {
  }
// Regular:
  explicit
  product_iter_t() = default;

  bool equal(product_iter_t const& x) const KA_NOEXCEPT_EXPR(iters == x.iters) {
    return iters == x.iters;
  }
// InputIterator:

  //     *(i * ...) = ((*i) * ...)
  reference dereference() const {
    return do_dereference(indexes_type{});
  }
  //  && (i * ...) + n = (i + n) * ...
  void increment() {
    do_increment(indexes_type{});
  }
// BidirectionalIterator (if BidirectionalIterator... I):
  void decrement() {
    do_decrement(indexes_type{});
  }
// RandomAccessIterator (if RandomAccessIterator... I):
  /// Integral N
  template<typename N>
  void advance(N n) {
    do_advance(n, indexes_type{});
  }
  difference_type distance_to(product_iter_t x) const {
    // There may be an algorithm better than this O(n) one...
    // But it strictly follows the law of difference stated above, and therefore
    // is correct for any orbit-shapes (infinite, circular, etc.).
    auto n = difference_type(0);
    while (x.iters != iters) {
      ++n;
      x.do_increment(indexes_type{});
    }
    return -n;
  }
private:
  template<std::size_t... i>
  reference do_dereference(index_sequence<i...>) const {
    return reference{*std::get<i>(iters)...};
  }

  template<std::size_t... i>
  void do_increment(index_sequence<i...>) {
    (void)std::initializer_list<int>{0, (void(++std::get<i>(iters)), 0)...};
  }

  template<std::size_t... i>
  void do_decrement(index_sequence<i...>) {
    (void)std::initializer_list<int>{0, (void(--std::get<i>(iters)), 0)...};
  }

  template<std::size_t... i, typename N>
  void do_advance(N n, index_sequence<i...>) {
    (void)std::initializer_list<int>{0, (std::advance(std::get<i>(iters), n), 0)...};
  }
};

/// Empty specialization.
/// This is the unit type of the category of iterators.
///
/// See non-specialized version for info.
template<>
struct product_iter_t<> : boost::iterator_facade<
    product_iter_t<>,
    std::tuple<>,
    std::random_access_iterator_tag,
    std::tuple<>,
    std::ptrdiff_t
  >
{
// InputIterator:
  constexpr
  reference dereference() const noexcept {
    return std::tuple<>{};
  }
  constexpr
  bool equal(product_iter_t const&) const noexcept {
    return true;
  }
  // `void` cannot be returned for the method to be `constexpr`...
  // `iterator_facade` impose no constraint on the return type, so `int` is ok.
  constexpr
  int increment() const noexcept {
    return 0;
  }
// BidirectionalIterator (if BidirectionalIterator... I):
  constexpr
  int decrement() const noexcept {
    return 0;
  }
// RandomAccessIterator (if RandomAccessIterator... I):
  /// Integral N
  template<typename N> constexpr
  int advance(N) const noexcept {
    return 0;
  }
  constexpr
  difference_type distance_to(product_iter_t const&) const noexcept {
    return 0;
  }
};

/// Random access iterator that is its own successor and predecessor.
/// Its value type is `unit_t`.
/// As `unit_t`, this iterator type does not hold any information.
using unit_iter_t = product_iter_t<>;

/// Unique value of `unit_iter_t`.
constexpr unit_iter_t unit_iter;

/// Contructs a product of iterators, performing argument type deduction.
///
/// InputIterator... I
template<typename... I>
auto product_iter(I&&... i) -> product_iter_t<Decay<I>...> {
  return product_iter_t<Decay<I>...>{product(fwd<I>(i)...)};
}

// Put `begin`/`end` functions in the detail namespace to be able to leverage
// `ADL` in `decltype` clauses.
namespace product_iter_detail {
  using std::begin;
  using std::end;

  template<typename L>
  auto adl_begin(L&& l) -> decltype(begin(fwd<L>(l))) {
    return begin(fwd<L>(l));
  }

  template<typename L>
  auto adl_end(L&& l) -> decltype(end(fwd<L>(l))) {
    return end(fwd<L>(l));
  }

  // Some containers have a `size` method, returning `size_type` which is
  // typically `std::size_t` (unsigned), while others don't have such a method.
  // In the latter case `std::distance` is used, but it returns the iterator
  // `difference_type`, which is typically `std::ptrdiff_t` (signed). A cast
  // must be done. Fortunately, the context guarantees a non-null
  // `difference_type`, so the only problem could be in a difference of ranges
  // between `size_type` and `difference_type`.
  template<typename T>
  auto total_size(true_t /* HasMemberSize */, T const& t) -> decltype(t.size()) {
    return t.size();
  }

  template<typename T>
  auto total_size(false_t /* HasMemberSize */, T const& t) -> std::size_t {
    return static_cast<std::size_t>(std::distance(begin(t), end(t)));
  }

  // Precondition: n <= size
  template<typename L, typename N>
  auto iter_at(L&& l, N n, N size) -> decltype(end(fwd<L>(l))) {
    if (n == size) {
      return end(fwd<L>(l));
    }
    return std::next(begin(fwd<L>(l)), n);
  }

  // Computes the minimum size, then advances iterators accordingly.
  template<typename... L, std::size_t... i>
  auto product_end(index_sequence<i...>, L&&... l)
      -> decltype(ka::product_iter(end(fwd<L>(l))...)) {
    // This is a slight simplification: we should use internal `distance_type`
    // with a special case for `std::array`.
    std::array<std::size_t, sizeof...(L)> sizes =
      {{total_size(HasMemberSize<Decay<L>>{}, l)...}};
    auto const it = std::min_element(sizes.begin(), sizes.end());
    return product_iter(iter_at(fwd<L>(l), *it, sizes[i])...);
  }
} // namespace product_iter_detail

/// Product of begin iterators.
///
/// invariant: product_begin(l...) == product_iter(begin(l)...)
///
/// Linearizable... L
template<typename... L>
auto product_begin(L&&... l)
    -> decltype(product_iter(product_iter_detail::adl_begin(fwd<L>(l))...)) {
  return product_iter(product_iter_detail::adl_begin(fwd<L>(l))...);
}

/// Product of iterators aligned with the smallest end.
///
/// invariant: product_end(l...) == product_iter(begin(l) + n...), where
///            n == min(size(l)...) (min and size are specification-only)
///
/// Complexity: Constructing the end iterator requires finding the minimum size
///   of each range. Computing the size of a standard container is O(1), except
///   for `std::forward_list` which is O(n). Then, finding the minimum of these
///   sizes is O(n) in the number of containers.
///
/// Linearizable... L
template<typename... L>
auto product_end(L&&... l) -> decltype(product_iter_detail::product_end(
    index_sequence_for<L...>{}, fwd<L>(l)...)) {
  return product_iter_detail::product_end(
    index_sequence_for<L...>{}, fwd<L>(l)...);
}

/// Product of end iterators.
///
/// Precondition: (forall x,y in l...) x.size() == y.size()
/// Invariant: product_end(l...) == product_iter(end(l)...)
///
/// Note: If the size is known to be the same, an alternative to create an end
///   iterator and form a bounded range (a begin-end pair of iterators) is to
///   directly use a counted range (a begin iterator together with a distance).
///
/// Linearizable... L
template<typename... L>
auto product_end_same_size(L&&... l)
    -> decltype(product_iter(product_iter_detail::adl_end(fwd<L>(l))...)) {
  return product_iter(product_iter_detail::adl_end(fwd<L>(l))...);
}

/// Projection of n th iterator of the product.
template<std::size_t n, typename... I>
auto proj(product_iter_t<I...> const& x) -> decltype(std::get<n>(x.iters)) {
  return std::get<n>(x.iters);
}

/// Projection of n th iterator of the product.
template<std::size_t n, typename... I>
auto proj(product_iter_t<I...>& x) -> decltype(std::get<n>(x.iters)) {
  return std::get<n>(x.iters);
}

/// Projection of n th iterator of the product.
template<std::size_t n, typename... I>
auto proj(product_iter_t<I...>&& x) -> decltype(std::get<n>(std::move(x.iters))) {
  return std::get<n>(std::move(x.iters));
}

/// Categorical operators for iterators.
namespace iterator_cat_ops {

/// InputIterator I, InputIterator J
template<typename I, typename J,
  typename = EnableIfInputIterator<I>, typename = EnableIfInputIterator<J>>
auto operator*(I i, J j) -> decltype(product_iter(i, j)) {
  return product_iter(std::move(i), std::move(j));
}

} // namespace iterator_ops

} // namespace ka

#endif // KA_PRODUCTITER_HPP


#pragma once

#include <crest/wave/solver.hpp>
#include <crest/geometry/indexed_mesh.hpp>
#include <crest/quadrature/simpsons.hpp>
#include <crest/util/stat.hpp>

#include <string>
#include <cmath>

#include <boost/optional.hpp>

struct OfflineParameters
{
    unsigned int oversampling;
    double mesh_resolution;

    std::string basis_export_file;
    std::string basis_import_file;

    unsigned int dense_fallback_threshold;
    double iterative_tolerance;

    OfflineParameters()
            : oversampling(0),
              mesh_resolution(0.0),
              dense_fallback_threshold(300),
              iterative_tolerance(10.0 * std::numeric_limits<double>::epsilon())
    { }

    OfflineParameters & with_oversampling(unsigned int oversampling)
    {
        this->oversampling = oversampling;
        return *this;
    }

    OfflineParameters & with_mesh_resolution(double mesh_resolution)
    {
        this->mesh_resolution = mesh_resolution;
        return *this;
    }

    OfflineParameters & with_basis_export_file(std::string path)
    {
        this->basis_export_file = path;
        return *this;
    }

    OfflineParameters & with_basis_import_file(std::string path)
    {
        this->basis_import_file = path;
        return *this;
    }

    OfflineParameters & with_dense_fallback_threshold(unsigned int threshold)
    {
        this->dense_fallback_threshold = threshold;
        return *this;
    }

    OfflineParameters & with_iterative_tolerance(double iterative_tolerance)
    {
        this->iterative_tolerance = iterative_tolerance;
        return *this;
    }
};

struct OnlineParameters
{
    double end_time;
    uint64_t sample_count;
    int load_quadrature_strength;
    bool use_coarse_rhs;
    bool use_coarse_mass_matrix;
    double iterative_tolerance;
    bool measure_error;
    double max_error_before_abort;

    std::string integrator_name;

    OnlineParameters()
            : end_time(0.0),
              sample_count(0),
              load_quadrature_strength(4),
              use_coarse_rhs(false),
              use_coarse_mass_matrix(false),
              iterative_tolerance(10.0 * std::numeric_limits<double>::epsilon()),
              measure_error(true),
              max_error_before_abort(std::numeric_limits<double>::max())
    {}

    OnlineParameters & with_end_time(double end_time)
    {
        this->end_time = end_time;
        return *this;
    }

    OnlineParameters & with_sample_count(uint64_t sample_count)
    {
        this->sample_count = sample_count;
        return *this;
    }

    OnlineParameters & with_integrator_name(std::string name)
    {
        this->integrator_name = name;
        return *this;
    }

    OnlineParameters & with_coarse_rhs(bool use_coarse_rhs)
    {
        this->use_coarse_rhs = use_coarse_rhs;
        return *this;
    }

    OnlineParameters & with_coarse_mass_matrix(bool use_coarse_mass_matrix)
    {
        this->use_coarse_mass_matrix = use_coarse_mass_matrix;
        return *this;
    }

    OnlineParameters & with_iterative_tolerance(double iterative_tolerance)
    {
        this->iterative_tolerance = iterative_tolerance;
        return *this;
    }

    OnlineParameters & with_error_measurement(bool measure_error)
    {
        this->measure_error = measure_error;
        return *this;
    }

    double dt() const
    {
        return end_time / (sample_count - 1);
    }
};

struct ExperimentInput
{
    std::string                         name;
    OfflineParameters                   offline_parameters;
    boost::optional<OnlineParameters>   online_parameters;

    ExperimentInput & with_name(std::string name)
    {
        this->name = name;
        return *this;
    }

    ExperimentInput & with_offline_parameters(OfflineParameters parameters)
    {
        this->offline_parameters = parameters;
        return *this;
    }

    ExperimentInput & with_online_parameters(OnlineParameters parameters)
    {
        this->online_parameters = parameters;
        return *this;
    }
};

struct MeshDetails
{
    int num_coarse_vertices;
    int num_coarse_elements;
    int num_fine_vertices;
    int num_fine_elements;

    // h is diameter of element
    double h_max_coarse;
    double h_min_coarse;
    double h_max_fine;
    double h_min_fine;

    MeshDetails() : num_coarse_vertices(0),
                    num_coarse_elements(0),
                    num_fine_vertices(0),
                    num_fine_elements(0),
                    h_max_coarse(0.0),
                    h_min_coarse(0.0),
                    h_max_fine(0.0),
                    h_min_fine(0.0)
    { }
};

std::pair<double, double> extremal_diameters(const crest::IndexedMesh<double, int> & mesh)
{
    double hmin_squared = std::numeric_limits<double>::max();
    double hmax_squared = 0.0;

    for (int element = 0; element < mesh.num_elements(); ++element)
    {
        const auto triangle = mesh.triangle_for(element);
        const auto diameter_squared = crest::diameter_squared(triangle);
        if (diameter_squared > hmax_squared) {
            hmax_squared = diameter_squared;
        }
        if (diameter_squared < hmin_squared) {
            hmin_squared = diameter_squared;
        }
    }

    return std::make_pair(std::sqrt(hmin_squared), std::sqrt(hmax_squared));
};

MeshDetails details_from_meshes(const crest::IndexedMesh<double, int> & coarse,
                                const crest::IndexedMesh<double, int> & fine)
{
    MeshDetails details;
    details.num_coarse_elements = coarse.num_elements();
    details.num_coarse_vertices = coarse.num_vertices();
    details.num_fine_elements = fine.num_elements();
    details.num_fine_vertices = fine.num_vertices();

    std::tie(details.h_min_coarse, details.h_max_coarse) = extremal_diameters(coarse);
    std::tie(details.h_min_fine, details.h_max_fine) = extremal_diameters(fine);

    return details;
}

struct ErrorSummary
{
    double l2;
    double h1;
    double h1_semi;

    ErrorSummary & with_l2(double error)
    {
        this->l2 = error;
        return *this;
    }

    ErrorSummary & with_h1(double error)
    {
        this->h1 = error;
        return *this;
    }

    ErrorSummary & with_h1_semi(double error)
    {
        this->h1_semi = error;
        return *this;
    }
};

struct OfflineTiming
{
    double mesh_construction;
    double basis_construction;
    double total;

    OfflineTiming() : mesh_construction(0.0), basis_construction(0.0), total(0.0) {}
};

struct OfflineResult
{
    MeshDetails mesh_details;
    OfflineTiming timing;

    std::unordered_map<std::string, crest::AccumulatedDensityHistogram> stats;

    OfflineResult & with_mesh_details(MeshDetails mesh_details)
    {
        this->mesh_details = mesh_details;
        return *this;
    }

    OfflineResult & with_timing(OfflineTiming timing)
    {
        this->timing = timing;
        return *this;
    }

    OfflineResult & with_stats(std::unordered_map<std::string, crest::AccumulatedDensityHistogram> stats)
    {
        this->stats = stats;
        return *this;
    }
};

typedef crest::wave::SolveTiming OnlineTiming;

struct OnlineResult
{
    ErrorSummary error_summary;
    OnlineTiming timing;
    bool converged;

    OnlineResult & with_error_summary(ErrorSummary summary)
    {
        this->error_summary = summary;
        return *this;
    }

    OnlineResult & with_timing(OnlineTiming timing)
    {
        this->timing = timing;
        return *this;
    }

    OnlineResult & with_convergence(bool converged)
    {
        this->converged = converged;
        return *this;
    }

    OnlineResult() : converged(false) {}
};

struct ExperimentResult
{
    std::string                         name;
    OfflineParameters                   offline_parameters;
    boost::optional<OnlineParameters>   online_parameters;
    OfflineResult                       offline_result;
    boost::optional<OnlineResult>       online_result;

    ExperimentResult & with_name(std::string name)
    {
        this->name = name;
        return *this;
    }

    ExperimentResult & with_offline_parameters(OfflineParameters param)
    {
        this->offline_parameters = param;
        return *this;
    }

    ExperimentResult & with_online_parameters(OnlineParameters param)
    {
        this->online_parameters = param;
        return *this;
    }

    ExperimentResult & with_online_parameters(boost::optional<OnlineParameters> param)
    {
        this->online_parameters = param;
        return *this;
    }

    ExperimentResult & with_offline_result(OfflineResult result)
    {
        this->offline_result = result;
        return *this;
    }

    ExperimentResult & with_online_result(OnlineResult result)
    {
        this->online_result = result;
        return *this;
    }

    ExperimentResult & with_online_result(boost::optional<OnlineResult> result)
    {
        this->online_result = result;
        return *this;
    }
};

void verify_parameter_validity(const OfflineParameters & parameters);
void verify_parameter_validity(const OnlineParameters & parameters);

class Experiment
{
public:
    virtual ~Experiment() {}

    OfflineResult run_offline(const OfflineParameters & parameters)
    {
        crest::Timer timer;
        verify_parameter_validity(parameters);
        auto result = solve_offline(parameters);
        result.timing.total = timer.elapsed();
        return result;
    }

    OnlineResult run_online(const OnlineParameters & parameters,
                            crest::wave::Integrator<double> & integrator)
    {
        verify_parameter_validity(parameters);
        return solve_online(parameters, integrator);
    }

protected:
    virtual OfflineResult solve_offline(const OfflineParameters & parameters) = 0;
    virtual OnlineResult solve_online(const OnlineParameters & parameters,
                                      crest::wave::Integrator<double> & integrator) = 0;

    /**
     * Helper function for implementing solve()
     */
    template <typename BasisImpl, typename Function2dt, typename Function2dt_x, typename Function2dt_y>
    OnlineResult solve_and_analyze(const Function2dt & u,
                                   const Function2dt_x & u_x,
                                   const Function2dt_y & u_y,
                                   const OnlineParameters & parameters,
                                   const crest::Basis<double, BasisImpl> & basis,
                                   const crest::wave::InitialConditions<double> & initial_conditions,
                                   const crest::wave::ConstrainedSystem<double> & system,
                                   crest::wave::Integrator<double> & integrator,
                                   const crest::wave::Initializer<double> & initializer,
                                   double assembly_time) const
    {
        const auto dt = parameters.dt();
        crest::wave::Parameters<double> param;
        param.num_samples = parameters.sample_count;
        param.dt = dt;

        if (parameters.measure_error) {
            const auto error_transformer = crest::wave::make_error_transformer<4>(
                    basis, u, u_x, u_y, parameters.max_error_before_abort);
            auto result = crest::wave::solve(system, initial_conditions,
                                             integrator, initializer, param, error_transformer);

            // Augment the result with the given assembly time. Yes, I know, this is very hacky and
            // very poorly designed (last minute additions)
            result.timing.assembly_time = assembly_time;

            if (result.converged) {

                std::vector<double> l2_error_at_each_step;
                std::vector<double> h1_semi_error_at_each_step;
                std::vector<double> h1_error_at_each_step;

                for (const auto sample_error : result.result)
                {
                    l2_error_at_each_step.push_back(sample_error.l2);
                    h1_semi_error_at_each_step.push_back(sample_error.h1_semi);
                    h1_error_at_each_step.push_back(sample_error.h1);
                }

                ErrorSummary errors;

                switch (param.num_samples) {
                    case 1:
                        errors.h1 = h1_error_at_each_step.back();
                        errors.l2 = l2_error_at_each_step.back();
                        errors.h1_semi = h1_semi_error_at_each_step.back();
                        break;

                    default:
                        // TODO: Support even number of samples. Simpsons will currently throw when it
                        // is given an even number of samples
                        errors.l2 = crest::composite_simpsons(l2_error_at_each_step.begin(),
                                                              l2_error_at_each_step.end(),
                                                              dt);
                        errors.h1_semi = crest::composite_simpsons(h1_semi_error_at_each_step.begin(),
                                                                   h1_semi_error_at_each_step.end(),
                                                                   dt);
                        errors.h1 = crest::composite_simpsons(h1_error_at_each_step.begin(),
                                                              h1_error_at_each_step.end(),
                                                              dt);
                        break;
                }

                return OnlineResult()
                        .with_error_summary(errors)
                        .with_timing(result.timing)
                        .with_convergence(result.converged);
            } else {
                // If the simulation did not converge, we do not want to compute an error at all,
                // particularly because we might have an odd number of samples (in which case the above would throw)
                // This last part is of course unfortunate, but I don't want to spend time on fixing that at the moment.
                return OnlineResult()
                        .with_timing(result.timing)
                        .with_convergence(result.converged);
            }

        } else {
            const auto ignore_transformer = crest::wave::IgnoreTransformer<double>();
            auto result = crest::wave::solve(system, initial_conditions, integrator,
                                             initializer, param, ignore_transformer);
            // Augment the result with the given assembly time. Yes, I know, this is very hacky and
            // very poorly designed (last minute additions)
            result.timing.assembly_time = assembly_time;
            return OnlineResult()
                    .with_timing(result.timing)
                    .with_convergence(result.converged);
        };
    };
};

inline void verify_parameter_validity(const OnlineParameters & parameters)
{
    if (parameters.end_time <= 0.0 || !std::isfinite(parameters.end_time))
    {
        throw std::invalid_argument("Final time T must be finite and greater than zero.");
    }

    if (parameters.sample_count < 1) {
        throw std::invalid_argument("Number of samples N must be 1 or greater.");
    }

    if (parameters.sample_count % 2 == 0) {
        throw std::invalid_argument("Number of samples must be an odd number.");
    }
}

inline void verify_parameter_validity(const OfflineParameters & parameters)
{
    if (parameters.mesh_resolution <= 0.0 || !std::isfinite(parameters.mesh_resolution))
    {
        throw std::invalid_argument("Spatial resolution h must be finite and greater than zero.");
    }
}


// Copyright (c) 2020, Ryo Currency Project
// Portions copyright (c) 2016, Monero Research Labs
//
// Author: Shen Noether <shen.noether@gmx.com>
//
// Portions of this file are available under BSD-3 license. Please see ORIGINAL-LICENSE for details
// All rights reserved.
//
// Authors and copyright holders give permission for following:
//
// 1. Redistribution and use in source and binary forms WITHOUT modification.
//
// 2. Modification of the source form for your own personal use.
//
// As long as the following conditions are met:
//
// 3. You must not distribute modified copies of the work to third parties. This includes
//    posting the work online, or hosting copies of the modified work for download.
//
// 4. Any derivative version of this work is also covered by this license, including point 8.
//
// 5. Neither the name of the copyright holders nor the names of the authors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// 6. You agree that this licence is governed by and shall be construed in accordance
//    with the laws of England and Wales.
//
// 7. You agree to submit all disputes arising out of or in connection with this licence
//    to the exclusive jurisdiction of the Courts of England and Wales.
//
// Authors and copyright holders agree that:
//
// 8. This licence expires and the work covered by it is released into the
//    public domain on 1st of February 2021
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "rctOps.h"
#include "common/gulps.hpp"
#include <boost/lexical_cast.hpp>
using namespace crypto;
using namespace std;

//undef RYO_DEFAULT_LOG_CATEGORY
//#define RYO_DEFAULT_LOG_CATEGORY

#define CHECK_AND_ASSERT_THROW_MES_L1(expr, ...) \
	{                                                \
		if(!(expr))                                  \
		{                                            \
			std::string str;	\
			str = stream_writer::write(__VA_ARGS__);	\
			{GULPS_CAT_MAJOR("rctOps"); GULPS_WARN(str);}                       \
			throw std::runtime_error(str);       \
		}                                            \
	}

namespace rct
{

//Various key initialization functions

//initializes a key matrix;
//first parameter is rows,
//second is columns
keyM keyMInit(size_t rows, size_t cols)
{
	keyM rv(cols);
	size_t i = 0;
	for(i = 0; i < cols; i++)
	{
		rv[i] = keyV(rows);
	}
	return rv;
}

//Various key generation functions

//generates a random scalar which can be used as a secret key or mask
void skGen(key &sk)
{
	random_scalar(sk.bytes);
}

//generates a random scalar which can be used as a secret key or mask
key skGen()
{
	key sk;
	skGen(sk);
	return sk;
}

//Generates a vector of secret key
//Mainly used in testing
keyV skvGen(size_t rows)
{
	GULPS_CAT_MAJOR("rctOps");
	GULPS_CHECK_AND_ASSERT_THROW_MES(rows > 0, "0 keys requested");
	keyV rv(rows);
	for(size_t i = 0; i < rows; i++)
		skGen(rv[i]);
	return rv;
}

//generates a random curve point (for testing)
key pkGen()
{
	key sk = skGen();
	key pk = scalarmultBase(sk);
	return pk;
}

//generates a random secret and corresponding public key
void skpkGen(key &sk, key &pk)
{
	skGen(sk);
	scalarmultBase(pk, sk);
}

//generates a random secret and corresponding public key
tuple<key, key> skpkGen()
{
	key sk = skGen();
	key pk = scalarmultBase(sk);
	return make_tuple(sk, pk);
}

//generates C =aG + bH from b, a is given..
void genC(key &C, const key &a, ryo_amount amount)
{
	key bH = scalarmultH(d2h(amount));
	addKeys1(C, a, bH);
}

//generates a <secret , public> / Pedersen commitment to the amount
tuple<ctkey, ctkey> ctskpkGen(ryo_amount amount)
{
	ctkey sk, pk;
	skpkGen(sk.dest, pk.dest);
	skpkGen(sk.mask, pk.mask);
	key am = d2h(amount);
	key bH = scalarmultH(am);
	addKeys(pk.mask, pk.mask, bH);
	return make_tuple(sk, pk);
}

//generates a <secret , public> / Pedersen commitment but takes bH as input
tuple<ctkey, ctkey> ctskpkGen(const key &bH)
{
	ctkey sk, pk;
	skpkGen(sk.dest, pk.dest);
	skpkGen(sk.mask, pk.mask);
	addKeys(pk.mask, pk.mask, bH);
	return make_tuple(sk, pk);
}

key zeroCommit(ryo_amount amount)
{
	key am = d2h(amount);
	key bH = scalarmultH(am);
	return addKeys(G, bH);
}

key commit(ryo_amount amount, const key &mask)
{
	key c = scalarmultBase(mask);
	key am = d2h(amount);
	key bH = scalarmultH(am);
	addKeys(c, c, bH);
	return c;
}

//generates a random uint long long (for testing)
ryo_amount randRyoAmount(ryo_amount upperlimit)
{
	return h2d(skGen()) % (upperlimit);
}

//Scalar multiplications of curve points

//does a * G where a is a scalar and G is the curve basepoint
void scalarmultBase(key &aG, const key &a)
{
	ge_p3 point;
	sc_reduce32copy(aG.bytes, a.bytes); //do this beforehand!
	ge_scalarmult_base(&point, aG.bytes);
	ge_p3_tobytes(aG.bytes, &point);
}

//does a * G where a is a scalar and G is the curve basepoint
key scalarmultBase(const key &a)
{
	ge_p3 point;
	key aG;
	sc_reduce32copy(aG.bytes, a.bytes); //do this beforehand
	ge_scalarmult_base(&point, aG.bytes);
	ge_p3_tobytes(aG.bytes, &point);
	return aG;
}

//does a * P where a is a scalar and P is an arbitrary point
void scalarmultKey(key &aP, const key &P, const key &a)
{
	ge_p3 A;
	ge_p2 R;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_scalarmult(&R, a.bytes, &A);
	ge_tobytes(aP.bytes, &R);
}

//does a * P where a is a scalar and P is an arbitrary point
key scalarmultKey(const key &P, const key &a)
{
	ge_p3 A;
	ge_p2 R;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A, P.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_scalarmult(&R, a.bytes, &A);
	key aP;
	ge_tobytes(aP.bytes, &R);
	return aP;
}

//Computes 8P
key scalarmult8(const key & P)
{
	ge_p3 p3;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&p3, P.bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
	ge_p2 p2;
	ge_p3_to_p2(&p2, &p3);
	ge_p1p1 p1;
	ge_mul8(&p1, &p2);
	ge_p1p1_to_p2(&p2, &p1);
	rct::key res;
	ge_tobytes(res.bytes, &p2);
	return res;
}

//Computes aH where H= toPoint(cn_fast_hash(G)), G the basepoint
key scalarmultH(const key &a)
{
	ge_p3 A;
	ge_p2 R;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A, H.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_scalarmult(&R, a.bytes, &A);
	key aP;
	ge_tobytes(aP.bytes, &R);
	return aP;
}

//Curve addition / subtractions

//for curve points: AB = A + B
void addKeys(key &AB, const key &A, const key &B)
{
	ge_p3 B2, A2;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_cached tmp2;
	ge_p3_to_cached(&tmp2, &B2);
	ge_p1p1 tmp3;
	ge_add(&tmp3, &A2, &tmp2);
	ge_p1p1_to_p3(&A2, &tmp3);
	ge_p3_tobytes(AB.bytes, &A2);
}

rct::key addKeys(const key &A, const key &B)
{
	key k;
	addKeys(k, A, B);
	return k;
}

rct::key addKeys(const keyV &A)
{
	if (A.empty())
		return rct::identity();

	ge_p3 p3, tmp;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&p3, A[0].bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
	for (size_t i = 1; i < A.size(); ++i)
	{
		CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&tmp, A[i].bytes) == 0, "ge_frombytes_vartime failed at "+boost::lexical_cast<std::string>(__LINE__));
		ge_cached p2;
		ge_p3_to_cached(&p2, &tmp);
		ge_p1p1 p1;
		ge_add(&p1, &p3, &p2);
		ge_p1p1_to_p3(&p3, &p1);
	}

	rct::key res;
	ge_p3_tobytes(res.bytes, &p3);
	return res;
}

//addKeys1
//aGB = aG + B where a is a scalar, G is the basepoint, and B is a point
void addKeys1(key &aGB, const key &a, const key &B)
{
	key aG = scalarmultBase(a);
	addKeys(aGB, aG, B);
}

//addKeys2
//aGbB = aG + bB where a, b are scalars, G is the basepoint and B is a point
void addKeys2(key &aGbB, const key &a, const key &b, const key &B)
{
	ge_p2 rv;
	ge_p3 B2;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_double_scalarmult_base_vartime(&rv, b.bytes, &B2, a.bytes);
	ge_tobytes(aGbB.bytes, &rv);
}

//Does some precomputation to make addKeys3 more efficient
// input B a curve point and output a ge_dsmp which has precomputation applied
void precomp(ge_dsmp rv, const key &B)
{
	ge_p3 B2;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_dsm_precomp(rv, &B2);
}

//addKeys3
//aAbB = a*A + b*B where a, b are scalars, A, B are curve points
//B must be input after applying "precomp"
void addKeys3(key &aAbB, const key &a, const key &A, const key &b, const ge_dsmp B)
{
	ge_p2 rv;
	ge_p3 A2;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_double_scalarmult_precomp_vartime(&rv, a.bytes, &A2, b.bytes, B);
	ge_tobytes(aAbB.bytes, &rv);
}

//addKeys3
//aAbB = a*A + b*B where a, b are scalars, A, B are curve points
//A and B must be input after applying "precomp"
void addKeys3(key &aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B)
{
	ge_p2 rv;
	ge_double_scalarmult_precomp_vartime2(&rv, a.bytes, A, b.bytes, B);
	ge_tobytes(aAbB.bytes, &rv);
}

//subtract Keys (subtracts curve points)
//AB = A - B where A, B are curve points
void subKeys(key &AB, const key &A, const key &B)
{
	ge_p3 B2, A2;
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&B2, B.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&A2, A.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_cached tmp2;
	ge_p3_to_cached(&tmp2, &B2);
	ge_p1p1 tmp3;
	ge_sub(&tmp3, &A2, &tmp2);
	ge_p1p1_to_p3(&A2, &tmp3);
	ge_p3_tobytes(AB.bytes, &A2);
}

//checks if A, B are equal in terms of bytes (may say no if one is a non-reduced scalar)
//without doing curve operations
bool equalKeys(const key &a, const key &b)
{
	bool rv = true;
	for(int i = 0; i < 32; ++i)
	{
		if(a.bytes[i] != b.bytes[i])
		{
			rv = false;
		}
	}
	return rv;
}

//Hashing - cn_fast_hash
//be careful these are also in crypto namespace
//cn_fast_hash for arbitrary multiples of 32 bytes
void cn_fast_hash(key &hash, const void *data, const std::size_t l)
{
	keccak((const uint8_t *)data, l, hash.bytes, 32);
}

void hash_to_scalar(key &hash, const void *data, const std::size_t l)
{
	cn_fast_hash(hash, data, l);
	sc_reduce32(hash.bytes);
}

//cn_fast_hash for a 32 byte key
void cn_fast_hash(key &hash, const key &in)
{
	keccak((const uint8_t *)in.bytes, 32, hash.bytes, 32);
}

void hash_to_scalar(key &hash, const key &in)
{
	cn_fast_hash(hash, in);
	sc_reduce32(hash.bytes);
}

//cn_fast_hash for a 32 byte key
key cn_fast_hash(const key &in)
{
	key hash;
	keccak((const uint8_t *)in.bytes, 32, hash.bytes, 32);
	return hash;
}

key hash_to_scalar(const key &in)
{
	key hash = cn_fast_hash(in);
	sc_reduce32(hash.bytes);
	return hash;
}

//cn_fast_hash for a 128 byte unsigned char
key cn_fast_hash128(const void *in)
{
	key hash;
	keccak((const uint8_t *)in, 128, hash.bytes, 32);
	return hash;
}

key hash_to_scalar128(const void *in)
{
	key hash = cn_fast_hash128(in);
	sc_reduce32(hash.bytes);
	return hash;
}

//cn_fast_hash for multisig purpose
//This takes the outputs and commitments
//and hashes them into a 32 byte sized key
key cn_fast_hash(const ctkeyV &PC)
{
	if(PC.empty())
		return rct::hash2rct(crypto::cn_fast_hash("", 0));
	key rv;
	cn_fast_hash(rv, &PC[0], 64 * PC.size());
	return rv;
}

key hash_to_scalar(const ctkeyV &PC)
{
	key rv = cn_fast_hash(PC);
	sc_reduce32(rv.bytes);
	return rv;
}

//cn_fast_hash for a key-vector of arbitrary length
//this is useful since you take a number of keys
//put them in the key vector and it concatenates them
//and then hashes them
key cn_fast_hash(const keyV &keys)
{
	if(keys.empty())
		return rct::hash2rct(crypto::cn_fast_hash("", 0));
	key rv;
	cn_fast_hash(rv, &keys[0], keys.size() * sizeof(keys[0]));
	//dp(rv);
	return rv;
}

key hash_to_scalar(const keyV &keys)
{
	key rv = cn_fast_hash(keys);
	sc_reduce32(rv.bytes);
	return rv;
}

key cn_fast_hash(const key64 keys)
{
	key rv;
	cn_fast_hash(rv, &keys[0], 64 * sizeof(keys[0]));
	//dp(rv);
	return rv;
}

key hash_to_scalar(const key64 keys)
{
	key rv = cn_fast_hash(keys);
	sc_reduce32(rv.bytes);
	return rv;
}

key hashToPointSimple(const key &hh)
{
	key pointk;
	ge_p1p1 point2;
	ge_p2 point;
	ge_p3 res;
	key h = cn_fast_hash(hh);
	CHECK_AND_ASSERT_THROW_MES_L1(ge_frombytes_vartime(&res, h.bytes) == 0, "ge_frombytes_vartime failed at " + boost::lexical_cast<std::string>(__LINE__));
	ge_p3_to_p2(&point, &res);
	ge_mul8(&point2, &point);
	ge_p1p1_to_p3(&res, &point2);
	ge_p3_tobytes(pointk.bytes, &res);
	return pointk;
}

key hashToPoint(const key &hh)
{
	key pointk;
	ge_p2 point;
	ge_p1p1 point2;
	ge_p3 res;
	key h = cn_fast_hash(hh);
	ge_fromfe_frombytes_vartime(&point, h.bytes);
	ge_mul8(&point2, &point);
	ge_p1p1_to_p3(&res, &point2);
	ge_p3_tobytes(pointk.bytes, &res);
	return pointk;
}

void hashToPoint(key &pointk, const key &hh)
{
	ge_p2 point;
	ge_p1p1 point2;
	ge_p3 res;
	key h = cn_fast_hash(hh);
	ge_fromfe_frombytes_vartime(&point, h.bytes);
	ge_mul8(&point2, &point);
	ge_p1p1_to_p3(&res, &point2);
	ge_p3_tobytes(pointk.bytes, &res);
}

//sums a vector of curve points (for scalars use sc_add)
void sumKeys(key &Csum, const keyV &Cis)
{
	identity(Csum);
	size_t i = 0;
	for(i = 0; i < Cis.size(); i++)
	{
		addKeys(Csum, Csum, Cis[i]);
	}
}

//Elliptic Curve Diffie Helman: encodes and decodes the amount b and mask a
// where C= aG + bH
void ecdhEncode(ecdhTuple &unmasked, const key &sharedSec)
{
	key sharedSec1 = hash_to_scalar(sharedSec);
	key sharedSec2 = hash_to_scalar(sharedSec1);
	//encode
	sc_add(unmasked.mask.bytes, unmasked.mask.bytes, sharedSec1.bytes);
	sc_add(unmasked.amount.bytes, unmasked.amount.bytes, sharedSec2.bytes);
}
void ecdhDecode(ecdhTuple &masked, const key &sharedSec)
{
	key sharedSec1 = hash_to_scalar(sharedSec);
	key sharedSec2 = hash_to_scalar(sharedSec1);
	//decode
	sc_sub(masked.mask.bytes, masked.mask.bytes, sharedSec1.bytes);
	sc_sub(masked.amount.bytes, masked.amount.bytes, sharedSec2.bytes);
}
}


#ifndef GEODB_ALGORITHM_HPP
#define GEODB_ALGORITHM_HPP

#include "geodb/common.hpp"

#include <boost/range/concepts.hpp>
#include <boost/range/metafunctions.hpp>
#include <boost/range/sub_range.hpp>

#include <algorithm>
#include <map>
#include <queue>
#include <vector>

/// \file
/// Some generic algorithms.

namespace geodb {

/// Invokes the function `f` for every token in `r`.
/// The end of a token is reached when
///     - the end of the range was encountered
///     - the predicate `p' returns `true`
///
/// `p` will be invoked with the iterator to the current element,
/// which is always valid.
///
/// `f` will be invoked for each token with an argument of type
/// `boost::sub_range<Range>`.
template<typename Range, typename Predicate, typename Function>
void for_each_token_if(Range&& r, Predicate&& p, Function&& f) {
    using iterator = typename boost::range_iterator<Range>::type;
    using sub_range = boost::sub_range<Range>;

    iterator first = boost::begin(r);
    iterator last = boost::end(r);
    if (first == last)
        return;

    while (1) {
        // There is at least one more token, although it may be empty.
        auto pos = first;

        // Find the end of the current token.
        // This is either the end of the range or the item
        // for which p returns true.
        // If p returns true, at least one more token must follow.
        while (1) {
            if (pos == last) {
                f(sub_range(first, pos));
                return;
            }
            if (p(pos)) {
                f(sub_range(first, pos));
                ++pos; // skip separator in output.
                break;
            }
            ++pos;
        }

        first = pos;
    }
}

/// Invokes the function `f` for every token in `r`.
/// Tokens are separated by the seperator element.
template<typename Range, typename Function>
void for_each_token(Range&& r, typename boost::range_value<Range>::type separator, Function&& f) {
    auto predicate = [&](const auto& iter) {
        return *iter == separator;
    };
    return for_each_token_if(std::forward<Range>(r), predicate, std::forward<Function>(f));
}

/// Takes the `k` smallest entries (according to `cmp`) and puts then into `out`.
/// The first `k` elements of `out` will be in sorted order.
/// Runtime complexity: O(n * log(k)) comparisons.
///
/// \pre `k > 0`.
/// \pre `r` must have at least `k` elements.
/// \pre `out` must have space for `k` elements.
template<typename Range, typename OutRange, typename Compare>
void k_smallest(Range&& r, size_t k, OutRange& out, Compare&& cmp) {
    BOOST_CONCEPT_ASSERT(( boost::RandomAccessRangeConcept<OutRange> ));

    geodb_assert(k > 0, "invalid k");
    geodb_assert(boost::size(out) >= k, "out range too small");

    auto heap_first = boost::begin(out);
    auto heap_last = heap_first + k;

    auto first = boost::const_begin(r);
    auto last = boost::const_end(r);

    // Push first k elements into the output heap.
    // Invariant: heap stores the k smallest elements in
    // the visited part of the range.
    {
        auto first_k = first + k;
        std::copy(first, first_k, heap_first);
        std::make_heap(heap_first, heap_last, cmp);
        first = first_k;
    }

    // If the next element is smaller than the max,
    // then replace the max with it. Otherwise: the element is
    // definitely not among the smallest k.
    for (; first != last; ++first) {
        if(cmp(*first, *heap_first)) {
            std::pop_heap(heap_first, heap_last, cmp);
            heap_last[-1] = *first;
            std::push_heap(heap_first, heap_last, cmp);
        }
    }
    std::sort_heap(heap_first, heap_last, cmp);
}

/// Uses `operator<` for comparisons.
template<typename Range, typename OutRange>
void k_smallest(Range&& r, size_t k, OutRange&& out) {
    k_smallest(r, k, out, std::less<>());
}

/// Invokes the function object `f` for every pair of adjacent elements
/// in `r`.
///
/// For example, if `r = [1, 2, 3]`, this algorithm will
/// call `f(1, 2)` and `f(2, 3)`.
template<typename FwdRange, typename Function>
void for_each_adjacent(FwdRange&& r, Function&& f) {
    BOOST_CONCEPT_ASSERT(( boost::ForwardRangeConcept<FwdRange> ));

    auto current = boost::begin(r);
    auto last = boost::end(r);

    if (current != last) {
        auto previous = current;
        ++current;
        for (; current != last; ++current, ++previous) {
            f(*previous, *current);
        }
    }
}

/// Iterate over a range of ranges and treat their elements
/// as one large, contiguous sequence.
/// The elements will be visited in sorted order.
///
/// Uses a binary heap internally to track the position in each
/// range and thus uses O(m) additional space (where m is the number of ranges).
/// The algorithm thus takes O(n * log m) time, where n is the number of total
/// elements over all ranges.
///
/// \param ranges
///     A range of ranges. Every individual range must be sorted.
/// \param fn
///     A function that will be invoked for every element in the ranges.
/// \param comp
///     A comparison function to compare individual elements of the ranges.
///     Defaults to std::less.
template<typename Ranges, typename Function, typename Comp = std::less<>>
void for_each_sorted(const Ranges& ranges, Function&& fn, Comp&& comp = Comp()) {
    using nested_range = typename boost::range_value<const Ranges>::type;
    using nested_iterator = typename boost::range_const_iterator<nested_range>::type;

    static_assert(std::is_lvalue_reference<typename boost::range_reference<const Ranges>::type>::value,
                  "nested ranges must be lvalues (i.e. no temporaries).");

    struct cursor {
        nested_iterator current;
        nested_iterator end;

        cursor(nested_iterator current, nested_iterator end)
            : current(current), end(end)
        {}
    };

    struct cursor_compare {
        Comp& value_comp;

        cursor_compare(Comp& value_comp): value_comp(value_comp) {}

        bool operator()(const cursor& a, const cursor& b) const {
            return value_comp(*b.current, *a.current);
        }
    };

    auto heap = [&]{
        std::vector<cursor> cursors;
        cursors.reserve(boost::size(ranges));

        // Add a cursor for every range.
        for (auto&& range : ranges) {
            cursor c(boost::const_begin(range), boost::const_end(range));
            if (c.current != c.end) {
                cursors.push_back(std::move(c));
            }
        }
        return std::priority_queue<cursor,
                std::vector<cursor>,
                cursor_compare>(cursor_compare(comp), std::move(cursors));
    }();

    // Read the min value until we have seen everything.
    while (!heap.empty()) {
        cursor c = heap.top();
        heap.pop();

        fn(*c.current);
        ++c.current;
        if (c.current != c.end) {
            heap.push(std::move(c));
        }
    }
}

/// Creates a map of groups and their values.
/// All elements with the same key value will be put into the same group.
/// Relative order of elements within a group is preserved.
///
/// \param r        The input range.
/// \param key      A function that maps an element of `r` to a key value.
///
/// \return         A `map<key_type, std::vector<value_type>>` where `key_type`
///                 is the return type of `key`.
template<typename Range, typename KeyFunc>
auto group_by_key(Range&& r, KeyFunc&& key) {
    using value_type = typename boost::range_value<Range>::type;
    using key_type = decltype(key(std::declval<const value_type&>()));

    std::map<key_type, std::vector<value_type>> result;
    for (const auto& item : r) {
        result[key(item)].push_back(item);
    }
    return result;
}

/// Assign a range to a container.
template<typename Container, typename Range>
void assign(Container&& c, const Range& r) {
    c.assign(boost::begin(r), boost::end(r));
}

/// Append a range to a container.
template<typename Container, typename Range>
void append(Container&& c, const Range& r) {
    c.insert(c.end(), boost::begin(r), boost::end(r));
}

/// True iff `r` contains `t`.
template<typename Range, typename T>
bool contains(const Range& r, const T& t) {
    return std::find(boost::begin(r), boost::end(r), t) != boost::end(r);
}

/// True iff `p(x)` is true for every element `x` of `r`.
template<typename Range, typename Predicate>
bool all_of(const Range& r, Predicate p) {
    return std::any_of(boost::begin(r), boost::end(r), p);
}

/// Copy the contents of the given range into a new vector.
template<typename Range>
std::vector<typename boost::range_value<Range>::type> to_vector(const Range& r) {
    return std::vector<typename boost::range_value<Range>::type>(boost::begin(r), boost::end(r));
}

/// Removes the element at position `pos` from the vector `v`
/// by swapping the last element of `v` into the position
/// and decreasing the size of the vector.
/// In other words, the order of elements will not be maintained.
///
/// \pre `pos` points to an element, i.e. it is not `v.end()`.
template<typename Vector>
void fast_remove(Vector&& v, typename Vector::iterator pos) {
    geodb_assert(pos != v.end(), "trying to remove the end iterator");
    goedb_assert(!v.empty(), "trying to remove from an empty vector");
    const auto last = v.end() - 1;
    if (pos != last) {
        std::iter_swap(pos, last);
    }
    v.pop_back();
}

} // namespace geodb

#endif // GEODB_ALGORITHM_HPP


// Copyright 2013-2020 Lawrence Livermore National Security, LLC and other
// HavoqGT Project Developers. See the top-level LICENSE file for details.
//
// SPDX-License-Identifier: MIT

#ifndef HAVOQGT_MPI_TRIANGLE_COUNT_HPP_INCLUDED
#define HAVOQGT_MPI_TRIANGLE_COUNT_HPP_INCLUDED

#include <algorithm>
#include <boost/container/deque.hpp>
#include <fstream>
#include <havoqgt/visitor_queue.hpp>
#include <utility>
//#include <boost/container/flat_map.hpp>
#include <map>
#include <unordered_map>

namespace havoqgt {

struct dogr_edge {
  uint32_t target_degree       = 0;
  uint32_t edge_triangle_count = 0;
  bool     is_j                = false;
};

template <typename Visitor>
class lifo_queue {
 protected:
  std::vector<Visitor> m_data;

 public:
  lifo_queue() {}

  bool push(Visitor const& task) {
    m_data.push_back(task);
    return true;
  }

  void pop() { m_data.pop_back(); }

  Visitor const& top()  // const
  {
    return m_data.back();
  }

  size_t size() const {
    return m_data.size();
    ;
  }

  bool empty() const { return m_data.empty(); }

  void clear() { m_data.clear(); }
};

template <typename Graph>
class core2_visitor {
 public:
  typedef core2_visitor<Graph>           my_type;
  typedef typename Graph::vertex_locator vertex_locator;

  core2_visitor() : vertex(), init(true) {}

  core2_visitor(vertex_locator v) : vertex(v), init(true) {}

  core2_visitor(vertex_locator v, bool _init) : vertex(v), init(_init) {}

  template <typename AlgData>
  bool pre_visit(AlgData& alg_data) const {
    if (vertex.is_delegate()) {
      if (!vertex.is_delegate_master()) {
        return true;
      }
    }
    if (std::get<1>(alg_data)[vertex]) {
      --(std::get<0>(alg_data)[vertex]);

      if (std::get<0>(alg_data)[vertex] < 2) {
        // remove from 2 core
        std::get<1>(alg_data)[vertex] = false;
        std::get<0>(alg_data)[vertex] = 0;
        return true;
      }
    }

    // Retrun true if alive
    // return std::get<1>(alg_data)[vertex];
    return false;
  }

  template <typename VisitorQueueHandle, typename AlgData>
  bool init_visit(Graph& g, VisitorQueueHandle vis_queue,
                  AlgData& alg_data) const {
    if (std::get<1>(alg_data)[vertex]) {
      //   --(std::get<0>(alg_data)[vertex]);
      if (std::get<0>(alg_data)[vertex] < 2) {
        // remove from 2 core
        std::get<1>(alg_data)[vertex] = false;
        std::get<0>(alg_data)[vertex] = 0;
        for (auto eitr = g.edges_begin(vertex); eitr != g.edges_end(vertex);
             ++eitr) {
          vertex_locator neighbor = eitr.target();
          my_type        new_visitor(neighbor, false);
          vis_queue->queue_visitor(new_visitor);
        }
      }
      return true;
    }
    return false;
  }

  template <typename VisitorQueueHandle, typename AlgData>
  bool visit(Graph& g, VisitorQueueHandle vis_queue, AlgData& alg_data) const {
    if (init) {
      if (std::get<0>(alg_data)[vertex] < 2) {
        // remove from 2 core
        std::get<1>(alg_data)[vertex] = false;
        std::get<0>(alg_data)[vertex] = 0;
        for (auto eitr = g.edges_begin(vertex); eitr != g.edges_end(vertex);
             ++eitr) {
          vertex_locator neighbor = eitr.target();
          my_type        new_visitor(neighbor, false);
          vis_queue->queue_visitor(new_visitor);
        }
      }
      return true;
    }

    if (std::get<1>(alg_data)[vertex]) {
      std::cerr << "LOGIC ERROR" << std::endl;
      exit(-1);
    }
    // if(std::get<1>(alg_data)[vertex]) {
    //  --(std::get<0>(alg_data)[vertex]);
    //  if(std::get<0>(alg_data)[vertex] < 2) {
    //    //remove from 2 core
    //    std::get<1>(alg_data)[vertex] = false;
    //    std::get<0>(alg_data)[vertex] = 0;
    for (auto eitr = g.edges_begin(vertex); eitr != g.edges_end(vertex);
         ++eitr) {
      vertex_locator neighbor = eitr.target();
      my_type        new_visitor(neighbor, false);
      vis_queue->queue_visitor(new_visitor);
    }
    //  }
    //  return true;
    //}
    // return false;
    return true;
  }

  friend inline bool operator>(const core2_visitor& v1,
                               const core2_visitor& v2) {
    return false;
  }

  friend inline bool operator<(const core2_visitor& v1,
                               const core2_visitor& v2) {
    return false;
  }

  vertex_locator vertex;
  bool           init;
};

template <typename Graph>
class directed_core2 {
 public:
  typedef directed_core2<Graph>          my_type;
  typedef typename Graph::vertex_locator vertex_locator;

  directed_core2() : vertex(), init(true) {}

  directed_core2(vertex_locator v) : vertex(v), init(true) {}

  directed_core2(vertex_locator v, vertex_locator _from, uint32_t _from_degree)
      : vertex(v), from_label(_from), from_degree(_from_degree), init(false) {}

  template <typename AlgData>
  bool pre_visit(AlgData& alg_data) const {
    // if(std::get<0>(alg_data)[vertex] >= 2) {
    if (from_degree >= std::get<2>(alg_data).degree(
                           vertex) /*std::get<0>(alg_data)[vertex]*/) {
      // previously returned true, but changing here --- return true;
      if (vertex.is_delegate()) {
        if (!vertex.is_delegate_master()) {
          return true;
        }
      }
      if (/*std::get<0>(alg_data)[vertex]*/ std::get<2>(alg_data).degree(
              vertex) < 2)
        return false;
      // only here should be low-degree & masters
      if (from_degree > std::get<2>(alg_data).degree(
                            vertex) /*std::get<0>(alg_data)[vertex]*/
          || (from_degree ==
                  std::get<2>(alg_data).degree(
                      vertex) /*std::get<0>(alg_data)[vertex]*/
              && vertex < from_label)) {
        auto vv = vertex;
        vv.set_bcast(0);
        vv.set_intercept(0);
        auto fl = from_label;
        fl.set_bcast(0);
        fl.set_intercept(0);

        std::get<1>(alg_data)[vv][fl].target_degree = from_degree;
        // std::get<1>(alg_data)[vv].add(fl, dogr_edge{from_degree, 0, 0});
        // attempting to replicate dogr graph
        if (vertex.is_delegate()) {
          if (vertex.is_delegate_master()) {
            return true;
          }
        }
      }
    }
    //}
    return false;
  }

  template <typename VisitorQueueHandle, typename AlgData>
  bool init_visit(Graph& g, VisitorQueueHandle vis_queue,
                  AlgData& alg_data) const {
    if (/*std::get<0>(alg_data)[vertex]*/ g.degree(vertex) >= 2) {
      // if in 2core, send degree to neighbors
      uint32_t my_degree = /*std::get<0>(alg_data)[vertex];*/ g.degree(vertex);
      for (auto eitr = g.edges_begin(vertex); eitr != g.edges_end(vertex);
           ++eitr) {
        vertex_locator neighbor = eitr.target();
        if (neighbor == vertex) continue;
        my_type new_visitor(neighbor, vertex, my_degree);
        vis_queue->queue_visitor(new_visitor);
      }
      return true;
    }
    return false;
  }

  template <typename VisitorQueueHandle, typename AlgData>
  bool visit(Graph& g, VisitorQueueHandle vis_queue, AlgData& alg_data) const {
    if (init) {
      if (/*std::get<0>(alg_data)[vertex]*/ g.degree(vertex) >= 2) {
        // if in 2core, send degree to neighbors
        uint32_t my_degree =
            /*std::get<0>(alg_data)[vertex];*/ g.degree(vertex);
        for (auto eitr = g.edges_begin(vertex); eitr != g.edges_end(vertex);
             ++eitr) {
          vertex_locator neighbor = eitr.target();
          if (neighbor == vertex) continue;
          my_type new_visitor(neighbor, vertex, my_degree);
          vis_queue->queue_visitor(new_visitor);
        }
        return true;
      } else {
        return false;
      }
    } else {
      auto vv = vertex;
      vv.set_bcast(0);
      vv.set_intercept(0);
      auto fl = from_label;
      fl.set_bcast(0);
      fl.set_intercept(0);

      std::get<1>(alg_data)[vv][fl].target_degree = from_degree;
      // std::get<1>(alg_data)[vv].add(fl, dogr_edge{from_degree, 0, 0});
      return true;
    }

    //    if(from_degree > g.degree(vertex) /*std::get<0>(alg_data)[vertex]*/ ||
    //      (from_degree == g.degree(vertex)/*std::get<0>(alg_data)[vertex]*/ &&
    //      vertex < from_label )) {
    //
    //      auto vv = vertex;
    //      vv.set_bcast(0);
    //      vv.set_intercept(0);
    //      auto fl = from_label;
    //      fl.set_bcast(0);
    //      fl.set_intercept(0);
    //
    //      std::get<1>(alg_data)[vv][fl] = from_degree;
    //    }
    // return false;
  }

  friend inline bool operator>(const directed_core2& v1,
                               const directed_core2& v2) {
    return false;
  }

  friend inline bool operator<(const directed_core2& v1,
                               const directed_core2& v2) {
    return false;
  }

  vertex_locator vertex;
  vertex_locator from_label;
  uint32_t       from_degree;
  bool           init;
};

template <typename Graph>
class core2_wedges {
 public:
  typedef core2_wedges<Graph>            my_type;
  typedef typename Graph::vertex_locator vertex_locator;

  core2_wedges() : vertex() {}

  core2_wedges(vertex_locator v) : vertex(v) {}

  core2_wedges(vertex_locator v, vertex_locator _check_close,
               vertex_locator _from_vertex, bool _do_check_close)
      : vertex(v),
        check_close(_check_close),
        from_vertex(_from_vertex),
        do_check_close(_do_check_close) {}

  template <typename AlgData>
  bool pre_visit(AlgData& alg_data) const {
    if (vertex.is_delegate()) {
      if (vertex.is_delegate_master()) {
        if (do_check_close) {
          ++std::get<2>(alg_data);
          ++std::get<3>(alg_data);
          // std::get<3>(alg_data) <<
          // std::get<4>(alg_data).locator_to_label(vertex) << " " <<
          // check_close
          // << std::endl;
          // std::get<4>(alg_data)[vertex]++;
          if (std::get<0>(alg_data)[vertex].count(check_close) > 0) {
            ++std::get<1>(alg_data);
            // std::get<5>(alg_data)[vertex]++;
            // std::cout << "FOUND" << std::endl;
            std::get<0>(alg_data)[vertex][check_close].edge_triangle_count++;
            std::get<0>(alg_data)[vertex][check_close].is_j = true;
            return true;  /// now returning true to count
          }
        } else {  // already found, edge counting
          if (std::get<0>(alg_data)[vertex].count(check_close) == 0 ||
              std::get<0>(alg_data)[vertex].count(from_vertex) == 0) {
            std::cerr << "Error in edge counting" << std::endl;
          }
        }
        return false;
      } else {
        if (do_check_close) {
          if (std::get<0>(alg_data)[vertex].count(check_close) == 0) {
            ++std::get<4>(alg_data);
            // std::cout << "Skipped via replication" << std::endl;
            // exit(-1);
            return false;
          }
        }
        return true;
      }
    }
    if (do_check_close) {
      ++std::get<2>(alg_data);
      // std::get<4>(alg_data)[vertex]++;
      // std::get<3>(alg_data) << std::get<4>(alg_data).locator_to_label(vertex)
      // << " " << check_close << std::endl;
      if (std::get<0>(alg_data)[vertex].count(check_close) > 0) {
        ++std::get<1>(alg_data);
        // std::get<5>(alg_data)[vertex]++;
        // std::cout << "FOUND" << std::endl;
        std::get<0>(alg_data)[vertex][check_close].edge_triangle_count++;
        std::get<0>(alg_data)[vertex][check_close].is_j = true;
        return true;
      }
    } else {  // already found, edge counting
      if (std::get<0>(alg_data)[vertex].count(check_close) == 0 ||
          std::get<0>(alg_data)[vertex].count(from_vertex) == 0) {
        std::cerr << "Error in edge counting" << std::endl;
      } else {
        std::get<0>(alg_data)[vertex][check_close].edge_triangle_count++;
        std::get<0>(alg_data)[vertex][from_vertex].edge_triangle_count++;
      }
    }
    return false;
  }

  template <typename VisitorQueueHandle, typename AlgData>
  bool init_visit(Graph& g, VisitorQueueHandle vis_queue,
                  AlgData& alg_data) const {
    if (std::get<0>(alg_data)[vertex].size() > 1) {
      for (const auto& pair_a : std::get<0>(alg_data)[vertex]) {
        for (const auto& pair_b : std::get<0>(alg_data)[vertex]) {
          if (pair_a.second.target_degree < pair_b.second.target_degree ||
              (pair_a.second.target_degree == pair_b.second.target_degree &&
               pair_a.first < pair_b.first)) {
            my_type new_visitor(pair_a.first, pair_b.first, vertex, true);
            vis_queue->queue_visitor(new_visitor);
            // std::get<3>(alg_data)[vertex]++;
            // fake ++std::get<1>(alg_data);  //fake counting here
          }
        }
      }
      /*for(auto itr_i = std::get<0>(alg_data)[vertex].begin(); itr_i !=
      std::get<0>(alg_data)[vertex].end(); ++itr_i) {
        for(auto itr_j = itr_i+1; itr_j != std::get<0>(alg_data)[vertex].end();
      ++itr_j) {
          auto pair_a = *itr_i;
          auto pair_b = *itr_j;
          if(pair_a.second < pair_b.second ||
            (pair_a.second == pair_b.second && pair_a.first < pair_b.first)) {
              my_type new_visitor(g.label_to_locator(pair_a.first),
      pair_b.first);
              //vis_queue->queue_visitor(new_visitor);
              ++std::get<1>(alg_data);  //fake counting here
          } else {
            my_type new_visitor(g.label_to_locator(pair_b.first), pair_a.first);
            //vis_queue->queue_visitor(new_visitor);
             ++std::get<1>(alg_data);  //fake counting here
          }
        }
      }*/

      //        ++std::get<1>(alg_data);  //fake counting here
    }
    return false;
  }

  template <typename VisitorQueueHandle, typename AlgData>
  bool visit(Graph& g, VisitorQueueHandle vis_queue, AlgData& alg_data) const {
    if (do_check_close) {
      my_type new_visitor(from_vertex, check_close, vertex, false);
      vis_queue->queue_visitor(new_visitor);
      return false;
    }
    std::cout << "Shoudn't be here" << std::endl;
    exit(-1);
    /*    ++std::get<2>(alg_data);
        std::get<3>(alg_data) << g.locator_to_label(vertex) << " " <<
       check_close << std::endl;
        if(std::get<0>(alg_data)[vertex].count(check_close) > 0) {
          ++std::get<1>(alg_data);
          //std::cout << "FOUND" << std::endl;
        }
        return false;*/
  }

  friend inline bool operator>(const core2_wedges& v1, const core2_wedges& v2) {
    return false;
  }

  friend inline bool operator<(const core2_wedges& v1, const core2_wedges& v2) {
    return false;
  }

  vertex_locator vertex;
  vertex_locator check_close;
  vertex_locator from_vertex;
  bool           do_check_close;
};

void output_degree_distribution(
    const std::map<uint64_t, uint64_t>& local_degree_count, const char* fname) {
  int mpi_size(0), mpi_rank(0);
  CHK_MPI(MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank));
  CHK_MPI(MPI_Comm_size(MPI_COMM_WORLD, &mpi_size));

  std::vector<std::vector<std::pair<uint64_t, uint64_t>>> send_p_vec(mpi_size);
  std::vector<std::vector<std::pair<uint64_t, uint64_t>>> recv_p_vec(mpi_size);
  for (auto deg_cnt : local_degree_count) {
    send_p_vec[deg_cnt.first % uint64_t(mpi_size)].push_back(deg_cnt);
  }

  mpi_all_to_all(send_p_vec, recv_p_vec, MPI_COMM_WORLD);

  std::map<uint64_t, uint64_t> partitioned_deg_count;
  for (auto& vec_deg_cnt : recv_p_vec) {
    for (auto deg_cnt : vec_deg_cnt) {
      partitioned_deg_count[deg_cnt.first] += deg_cnt.second;
    }
  }

  //
  // Send all to Rank 0 -- this is not efficient way
  send_p_vec.clear();
  send_p_vec.resize(mpi_size);
  recv_p_vec.clear();
  recv_p_vec.resize(mpi_size);
  for (auto vec_deg_cnt : partitioned_deg_count) {
    send_p_vec[0].push_back(vec_deg_cnt);
  }
  mpi_all_to_all(send_p_vec, recv_p_vec, MPI_COMM_WORLD);

  if (mpi_rank == 0) {
    std::vector<std::pair<uint64_t, uint64_t>> all_sorted;
    for (auto& vec_deg_cnt : recv_p_vec) {
      for (auto deg_cnt : vec_deg_cnt) {
        all_sorted.push_back(deg_cnt);
      }
    }
    std::sort(all_sorted.begin(), all_sorted.end());
    std::ofstream outfile(fname);
    for (auto deg_cnt : all_sorted) {
      outfile << deg_cnt.first << "\t" << deg_cnt.second << std::endl;
    }
  }
  MPI_Barrier(MPI_COMM_WORLD);
}
struct vertex_locator_hash {
  template <typename T>
  std::size_t operator()(const T& k) const {
    return k.hash();
  }
};

template <typename K, typename V>
class lazy_flat_map {
  using data = std::pair<K, V>;
  // struct data {
  //   K key;
  //   V value;
  // };  // __attribute__((packed));

 public:
  lazy_flat_map() { m_sorted = true; }

  typename std::vector<data>::iterator begin() {
    do_sort();
    return m_data.begin();
  }
  typename std::vector<data>::iterator end() { return m_data.end(); }

  size_t size() const { return m_data.size(); }

  void add(const K& key, const V& value) {
    m_data.push_back(data{key, value});
    m_sorted = false;
  }

  V& operator[](const K& inkey) {
    do_sort();
    data tmp_key{inkey, V{}};
    auto lb = std::lower_bound(m_data.begin(), m_data.end(), tmp_key,
                               [](const data& left, const data& right) {
                                 return left.first < right.first;
                               });
    if ((*lb).first == inkey) {
      return (*lb).second;
    } else {
      std::cerr << "NOT FOUND" << std::endl;
      exit(-1);
    }
  }

  size_t count(const K& inkey) {
    if (m_data.empty()) return 0;
    do_sort();
    data tmp_key{inkey, V{}};
    auto lb = std::lower_bound(m_data.begin(), m_data.end(), tmp_key,
                               [](const data& left, const data& right) {
                                 return left.first < right.first;
                               });
    if ((*lb).first == inkey) {
      return 1;
    } else {
      return 0;
    }
  }

  void do_sort() {
    if (!m_sorted) {
      m_sorted = true;
      std::sort(m_data.begin(), m_data.end(),
                [](const data& left, const data& right) {
                  return left.first < right.first;
                });
      auto last = std::unique(m_data.begin(), m_data.end(),
                              [](const data& left, const data& right) {
                                return left.first == right.first;
                              });
      m_data.erase(last, m_data.end());
    }
  }

 private:
  bool              m_sorted;
  std::vector<data> m_data;
};

template <typename TGraph>
uint64_t new_triangle_count(TGraph& g, const char* deg_output_fname) {
  typedef TGraph                          graph_type;
  typedef typename TGraph::vertex_locator vertex_locator;

  int mpi_size(0), mpi_rank(0);
  CHK_MPI(MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank));
  CHK_MPI(MPI_Comm_size(MPI_COMM_WORLD, &mpi_size));

  // std::stringstream fname_sv_partition, fname_wc_partition;
  // fname_sv_partition << "/p/lscratchf/havoqgtu/wedge_check_experiment/DOD_"
  // << mpi_rank << "_of_" << mpi_size;
  // fname_wc_partition <<
  // "/p/lscratchf/havoqgtu/wedge_check_experiment/wedges_" << mpi_rank <<
  // "_of_" << mpi_size;
  //
  // std::ofstream ofs_sv_partition(fname_sv_partition.str().c_str());
  // std::ofstream ofs_wc_partition(fname_wc_partition.str().c_str());

  //
  // 1)  Calculate 2core degree.   0 degree means that vertex is not in 2core
  typename graph_type::template vertex_data<
      /*lazy_flat_map*/ std::map<vertex_locator, dogr_edge>,
      std::allocator</*lazy_flat_map*/ std::map<vertex_locator, dogr_edge>>>
      core2_directed(g);
  {
    typename graph_type::template vertex_data<uint32_t,
                                              std::allocator<uint32_t>>
        core2_degree(g);
    {
      typename graph_type::template vertex_data<bool, std::allocator<bool>>
          core2_alive(g);
      core2_alive.reset(true);
      for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
        core2_degree[*vitr] = g.degree(*vitr);
      }
      for (auto ditr = g.delegate_vertices_begin();
           ditr != g.delegate_vertices_end(); ++ditr) {
        core2_degree[*ditr] = g.degree(*ditr);
      }

      // // /// This computes the 2core
      // double start_time = MPI_Wtime();
      // {
      //   auto alg_data = std::forward_as_tuple(core2_degree, core2_alive);
      //   auto vq = create_visitor_queue<core2_visitor<graph_type>,
      //   lifo_queue>(
      //       &g, alg_data);
      //   vq.init_visitor_traversal();
      // }
      // double end_time = MPI_Wtime();
      // if (mpi_rank == 0) {
      //   std::cout << "2Core time = " << end_time - start_time << std::endl;
      // }

      // for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr)
      // {
      //   std::cout << "Vertex " << g.locator_to_label(*vitr) << " has
      //   core2_degree = " << core2_degree[*vitr] << std::endl;
      // }
      // for (auto ditr = g.delegate_vertices_begin(); ditr !=
      // g.delegate_vertices_end(); ++ditr) {
      //   std::cout << "Delegate " << g.locator_to_label(*ditr) << " has
      //   core2_degree = " <<  core2_degree[*ditr] << std::endl;
      // }
    }

    //
    // 2)  Calculate directed 2core edges
    // typename graph_type::template
    // vertex_data<boost::container::flat_map<vertex_locator,uint32_t>,
    // std::allocator<boost::container::flat_map<vertex_locator,uint32_t>> >
    // core2_directed(g);
    // typename graph_type::template
    // vertex_data<std::unordered_map<vertex_locator,uint32_t,vertex_locator_hash>,
    // std::allocator<std::unordered_map<vertex_locator,uint32_t,vertex_locator_hash>
    // > >   core2_directed(g);
    double start_time = MPI_Wtime();
    {
      auto alg_data = std::forward_as_tuple(core2_degree, core2_directed, g);
      auto vq = create_visitor_queue<directed_core2<graph_type>, lifo_queue>(
          &g, alg_data);
      vq.init_visitor_traversal();
    }
    double end_time = MPI_Wtime();
    if (mpi_rank == 0) {
      std::cout << "Directed 2Core time = " << end_time - start_time
                << std::endl;
    }

    uint64_t local_core2_directed_edge_count(0);
    for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
      local_core2_directed_edge_count += core2_directed[*vitr].size();
    }

    uint64_t global_core2_directed_edge_count =
        comm_world().all_reduce(local_core2_directed_edge_count, MPI_SUM);
    if (comm_world().rank() == 0) {
      std::cout << "global_core2_directed_edge_count = "
                << global_core2_directed_edge_count << std::endl;
    }

    // for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
    //    ofs_sv_partition << g.locator_to_label(*vitr);
    //    for(auto pair : core2_directed[*vitr]) {
    //      ofs_sv_partition << " " << pair.first;
    //    }
    //    ofs_sv_partition << std::endl;
    //}

    // for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
    //   std::cout << "Vertex " << g.locator_to_label(*vitr) << " :: ";
    //     for(auto p : core2_directed[*vitr]) {
    //       std::cout << p.first << " ";
    //     }
    //     std::cout << std::endl;
    // }

    // Sort Directed 2 core -- lazy
    MPI_Barrier(MPI_COMM_WORLD);
    uint64_t local_max_dod(0), local_max_deg(0);
    start_time = MPI_Wtime();
    {
      for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
        // core2_directed[*vitr].do_sort();
        local_max_dod =
            std::max(local_max_dod, uint64_t(core2_directed[*vitr].size()));
        local_max_deg = std::max(local_max_deg, uint64_t(g.degree(*vitr)));
      }
      for (auto citr = g.controller_begin(); citr != g.controller_end();
           ++citr) {
        // core2_directed[*citr].do_sort();
        local_max_dod =
            std::max(local_max_dod, uint64_t(core2_directed[*citr].size()));
        local_max_deg = std::max(local_max_deg, uint64_t(g.degree(*citr)));
      }
    }
    MPI_Barrier(MPI_COMM_WORLD);
    end_time = MPI_Wtime();
    uint64_t global_max_dod =
        mpi_all_reduce(local_max_dod, std::greater<uint64_t>(), MPI_COMM_WORLD);
    uint64_t global_max_deg =
        mpi_all_reduce(local_max_deg, std::greater<uint64_t>(), MPI_COMM_WORLD);
    if (mpi_rank == 0) {
      std::cout << "Largest DOD out degree = " << global_max_dod << std::endl;
      std::cout << "Largest orig degree = " << global_max_deg << std::endl;
    }
    {
      //
      // 4)  Compute distributions
      std::map<uint64_t, uint64_t> local_orig_degree;
      std::map<uint64_t, uint64_t> local_2core_degree;
      std::map<uint64_t, uint64_t> local_2core_out_degree;

      uint64_t local_edge_count(0), local_dod_edge_count(0),
          local_in_zero_count(0), local_in_zero_edges_count(0);

      for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
        local_orig_degree[g.degree(*vitr)]++;
        local_2core_degree[core2_degree[*vitr]]++;
        local_2core_out_degree[core2_directed[*vitr].size()]++;
        local_edge_count += g.degree(*vitr);
        local_dod_edge_count += core2_directed[*vitr].size();
        if (core2_degree[*vitr] == core2_directed[*vitr].size()) {
          ++local_in_zero_count;
          local_in_zero_edges_count += core2_directed[*vitr].size();
        }
      }
      for (auto citr = g.controller_begin(); citr != g.controller_end();
           ++citr) {
        local_orig_degree[g.degree(*citr)]++;
        local_2core_degree[core2_degree[*citr]]++;
        local_2core_out_degree[core2_directed[*citr].size()]++;
        local_edge_count += g.degree(*citr);
        local_dod_edge_count += core2_directed[*citr].size();
        if (core2_degree[*citr] == core2_directed[*citr].size()) {
          ++local_in_zero_count;
          local_in_zero_edges_count += core2_directed[*citr].size();
        }
      }

      uint64_t global_edge_count = mpi_all_reduce(
          local_edge_count, std::plus<uint64_t>(), MPI_COMM_WORLD);
      uint64_t global_dod_edge_count = mpi_all_reduce(
          local_dod_edge_count, std::plus<uint64_t>(), MPI_COMM_WORLD);
      uint64_t global_in_zero_count = mpi_all_reduce(
          local_in_zero_count, std::plus<uint64_t>(), MPI_COMM_WORLD);
      uint64_t global_in_zero_edge_count = mpi_all_reduce(
          local_in_zero_edges_count, std::plus<uint64_t>(), MPI_COMM_WORLD);

      if (mpi_rank == 0) {
        std::cout << "global_edge_count = " << global_edge_count << std::endl;
        std::cout << "global_dod_edge_count = " << global_dod_edge_count
                  << std::endl;
        std::cout << "global_in_zero_count = " << global_in_zero_count
                  << std::endl;
        std::cout << "global_in_zero_edge_count = " << global_in_zero_edge_count
                  << std::endl;
      }

      // std::stringstream orig_degree, tcore_degree, tcore_out_degree;
      // orig_degree << deg_output_fname << "_orig_degree.txt";
      // tcore_degree << deg_output_fname << "_2core_degree.txt";
      // tcore_out_degree << deg_output_fname << "_2core_out_degree.txt";

      // output_degree_distribution(local_orig_degree,
      // orig_degree.str().c_str());
      // output_degree_distribution(local_2core_degree,
      // tcore_degree.str().c_str());
      // output_degree_distribution(local_2core_out_degree,
      // tcore_out_degree.str().c_str());
    }
  }

  //
  // 3)  Build wedges & count
  uint64_t local_triangle_count(0), local_wedge_count(0),
      local_master_wedge_count(0), local_delegate_skip(0);
  MPI_Barrier(MPI_COMM_WORLD);
  double start_time = MPI_Wtime();
  {
    auto alg_data = std::forward_as_tuple(
        core2_directed, local_triangle_count, local_wedge_count,
        local_master_wedge_count,
        local_delegate_skip /*, count_wedges_created, count_wedges_checked, count_triangles_found*/);
    auto vq = create_visitor_queue<core2_wedges<graph_type>, lifo_queue>(
        &g, alg_data);
    vq.init_visitor_traversal();
  }
  MPI_Barrier(MPI_COMM_WORLD);
  double   end_time = MPI_Wtime();
  uint64_t global_wedge_count =
      mpi_all_reduce(local_wedge_count, std::plus<uint64_t>(), MPI_COMM_WORLD);
  uint64_t global_master_wedge_count =
      comm_world().all_reduce(local_master_wedge_count, MPI_SUM);
  uint64_t global_delegate_skip =
      comm_world().all_reduce(local_delegate_skip, MPI_SUM);
  if (mpi_rank == 0) {
    std::cout << "TC on directed 2core time = " << end_time - start_time
              << std::endl;
    std::cout << "Total wedges checked = " << global_wedge_count << std::endl;
    std::cout << "global_master_wedge_count = " << global_master_wedge_count
              << std::endl;
    std::cout << "global_delegate_skip = " << global_delegate_skip << std::endl;
  }

  uint64_t local_edge_triangle_count(0);
  uint64_t local_j_count(0);
  uint64_t local_edge_count(0);
  for (auto vitr = g.vertices_begin(); vitr != g.vertices_end(); ++vitr) {
    for (const auto& edge_info : core2_directed[*vitr]) {
      local_edge_triangle_count += edge_info.second.edge_triangle_count;
      ++local_edge_count;
      if (edge_info.second.is_j) {
        ++local_j_count;
      }
    }
  }
  uint64_t local_controller_dogr_degree(0);
  for (auto citr = g.controller_begin(); citr != g.controller_end(); ++citr) {
    // std::cout << "Controller  Info: " << whoami()
    //           << " orig deg = " << g.degree(*citr)
    //           << " dogr degree = " << core2_directed[*citr].size() <<
    //           std::endl;
    local_controller_dogr_degree += core2_directed[*citr].size();
    for (const auto& edge_info : core2_directed[*citr]) {
      local_edge_triangle_count += edge_info.second.edge_triangle_count;
      ++local_edge_count;
      if (edge_info.second.is_j) {
        ++local_j_count;
      }
    }
  }
  uint64_t global_edge_triangle_count =
      comm_world().all_reduce(local_edge_triangle_count, MPI_SUM);

  uint64_t global_j_count = comm_world().all_reduce(local_j_count, MPI_SUM);

  uint64_t global_edge_count =
      comm_world().all_reduce(local_edge_count, MPI_SUM);
  uint64_t global_controller_dogr_degree =
      comm_world().all_reduce(local_controller_dogr_degree, MPI_SUM);

  if (comm_world().rank() == 0) {
    std::cout << "global_controller_dogr_degree = "
              << global_controller_dogr_degree << std::endl;
    std::cout << "global_edge_triangle_count / 3 = "
              << global_edge_triangle_count / 3 << std::endl;
    std::cout << "global_j_count = " << global_j_count << " ratio = "
              << double(global_j_count) / double(global_edge_count)
              << std::endl;
  }

  uint64_t global_triangle_count = mpi_all_reduce(
      local_triangle_count, std::plus<uint64_t>(), MPI_COMM_WORLD);
  return global_triangle_count;
}

}  // end namespace havoqgt

#endif  // HAVOQGT_MPI_TRIANGLE_COUNT_HPP_INCLUDED


#ifndef HERMIT_SPIRIT_KARMA_UTF8_HPP
#define HERMIT_SPIRIT_KARMA_UTF8_HPP

#include <iostream>
#include <vector>
#include <string>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace hermit {
  namespace spirit {
    namespace karma {
      template< typename OutputIterator >
        class utf8 :
          public boost::spirit::karma::grammar< OutputIterator, char32_t() > {
            public:
              utf8() : utf8::base_type( root ) {
                namespace karma = boost::spirit::karma;
                ascii_ = karma::byte_[ karma::_1 = karma::_val ];
                block1 = ( karma::byte_ << karma::byte_ )[
                  karma::_1 = 0xC0|( karma::_val >> 6 ),
                  karma::_2 = 0x80|( karma::_val & 0x3F )
                    ];
                block2 = ( karma::byte_ << karma::byte_ << karma::byte_ )[
                  karma::_1 = 0xE0|( karma::_val >> 12 ),
                  karma::_2 = 0x80|( ( karma::_val >> 6 ) & 0x3F ),
                  karma::_3 = 0x80|( karma::_val & 0x3F )
                    ];
                block3 = ( karma::byte_ << karma::byte_ << karma::byte_ << karma::byte_ )[
                  karma::_1 = 0xF0|( karma::_val >> 18 ),
                  karma::_2 = 0x80|( ( karma::_val >> 12 ) & 0x3F ),
                  karma::_3 = 0x80|( ( karma::_val >> 6 ) & 0x3F ),
                  karma::_4 = 0x80|( ( karma::_val ) & 0x3F )
                    ];
                char_raw = ascii_[
                  karma::_pass = karma::_val <= 0x7Ful,
                  karma::_1 = karma::_val
                ]|
                block1[
                  karma::_pass = karma::_val <= 0x7FFul,
                  karma::_1 = karma::_val
                ]|
                block2[
                  karma::_pass = karma::_val <= 0xFFFFul,
                  karma::_1 = karma::_val
                ]|
                block3[
                  karma::_pass = karma::_val <= 0x1FFFFFul,
                  karma::_1 = karma::_val
                ];
                root = char_raw[
                  karma::_pass = ( karma::_val < 0xD800ul || karma::_val > 0xDFFFul ) &&
                    ( karma::_val < 0xFDD0ul || karma::_val > 0xFDEFul ) &&
                    ( ( karma::_val & 0xFFFFul ) < 0xFFFEul ) &&
                    ( karma::_val <= 0x10FFFFul ),
                  karma::_1 = karma::_val
                ];
              }
            private:
              boost::spirit::karma::rule< OutputIterator, uint32_t() > ascii_;
              boost::spirit::karma::rule< OutputIterator, uint32_t() > block1;
              boost::spirit::karma::rule< OutputIterator, uint32_t() > block2;
              boost::spirit::karma::rule< OutputIterator, uint32_t() > block3;
              boost::spirit::karma::rule< OutputIterator, char32_t() > char_raw;
              boost::spirit::karma::rule< OutputIterator, char32_t() > root;
          };
      template< typename OutputIterator >
        class utf8string :
          public boost::spirit::karma::grammar< OutputIterator, std::u32string() > {
            public:
              utf8string() : utf8string::base_type( root ) {
                root = *utf8char;
              }
            private:
              utf8< OutputIterator > utf8char;
              boost::spirit::karma::rule< OutputIterator, std::u32string() > root;
      };
    }
  }
}

#endif