#include <boost/foreach.hpp>

#include "constants.hpp"
#include "gc.hpp"

boost::mutex GCCorrection::mut;

GCCorrection::GCCorrection(TranscriptSet& ts, const double* transcript_gc)
    : ts(ts)
    , transcript_gc(transcript_gc)
{
    n = 0;
    for (TranscriptSet::iterator t = ts.begin(); t != ts.end(); ++t) {
        n = std::max<size_t>(n, t->tgroup + 1);
    }

    gene_expr.resize(n);
    gene_gc.resize(n);
    xs.resize(n);
    ys.resize(n);
    fit.resize(ts.size());
    se_fit.resize(ts.size());
    tgc.resize(ts.size());

    lo.model.span = constants::gc_loess_smoothing;
    lo.model.family = "symmetric";
    lo.model.degree = 1;
}

GCCorrection::~GCCorrection()
{
}

// I really hat this code. It's so goofy.
// If I really want to correct for gc-content I also have to try take into account
// 5'-3' effects. Also, I should really be correcting for GC content at the fragment
// level.
void GCCorrection::correct(double* expr)
{
    boost::lock_guard<boost::mutex> lock(mut);

    std::fill(gene_gc.begin(), gene_gc.end(), 0.0);
    std::fill(gene_expr.begin(), gene_expr.end(), 0.0);
    for (TranscriptSet::iterator t = ts.begin(); t != ts.end(); ++t) {
        if (expr[t->id] == 0.0 ||
            t->exonic_length() < 500 ||
            transcript_gc[t->id] < 0.10 ||
            transcript_gc[t->id] > 0.90) {
            continue;
        }

        gene_gc[t->tgroup] += expr[t->id] * transcript_gc[t->id];
        gene_expr[t->tgroup] += expr[t->id];
    }

    double max_gc = 0.0, min_gc = 1.0;
    size_t j = 0;
    for (size_t i = 0; i < n; ++i) {
        if (gene_expr[i] > 0.0) {
            xs[j] = gene_gc[i] / gene_expr[i];
            ys[j] = log(gene_expr[i]);
            max_gc = std::max<double>(max_gc, xs[j]);
            min_gc = std::min<double>(min_gc, xs[j]);
            ++j;
        }
    }

    loess_setup(&xs.at(0), &ys.at(0), j, 1, &lo);
    lo.model.span = constants::gc_loess_smoothing;
    lo.model.family = "symmetric";
    lo.model.degree = 1;
    loess(&lo);

    // normalize to a arbitary point in the loess curve
    double ref_gc = 0.5;
    double ref, ref_se;
    predict2(&lo, &ref_gc, &ref, &ref_se, 1, FALSE);

    // normalize samples
    std::copy(transcript_gc, transcript_gc + ts.size(), tgc.begin());
    for (size_t i = 0; i < ts.size(); ++i) {
        tgc[i] = std::min(max_gc, std::max(min_gc, tgc[i]));
    }

    predict2(&lo, &tgc.at(0), &fit.at(0), &se_fit.at(0), ts.size(), FALSE);

    double z = 0.0;
    for (size_t i = 0; i < ts.size(); ++i) {
        expr[i]  = pow(expr[i], ref / fit[i]);
        z += expr[i];
    }

    for (size_t i = 0; i < ts.size(); ++i) {
        expr[i] /= z;
    }

    loess_free_mem(&lo);
}

void GCCorrection::adjustments(const double* expr, double* weights)
{
    boost::lock_guard<boost::mutex> lock(mut);

    std::fill(gene_gc.begin(), gene_gc.end(), 0.0);
    std::fill(gene_expr.begin(), gene_expr.end(), 0.0);
    for (TranscriptSet::iterator t = ts.begin(); t != ts.end(); ++t) {
        if (expr[t->id] == 0.0 ||
            t->exonic_length() < 500 ||
            transcript_gc[t->id] < 0.10 ||
            transcript_gc[t->id] > 0.90) {
            continue;
        }

        gene_gc[t->tgroup] += expr[t->id] * transcript_gc[t->id];
        gene_expr[t->tgroup] += expr[t->id];
    }

    double max_gc = 0.0, min_gc = 1.0;
    size_t j = 0;
    for (size_t i = 0; i < n; ++i) {
        if (gene_expr[i] > 0.0) {
            xs[j] = gene_gc[i] / gene_expr[i];
            ys[j] = log(gene_expr[i]);
            max_gc = std::max<double>(max_gc, xs[j]);
            min_gc = std::min<double>(min_gc, xs[j]);
            ++j;
        }
    }

    loess_setup(&xs.at(0), &ys.at(0), j, 1, &lo);
    lo.model.span = constants::gc_loess_smoothing;
    lo.model.family = "symmetric";
    lo.model.degree = 1;
    loess(&lo);

    // normalize to a arbitary point in the loess curve
    double ref_gc = 0.5;
    double ref, ref_se;
    predict2(&lo, &ref_gc, &ref, &ref_se, 1, FALSE);

    // normalize samples
    std::copy(transcript_gc, transcript_gc + ts.size(), tgc.begin());
    for (size_t i = 0; i < ts.size(); ++i) {
        tgc[i] = std::min(max_gc, std::max(min_gc, tgc[i]));
    }

    predict2(&lo, &tgc.at(0), &fit.at(0), &se_fit.at(0), ts.size(), FALSE);

    for (size_t i = 0; i < n; ++i) {
        weights[i] = ref / fit[i];
    }
}


#ifndef MPLLIBS_METAMONAD_TEST_SAME_MAP_HPP
#define MPLLIBS_METAMONAD_TEST_SAME_MAP_HPP

// Copyright Abel Sinkovics (abel@sinkovics.hu) 2013.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <mpllibs/metamonad/metafunction.hpp>
#include <mpllibs/metamonad/lambda_c.hpp>
#include <mpllibs/metamonad/lazy.hpp>
#include <mpllibs/metamonad/already_lazy.hpp>
#include <mpllibs/metamonad/lazy_protect_args.hpp>
#include <mpllibs/metamonad/name.hpp>
#include <mpllibs/metamonad/first.hpp>
#include <mpllibs/metamonad/second.hpp>

#include <boost/type_traits.hpp>

#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/and.hpp>
#include <boost/mpl/not.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/size.hpp>

// equal depends on the order of elements in the map
MPLLIBS_METAFUNCTION(same_map, (A)(B))
((
  boost::mpl::and_<
    typename
      boost::mpl::fold<
        A,
        boost::mpl::true_,
        mpllibs::metamonad::lambda_c<
          mpllibs::metamonad::s,
          mpllibs::metamonad::p,
          mpllibs::metamonad::lazy<
            boost::mpl::and_<
              mpllibs::metamonad::already_lazy<mpllibs::metamonad::s>,
              boost::is_same<
                boost::mpl::at<
                  mpllibs::metamonad::already_lazy<B>,
                  mpllibs::metamonad::lazy_protect_args<
                    mpllibs::metamonad::first<mpllibs::metamonad::p>
                  >
                >,
                mpllibs::metamonad::lazy_protect_args<
                  mpllibs::metamonad::second<mpllibs::metamonad::p>
                >
              >
            >
          >
        >
      >::type,
    typename boost::mpl::equal_to<
      typename boost::mpl::size<A>::type,
      typename boost::mpl::size<B>::type
    >::type
  >
));

MPLLIBS_METAFUNCTION(not_same_map, (A)(B))
((boost::mpl::not_<typename same_map<A, B>::type>));

#endif


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.
// Copyright (c) 2014-2017 Adam Wulkiewicz, Lodz, Poland.

// This file was modified by Oracle on 2017-2021.
// Modifications copyright (c) 2017-2021 Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_CORRECT_HPP
#define BOOST_GEOMETRY_ALGORITHMS_CORRECT_HPP

#include <algorithm>
#include <cstddef>
#include <functional>

#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>
#include <boost/range/value_type.hpp>

#include <boost/geometry/algorithms/area.hpp>
#include <boost/geometry/algorithms/correct_closure.hpp>
#include <boost/geometry/algorithms/detail/interior_iterator.hpp>
#include <boost/geometry/algorithms/detail/multi_modify.hpp>
#include <boost/geometry/algorithms/detail/visit.hpp>

#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/exterior_ring.hpp>
#include <boost/geometry/core/interior_rings.hpp>
#include <boost/geometry/core/mutable_range.hpp>
#include <boost/geometry/core/ring_type.hpp>
#include <boost/geometry/core/tags.hpp>
#include <boost/geometry/core/visit.hpp>

#include <boost/geometry/geometries/adapted/boost_variant.hpp> // For backward compatibility
#include <boost/geometry/geometries/concepts/check.hpp>

#include <boost/geometry/strategies/area/cartesian.hpp>
#include <boost/geometry/strategies/area/geographic.hpp>
#include <boost/geometry/strategies/area/spherical.hpp>
#include <boost/geometry/strategies/detail.hpp>

#include <boost/geometry/util/algorithm.hpp>

namespace boost { namespace geometry
{

// Silence warning C4127: conditional expression is constant
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4127)
#endif

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace correct
{

struct correct_nop
{
    template <typename Geometry, typename Strategy>
    static inline void apply(Geometry& , Strategy const& )
    {}
};

// Correct a box: make min/max correct
struct correct_box
{
    template <typename Box, typename Strategy>
    static inline void apply(Box& box, Strategy const& )
    {
        using coordinate_type = typename geometry::coordinate_type<Box>::type;

        // Currently only for Cartesian coordinates
        // (or spherical without crossing dateline)
        // Future version: adapt using strategies
        detail::for_each_dimension<Box>([&](auto dimension)
        {
            if (get<min_corner, dimension>(box) > get<max_corner, dimension>(box))
            {
                // Swap the coordinates
                coordinate_type max_value = get<min_corner, dimension>(box);
                coordinate_type min_value = get<max_corner, dimension>(box);
                set<min_corner, dimension>(box, min_value);
                set<max_corner, dimension>(box, max_value);
            }
        });
    }
};

// Close a ring, if not closed
template <typename Predicate = std::less<>>
struct correct_ring
{
    template <typename Ring, typename Strategy>
    static inline void apply(Ring& r, Strategy const& strategy)
    {
        // Correct closure if necessary
        detail::correct_closure::close_or_open_ring::apply(r);

        // NOTE: calculate_point_order should probably be used here instead.

        // Check area
        using area_t = typename area_result<Ring, Strategy>::type;
        area_t const zero = 0;
        if (Predicate()(detail::area::ring_area::apply(r, strategy), zero))
        {
            std::reverse(boost::begin(r), boost::end(r));
        }
    }
};

// Correct a polygon: normalizes all rings, sets outer ring clockwise, sets all
// inner rings counter clockwise (or vice versa depending on orientation)
struct correct_polygon
{
    template <typename Polygon, typename Strategy>
    static inline void apply(Polygon& poly, Strategy const& strategy)
    {
        correct_ring<std::less<>>::apply(exterior_ring(poly), strategy);

        auto&& rings = interior_rings(poly);
        auto const end = boost::end(rings);
        for (auto it = boost::begin(rings); it != end; ++it)
        {
            correct_ring<std::greater<>>::apply(*it, strategy);
        }
    }
};

}} // namespace detail::correct
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename Geometry, typename Tag = typename tag<Geometry>::type>
struct correct: not_implemented<Tag>
{};

template <typename Point>
struct correct<Point, point_tag>
    : detail::correct::correct_nop
{};

template <typename LineString>
struct correct<LineString, linestring_tag>
    : detail::correct::correct_nop
{};

template <typename Segment>
struct correct<Segment, segment_tag>
    : detail::correct::correct_nop
{};

template <typename Box>
struct correct<Box, box_tag>
    : detail::correct::correct_box
{};

template <typename Ring>
struct correct<Ring, ring_tag>
    : detail::correct::correct_ring<>
{};

template <typename Polygon>
struct correct<Polygon, polygon_tag>
    : detail::correct::correct_polygon
{};

template <typename MultiPoint>
struct correct<MultiPoint, multi_point_tag>
    : detail::correct::correct_nop
{};

template <typename MultiLineString>
struct correct<MultiLineString, multi_linestring_tag>
    : detail::correct::correct_nop
{};

template <typename Geometry>
struct correct<Geometry, multi_polygon_tag>
    : detail::multi_modify<detail::correct::correct_polygon>
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

namespace resolve_strategy
{

template
<
    typename Strategy,
    bool IsUmbrella = strategies::detail::is_umbrella_strategy<Strategy>::value
>
struct correct
{
    template <typename Geometry>
    static inline void apply(Geometry& geometry, Strategy const& strategy)
    {
        dispatch::correct<Geometry>::apply(geometry, strategy);
    }
};

template <typename Strategy>
struct correct<Strategy, false>
{
    template <typename Geometry>
    static inline void apply(Geometry& geometry, Strategy const& strategy)
    {
        // NOTE: calculate_point_order strategy should probably be used here instead.
        using geometry::strategies::area::services::strategy_converter;
        dispatch::correct<Geometry>::apply(geometry, strategy_converter<Strategy>::get(strategy));
    }
};

template <>
struct correct<default_strategy, false>
{
    template <typename Geometry>
    static inline void apply(Geometry& geometry, default_strategy const& )
    {
        // NOTE: calculate_point_order strategy should probably be used here instead.
        using strategy_type = typename strategies::area::services::default_strategy
            <
                Geometry
            >::type;
        dispatch::correct<Geometry>::apply(geometry, strategy_type());
    }
};

} // namespace resolve_strategy

namespace resolve_dynamic
{

template <typename Geometry, typename Tag = typename tag<Geometry>::type>
struct correct
{
    template <typename Strategy>
    static inline void apply(Geometry& geometry, Strategy const& strategy)
    {
        concepts::check<Geometry>();
        resolve_strategy::correct<Strategy>::apply(geometry, strategy);
    }
};

template <typename Geometry>
struct correct<Geometry, dynamic_geometry_tag>
{
    template <typename Strategy>
    static inline void apply(Geometry& geometry, Strategy const& strategy)
    {
        traits::visit<Geometry>::apply([&](auto & g)
        {
            correct<util::remove_cref_t<decltype(g)>>::apply(g, strategy);
        }, geometry);
    }
};

template <typename Geometry>
struct correct<Geometry, geometry_collection_tag>
{
    template <typename Strategy>
    static inline void apply(Geometry& geometry, Strategy const& strategy)
    {
        detail::visit_breadth_first([&](auto & g)
        {
            correct<util::remove_cref_t<decltype(g)>>::apply(g, strategy);
            return true;
        }, geometry);
    }
};

} // namespace resolve_dynamic

/*!
\brief Corrects a geometry
\details Corrects a geometry: all rings which are wrongly oriented with respect
    to their expected orientation are reversed. To all rings which do not have a
    closing point and are typed as they should have one, the first point is
    appended. Also boxes can be corrected.
\ingroup correct
\tparam Geometry \tparam_geometry
\param geometry \param_geometry which will be corrected if necessary

\qbk{[include reference/algorithms/correct.qbk]}
*/
template <typename Geometry>
inline void correct(Geometry& geometry)
{
    resolve_dynamic::correct<Geometry>::apply(geometry, default_strategy());
}

/*!
\brief Corrects a geometry
\details Corrects a geometry: all rings which are wrongly oriented with respect
    to their expected orientation are reversed. To all rings which do not have a
    closing point and are typed as they should have one, the first point is
    appended. Also boxes can be corrected.
\ingroup correct
\tparam Geometry \tparam_geometry
\tparam Strategy \tparam_strategy{Area}
\param geometry \param_geometry which will be corrected if necessary
\param strategy \param_strategy{area}

\qbk{distinguish,with strategy}

\qbk{[include reference/algorithms/correct.qbk]}
*/
template <typename Geometry, typename Strategy>
inline void correct(Geometry& geometry, Strategy const& strategy)
{
    resolve_dynamic::correct<Geometry>::apply(geometry, strategy);
}

#if defined(_MSC_VER)
#pragma warning(pop)
#endif

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_CORRECT_HPP


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_GROUP_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_GROUP_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/function/combine.hpp>
#include <boost/simd/function/slice.hpp>
#include <boost/simd/function/saturated.hpp>
#include <boost/simd/detail/dispatch/meta/downgrade.hpp>

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;

   BOOST_DISPATCH_OVERLOAD( group_
                          , (typename A0)
                          , bs::avx_
                          , bs::pack_<bd::double_<A0>, avx_>
                          , bs::pack_<bd::double_<A0>, avx_>
                          )
   {
      using result = bd::downgrade_t<A0>;

      BOOST_FORCEINLINE result operator()(const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        using slice_t = typename A0::template rebind<typename result::value_type>;
        return combine(slice_t(_mm256_cvtpd_ps(a0)),slice_t(_mm256_cvtpd_ps(a1)));
      }
   };

   BOOST_DISPATCH_OVERLOAD( group_
                          , (typename A0)
                          , bs::avx_
                          , bs::pack_<bd::integer_<A0>, avx_>
                          , bs::pack_<bd::integer_<A0>, avx_>
                          )
   {
      using result = bd::downgrade_t<A0>;

      BOOST_FORCEINLINE result operator()(const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        auto a0x = slice(a0), a1x = slice(a1);
        auto v0  = group(a0x[0], a0x[1]);
        auto v1  = group(a1x[0], a1x[1]);

        return _mm256_insertf128_si256(_mm256_castsi128_si256(v0), v1, 1);
      }
   };

   BOOST_DISPATCH_OVERLOAD( group_
                          , (typename A0)
                          , bs::avx_
                          , bs::saturated_tag
                          , bs::pack_<bd::integer_<A0>, avx_>
                          , bs::pack_<bd::integer_<A0>, avx_>
                          )
   {
      using result = bd::downgrade_t<A0>;

      BOOST_FORCEINLINE
      result operator()(bs::saturated_tag const&, const A0& a0, const A0& a1) const BOOST_NOEXCEPT
      {
        auto a0x = slice(a0), a1x = slice(a1);
        auto v0  = saturated_(group)(a0x[0], a0x[1]);
        auto v1  = saturated_(group)(a1x[0], a1x[1]);

        return _mm256_insertf128_si256(_mm256_castsi128_si256(v0), v1, 1);
      }
   };
} } }

#endif


#include "rdb_protocol/profile.hpp"

#include <inttypes.h>

#include <limits>

#include "errors.hpp"
#include <boost/variant/static_visitor.hpp>

#include "containers/archive/stl_types.hpp"
#include "logger.hpp"
#include "rdb_protocol/math_utils.hpp"
#include "rdb_protocol/datum.hpp"
#include "rdb_protocol/env.hpp"

namespace profile {

start_t::start_t() { }

start_t::start_t(const std::string &description)
    : description_(description), when_(get_ticks()) { }

RDB_IMPL_SERIALIZABLE_2_SINCE_v1_13(start_t, description_, when_);

split_t::split_t() { }

split_t::split_t(size_t n_parallel_jobs)
    : n_parallel_jobs_(n_parallel_jobs) { }

RDB_IMPL_SERIALIZABLE_1_SINCE_v1_13(split_t, n_parallel_jobs_);

sample_t::sample_t() { }

sample_t::sample_t(const std::string &description,
        ticks_t mean_duration, size_t n_samples)
    : description_(description), mean_duration_(mean_duration),
      n_samples_(n_samples)
{ }

RDB_IMPL_SERIALIZABLE_3_SINCE_v1_13(sample_t, description_, mean_duration_, n_samples_);

stop_t::stop_t()
    : when_(get_ticks()) { }

RDB_IMPL_SERIALIZABLE_1_SINCE_v1_13(stop_t, when_);

ql::datum_t construct_start(
        ticks_t duration, std::string description,
        ql::datum_t sub_tasks) {
        std::map<datum_string_t, ql::datum_t> res;
    res[datum_string_t("duration(ms)")] =
        ql::datum_t(safe_to_double(duration) / MILLION);
    res[datum_string_t("description")] =
        ql::datum_t(datum_string_t(description));
    res[datum_string_t("sub_tasks")] = sub_tasks;
    return ql::datum_t(std::move(res));
}

ql::datum_t construct_split(
        ql::datum_t par_tasks) {
    std::map<datum_string_t, ql::datum_t> res;
    res[datum_string_t("parallel_tasks")] = par_tasks;
    return ql::datum_t(std::move(res));
}

ql::datum_t construct_sample(
        const sample_t *sample) {
    std::map<datum_string_t, ql::datum_t> res;
    double mean_duration = safe_to_double(sample->mean_duration_) / MILLION;
    double n_samples = safe_to_double(sample->n_samples_);
    res[datum_string_t("mean_duration(ms)")] = ql::datum_t(mean_duration);
    res[datum_string_t("n_samples")] = ql::datum_t(n_samples);
    res[datum_string_t("description")] =
        ql::datum_t(datum_string_t(sample->description_));
        return ql::datum_t(std::move(res));
    return ql::datum_t();
}

ql::datum_t construct_datum(
        event_log_t::const_iterator *begin,
        event_log_t::const_iterator end,
        const ql::configured_limits_t &limits);

class construct_datum_visitor_t : public boost::static_visitor<void> {
public:
    // N.B.: it is important that the lifetime of this visitor not
    // exceed the lifetime of the limits reference: for example
    // writing construct_datum_visitor_t v(begin, end,
    // ql::configured_limits_t(), &res) is a terrible idea.  When in
    // doubt, use construct_datum, which ensures that its lifetime is
    // a subset of the limits lifetime.
    construct_datum_visitor_t(
        event_log_t::const_iterator *begin, event_log_t::const_iterator end,
        const ql::configured_limits_t *limits,
        std::vector<ql::datum_t> *res)
        : begin_(begin), end_(end), limits_(limits), res_(res) { }

    void operator()(const start_t &start) const {
        (*begin_)++;
        ql::datum_t sub_tasks = construct_datum(begin_, end_, *limits_);
        auto stop = boost::get<stop_t>(&**begin_);
        guarantee(stop);
        res_->push_back(construct_start(
            stop->when_ - start.when_, start.description_, sub_tasks));
            (*begin_)++;
    }
    void operator()(const split_t &split) const {
        (*begin_)++;
        std::vector<ql::datum_t> parallel_tasks;
        for (size_t i = 0; i < split.n_parallel_jobs_; ++i) {
            parallel_tasks.push_back(construct_datum(begin_, end_, *limits_));
            guarantee(boost::get<stop_t>(&**begin_));
            (*begin_)++;
        }
        res_->push_back(construct_split(
        ql::datum_t(std::move(parallel_tasks), *limits_)));
    }
    void operator()(const sample_t &sample) const {
        (*begin_)++;
        res_->push_back(construct_sample(&sample));
    }
    void operator()(const stop_t &) const {
        //Nothing to do here
    }

private:
    event_log_t::const_iterator *begin_;
    event_log_t::const_iterator end_;
    const ql::configured_limits_t *limits_;
    std::vector<ql::datum_t> *res_;
};

ql::datum_t construct_datum(
        event_log_t::const_iterator *begin,
        event_log_t::const_iterator end,
        const ql::configured_limits_t &limits) {
    std::vector<ql::datum_t> res;

    construct_datum_visitor_t visitor(begin, end, &limits, &res);
    while (*begin != end && !boost::get<stop_t>(&**begin)) {
        boost::apply_visitor(visitor, **begin);
    }

    return ql::datum_t(std::move(res), limits);
}

class print_event_log_visitor_t : public boost::static_visitor<void> {
public:
    void operator()(const start_t &start) const {
        logINF("Start: %s.\n", start.description_.c_str());
    }
    void operator()(const split_t &split) const {
        logINF("Split: %zu.\n", split.n_parallel_jobs_);
    }
    void operator()(const sample_t &) const {
        logINF("Sample.");
    }
    void operator()(const stop_t &) const {
        logINF("Stop.\n");
    }
};

void print_event_log(const event_log_t &event_log) {
    print_event_log_visitor_t visitor;
    for (auto it = event_log.begin(); it != event_log.end(); ++it) {
        boost::apply_visitor(visitor, *it);
    }
}

starter_t::starter_t(const std::string &description, trace_t *parent) {
    init(description, parent);
}

starter_t::starter_t(const std::string &description, const scoped_ptr_t<trace_t> &parent) {
    init(description, parent.get_or_null());
}

starter_t::~starter_t() {
    if (parent_) {
        parent_->stop();
    }
}

void starter_t::init(const std::string &description, trace_t *parent) {
    parent_ = parent;
    if (parent_) {
        parent_->start(description);
    }
}

splitter_t::splitter_t(trace_t *parent) {
    init(parent);
}

splitter_t::splitter_t(const scoped_ptr_t<trace_t> &parent) {
    init(parent.get_or_null());
}

void splitter_t::give_splits(
    size_t n_parallel_jobs, const event_log_t &event_log) {
    n_parallel_jobs_ = n_parallel_jobs;
    event_log_ = event_log;
}

splitter_t::~splitter_t() {
    if (parent_) {
        parent_->stop_split(n_parallel_jobs_, event_log_);
    }
}

void splitter_t::init(trace_t *parent) {
    parent_ = parent;
    n_parallel_jobs_ = 0;

    if (parent_) {
        parent_->start_split();
    }
}

sampler_t::sampler_t(const std::string &description, trace_t *parent) {
    init(description, parent);
}

sampler_t::sampler_t(const std::string &description, const scoped_ptr_t<trace_t> &parent) {
    init(description, parent.get_or_null());
}

void sampler_t::init(const std::string &description, trace_t *parent) {
    parent_ = parent;
    description_ = description;
    total_time_ = 0;
    n_samples_ = 0;
    if (parent_) {
        parent_->start_sample(&event_log_);
    }
}

ticks_t duration(const event_log_t &event_log) {
    guarantee(!event_log.empty());
    if (auto start = boost::get<start_t>(&event_log.at(0))) {
        auto stop = boost::get<stop_t>(&event_log.back());
        guarantee(stop);
        return stop->when_ - start->when_;
    } else {
        //This is a code path that is currently never hit and that will go a
        //way when we implement more meaningful sampling functions.
        return 0;
    }
}

void sampler_t::new_sample() {
    if (!event_log_.empty()) {
        n_samples_++;
        total_time_ += duration(event_log_);
    }

    event_log_.clear();
}

sampler_t::~sampler_t() {
    new_sample();
    if (parent_) {
        if (n_samples_ > 0) {
            parent_->stop_sample(description_, total_time_ / n_samples_, n_samples_, &event_log_);
        } else {
            parent_->stop_sample(&event_log_);
        }
    }
}

disabler_t::disabler_t(trace_t *parent) {
    init(parent);
}

disabler_t::disabler_t(const scoped_ptr_t<trace_t> &parent) {
    init(parent.get_or_null());
}

disabler_t::~disabler_t() {
    if (parent_) {
        parent_->enable();
    }
}

void disabler_t::init(trace_t *parent) {
    parent_ = parent;
    if (parent_) {
        parent_->disable();
    }
}

trace_t::trace_t()
    : redirected_event_log_(NULL), disabled_ref_count_(0) { }

ql::datum_t trace_t::as_datum() const {
    guarantee(!redirected_event_log_);
    event_log_t::const_iterator begin = event_log_.begin();
    // Again, use defaults, as there's no predicting where this could
    // come in response to user requests.
    return construct_datum(&begin, event_log_.end(),
                           ql::configured_limits_t());
}

event_log_t trace_t::extract_event_log() RVALUE_THIS {
    // These guarantees imply that this trace_t gets left in a default-constructed
    // state (which is valid, thereby acceptable for an RVALUE_THIS function).
    guarantee(redirected_event_log_ == NULL);
    guarantee(disabled_ref_count_ == 0);
    return std::move(event_log_);
}

void trace_t::start(const std::string &description) {
    if (disabled()) { return; }
    //debugf("Start %s %p.\n", description.c_str(), this);
    event_log_target()->push_back(start_t(description));
}

void trace_t::stop() {
    if (disabled()) { return; }
    //debugf("Stop %p.\n", this);
    event_log_target()->push_back(stop_t());
}

void trace_t::start_split() {
    if (disabled()) { return; }
    //debugf("Start split %p.\n", this);
    event_log_target()->push_back(split_t());
}

void trace_t::stop_split(size_t n_parallel_jobs_, const event_log_t &par_event_log) {
    if (disabled()) { return; }
    //debugf("Stop split %zu, %p.\n", n_parallel_jobs_, this);
    auto split = boost::get<split_t>(&event_log_target()->back());
    guarantee(split);
    split->n_parallel_jobs_ = n_parallel_jobs_;
    event_log_target()->insert(event_log_target()->end(), par_event_log.begin(), par_event_log.end());
}

void trace_t::start_sample(event_log_t *event_log) {
    if (disabled()) { return; }
    //debugf("Start sample %p.\n", this);
    /* This is a tad hacky. We currently don't  allow samples within samples.
     * And if someone tries to do it the inner sample winds up just being a
     * no-op. We should see if in practice this is something we actually want
     * to support. */
    if (!redirected_event_log_) {
        redirected_event_log_ = event_log;
    }
}

void trace_t::stop_sample(const std::string &description,
        ticks_t mean_duration, size_t n_samples, event_log_t *event_log) {
    if (disabled()) { return; }
    //debugf("Stop sample %s, %p.\n", description.c_str(), this);
    /* Don't reset the redirected_event_log_ if the sampler_t wasn't
     * actually being redirected to. The predicate fails when the
     * innter samplers in nested sampler_ts are destructed. */
    if (event_log == redirected_event_log_) {
        redirected_event_log_ = NULL;
    }
    event_log_target()->push_back(sample_t(description, mean_duration, n_samples));
}

void trace_t::stop_sample(event_log_t *event_log) {
    if (disabled()) { return; }
    //debugf("Stop sample %p.\n", this);
    /* Don't reset the redirected_event_log_ if the sampler_t wasn't
     * actually being redirected to. The predicate fails when the
     * innter samplers in nested sampler_ts are destructed. */
    if (event_log == redirected_event_log_) {
        redirected_event_log_ = NULL;
    }
}

void trace_t::disable() {
    disabled_ref_count_++;
}
void trace_t::enable() {
    disabled_ref_count_--;
}

bool trace_t::disabled() {
    return disabled_ref_count_ > 0;
}

event_log_t *trace_t::event_log_target() {
    if (redirected_event_log_) {
        return redirected_event_log_;
    } else {
        return &event_log_;
    }
}

} //namespace profile


#include "NeuralNetworkPixelClassifiers.h"
#include "image_loader.h"

#include "itkImageRegionConstIteratorWithIndex.h"

#include "log4cxx/logger.h"

#include <boost/filesystem.hpp>
#include <boost/regex.hpp>

#include <string>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <algorithm>
#include <utility>

struct _StringComparator {
	  bool operator() (const std::string a, const std::string b) { return a < b;}
} StringComparator;

void NeuralNetworkPixelClassifiers::create_neural_networks( const unsigned int inputSize, const unsigned int numberOfClassifiers, const std::vector< unsigned int > hiddenLayers, const float learning_rate )
{
	log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));

	m_InputSize = inputSize;
	m_NumberOfClassifiers = numberOfClassifiers;
	m_NumberOfClasses = 1 == m_NumberOfClassifiers ? 2 : m_NumberOfClassifiers;

	std::vector< unsigned int > layers = hiddenLayers;
	layers.insert(layers.begin(), m_InputSize);
	layers.push_back(1);

	for(int i = 0; i < m_NumberOfClassifiers; ++i)
	{
		NeuralNetwork* ann = fann_create_standard_array(layers.size(), layers.data());
		fann_set_activation_function_hidden(ann, FANN_SIGMOID);
		fann_set_activation_function_output(ann, FANN_SIGMOID);
		fann_set_train_stop_function(ann, FANN_STOPFUNC_MSE);
		fann_set_learning_rate(ann, learning_rate);

		m_NeuralNetworks.push_back( boost::shared_ptr< NeuralNetwork >( ann, fann_destroy ) );
	}
}

bool score_comparator(std::pair< float, float> & a, std::pair< float, float> & b)
{
	return a.second < b.second;
}

void NeuralNetworkPixelClassifiers::train_neural_networks(
	FannClassificationDataset const *training_sets,
	const unsigned int max_epoch,
	const float mse_target,
	FannClassificationDataset const *validation_sets )
{
	log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));

	m_TrainingScoresHistory.clear();

	if(validation_sets != NULL) {
		m_TrainingScoresHistory.reserve(m_NumberOfClassifiers);
		for(int i = 0; i < m_NumberOfClassifiers; ++i)
			m_TrainingScoresHistory.push_back(std::vector< std::pair< float, float > >());
	}

	#pragma omp parallel for
	for(int i = 0; i < m_NumberOfClassifiers; ++i)
	{
		LOG4CXX_INFO(logger, "Training ann #" << i);

		boost::shared_ptr< NeuralNetwork > current_neural_network = m_NeuralNetworks[i];
		FannClassificationDataset::FannDataset *current_training_set = training_sets->getSet(i);

		if(validation_sets == NULL)
		{
			fann_train_on_data(current_neural_network.get(), current_training_set, max_epoch, 0, mse_target);
		} else {
			FannClassificationDataset::FannDataset *current_validation_set = validation_sets->getSet(i);

			std::vector< boost::shared_ptr< NeuralNetwork > > trainingHistory;
			trainingHistory.reserve(max_epoch);

			for(int j = 0; j < max_epoch; ++j) {
				float train_mse      = fann_train_epoch( current_neural_network.get(), current_training_set  ),
				      validation_mse = fann_test_data(   current_neural_network.get(), current_validation_set );

				trainingHistory.push_back( boost::shared_ptr< NeuralNetwork >( fann_copy(current_neural_network.get()), fann_destroy ) );

				m_TrainingScoresHistory[i].push_back(std::make_pair(train_mse, validation_mse));

				LOG4CXX_INFO(logger, "MSE for ann #" << i << "; " << j << "; " << train_mse << "; " << validation_mse);
			}

			const int best_neural_network_index = std::distance(
					m_TrainingScoresHistory[i].begin(),
					std::min_element(m_TrainingScoresHistory[i].begin(), m_TrainingScoresHistory[i].end(), score_comparator)
				);

			LOG4CXX_INFO(logger, "Best neural network for dataset #" << i << " obtained at training-iteration #" << best_neural_network_index << ": MSE=" << m_TrainingScoresHistory[i][best_neural_network_index].second);

			m_NeuralNetworks[i] = boost::shared_ptr< NeuralNetwork >(trainingHistory[best_neural_network_index]);

			fann_test_data(m_NeuralNetworks[i].get(), current_validation_set);
		}

		LOG4CXX_INFO(logger, "MSE for ann #" << i << ": " << fann_get_MSE(m_NeuralNetworks[i].get()));
	}
}

void NeuralNetworkPixelClassifiers::save(const std::string dir)
{
	log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));
	LOG4CXX_INFO(logger, "Saving neural networks in " << dir);

	for(int i = 0; i < m_NumberOfClassifiers; ++i) {
		std::ostringstream filename;
		filename << std::setfill('0') << std::setw(6) << (i+1) << ".ann";

		boost::filesystem::path path = boost::filesystem::path(dir) / filename.str();

		if(0 != fann_save(m_NeuralNetworks[i].get(), path.native().c_str())) {
			throw std::runtime_error("Cannot save the neural-network in " + path.native());
		}

		if(!m_TrainingScoresHistory.empty()) {
			std::ostringstream filename;
			filename << std::setfill('0') << std::setw(6) << (i+1) << "-training-scores.dat";

			boost::filesystem::path path = boost::filesystem::path(dir) / filename.str();

			std::ofstream score_file;
			score_file.exceptions(std::ofstream::failbit | std::ofstream::badbit);
			score_file.open(path.native().c_str(), std::ios::out | std::ios::trunc); // This will erase the content of the file

			try {
				for(std::vector< std::pair< float, float > >::const_iterator it = m_TrainingScoresHistory[i].begin(); it != m_TrainingScoresHistory[i].end(); ++it) {
					score_file << it->first << "\t" << it->second << std::endl;
				}

				score_file.close();
			} catch(std::ifstream::failure &e) {
				throw std::runtime_error("Cannot save the neural-network training-scores file in " + path.native() + " (" + e.what() + ")");
			}
		}
	}
}

void NeuralNetworkPixelClassifiers::load(const std::string dir)
{
	log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));
	LOG4CXX_INFO(logger, "Loading neural networks from " << dir);

	m_TrainingScoresHistory.clear();

	const boost::regex config_file_filter( "\\d{6,6}.ann" );
	std::vector< std::string > config_files;

	boost::filesystem::directory_iterator end_itr; // Default ctor yields past-the-end
	for( boost::filesystem::directory_iterator i( dir ); i != end_itr; ++i ) {
		// Skip if not a file
		if( !boost::filesystem::is_regular_file( i->status() ) ) continue;

		boost::smatch what;

		// Skip if no match
		if( !boost::regex_match( i->path().filename().native(), what, config_file_filter ) ) continue;

		// File matches, store it
		config_files.push_back( i->path().native() );
	}

	std::sort(config_files.begin(), config_files.end(), StringComparator);

	m_NumberOfClassifiers = config_files.size();
	m_NumberOfClasses = 1 == m_NumberOfClassifiers ? 2 : m_NumberOfClassifiers;

	for(std::vector<std::string>::const_iterator it = config_files.begin(); it != config_files.end(); ++it) {
		LOG4CXX_INFO(logger, "Loading neural network from " << *it);

		NeuralNetwork* ann = fann_create_from_file(it->c_str());

		if(ann == NULL)
			throw std::runtime_error("Cannot load neural network from " + *it);

		m_NeuralNetworks.push_back( boost::shared_ptr< NeuralNetwork >( ann, fann_destroy ) );
	}

	m_InputSize = fann_get_num_input(m_NeuralNetworks.front().get());

	LOG4CXX_INFO(logger, "Number of components per pixel: " << m_InputSize);
}

std::vector<float> NeuralNetworkPixelClassifiers::classify(const std::vector< fann_type > &input) const
{
	std::vector<float> result(m_NumberOfClassifiers);

	for(int i = 0; i < m_NumberOfClassifiers; ++i)
	{
		double* r = fann_run( m_NeuralNetworks[i].get(), const_cast<fann_type *>( input.data() ) );
		result[i] = r[0];
	}

	return result;
}


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2012-2020 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_LINE_LINE_INTERSECTION_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_LINE_LINE_INTERSECTION_HPP

#include <boost/geometry/core/assert.hpp>
#include <boost/geometry/arithmetic/infinite_line_functions.hpp>
#include <boost/geometry/algorithms/detail/make/make.hpp>

#include <boost/core/ignore_unused.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace buffer
{

// TODO: it might once be changed this to proper strategy
struct line_line_intersection
{
    template <typename Point>
    static inline Point
    apply(Point const& pi, Point const& pj, Point const& qi, Point const& qj)
    {
        typedef typename coordinate_type<Point>::type ct;
        typedef model::infinite_line<ct> line_type;

        line_type const p = detail::make::make_infinite_line<ct>(pi, pj);
        line_type const q = detail::make::make_infinite_line<ct>(qi, qj);

        // The input lines are not parallel, they intersect, because
        // their join type is checked before.
        Point ip;
        bool const intersecting = arithmetic::intersection_point(p, q, ip);
        BOOST_GEOMETRY_ASSERT(intersecting);
        boost::ignore_unused(intersecting);

        return ip;
    }
};

}} // namespace detail::buffer
#endif // DOXYGEN_NO_DETAIL

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_LINE_LINE_INTERSECTION_HPP


#pragma once

#include <array>
#include <condition_variable>
#include <type_traits>

#include <blake2/blake2.h>

#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/filesystem.hpp>
#include <boost/multiprecision/cpp_int.hpp>

#include <cryptopp/osrng.h>

#include <liblmdb/lmdb.h>

#include <paper/config.hpp>

namespace paper
{
extern CryptoPP::AutoSeededRandomPool random_pool;
// We operate on streams of uint8_t by convention
using stream = std::basic_streambuf <uint8_t>;
using bufferstream = boost::iostreams::stream_buffer <boost::iostreams::basic_array_source <uint8_t>>;
using vectorstream = boost::iostreams::stream_buffer <boost::iostreams::back_insert_device <std::vector <uint8_t>>>;
// OS-specific way of finding a path to a home directory.
boost::filesystem::path working_path ();
// Get a unique path within the home directory, used for testing
boost::filesystem::path unique_path ();
// Read a raw byte stream the size of `T' and fill value.
template <typename T>
bool read (paper::stream & stream_a, T & value)
{
	static_assert (std::is_pod <T>::value, "Can't stream read non-standard layout types");
	auto amount_read (stream_a.sgetn (reinterpret_cast <uint8_t *> (&value), sizeof (value)));
	return amount_read != sizeof (value);
}
template <typename T>
void write (paper::stream & stream_a, T const & value)
{
	static_assert (std::is_pod <T>::value, "Can't stream write non-standard layout types");
	auto amount_written (stream_a.sputn (reinterpret_cast <uint8_t const *> (&value), sizeof (value)));
	assert (amount_written == sizeof (value));
}
std::string to_string_hex (uint64_t);
bool from_string_hex (std::string const &, uint64_t &);

using uint128_t = boost::multiprecision::uint128_t;
using uint256_t = boost::multiprecision::uint256_t;
using uint512_t = boost::multiprecision::uint512_t;
// SI dividers
paper::uint128_t const Gpaper_ratio = paper::uint128_t ("1000000000000000000000000000000000"); // 10^33
paper::uint128_t const Mpaper_ratio = paper::uint128_t ("1000000000000000000000000000000"); // 10^30
paper::uint128_t const kpaper_ratio = paper::uint128_t ("1000000000000000000000000000"); // 10^27
paper::uint128_t const  paper_ratio = paper::uint128_t ("1000000000000000000000000"); // 10^24
paper::uint128_t const mpaper_ratio = paper::uint128_t ("1000000000000000000000"); // 10^21
paper::uint128_t const upaper_ratio = paper::uint128_t ("1000000000000000000"); // 10^18
class mdb_env
{
public:
	mdb_env (bool &, boost::filesystem::path const &);
	~mdb_env ();
	operator MDB_env * () const;
	void add_transaction ();
	void remove_transaction ();
	MDB_env * environment;
	std::mutex lock;
	std::condition_variable open_notify;
	unsigned open_transactions;
	unsigned transaction_iteration;
	std::condition_variable resize_notify;
	bool resizing;
};
class mdb_val
{
public:
	mdb_val (size_t, void *);
	operator MDB_val * () const;
	operator MDB_val const & () const;
	MDB_val value;
};
class transaction
{
public:
	transaction (paper::mdb_env &, MDB_txn *, bool);
	~transaction ();
	operator MDB_txn * () const;
	MDB_txn * handle;
	paper::mdb_env & environment;
};
union uint128_union
{
public:
	uint128_union () = default;
	uint128_union (std::string const &);
	uint128_union (uint64_t);
	uint128_union (paper::uint128_union const &) = default;
	uint128_union (paper::uint128_t const &);
	bool operator == (paper::uint128_union const &) const;
	void encode_hex (std::string &) const;
	bool decode_hex (std::string const &);
	void encode_dec (std::string &) const;
	bool decode_dec (std::string const &);
	paper::uint128_t number () const;
	void clear ();
	bool is_zero () const;
	paper::mdb_val val () const;
	std::string to_string () const;
	std::array <uint8_t, 16> bytes;
	std::array <char, 16> chars;
	std::array <uint32_t, 4> dwords;
	std::array <uint64_t, 2> qwords;
};
// Balances are 128 bit.
using amount = uint128_union;
union uint256_union
{
	uint256_union () = default;
	uint256_union (std::string const &);
	uint256_union (uint64_t, uint64_t = 0, uint64_t = 0, uint64_t = 0);
	uint256_union (paper::uint256_t const &);
	uint256_union (paper::uint256_union const &, paper::uint256_union const &, uint128_union const &);
	uint256_union (MDB_val const &);
	uint256_union prv (uint256_union const &, uint128_union const &) const;
	uint256_union & operator ^= (paper::uint256_union const &);
	uint256_union operator ^ (paper::uint256_union const &) const;
	bool operator == (paper::uint256_union const &) const;
	bool operator != (paper::uint256_union const &) const;
	bool operator < (paper::uint256_union const &) const;
	paper::mdb_val val () const;
	void encode_hex (std::string &) const;
	bool decode_hex (std::string const &);
	void encode_dec (std::string &) const;
	bool decode_dec (std::string const &);
	void encode_base58check (std::string &) const;
	std::string to_base58check () const;
	bool decode_base58check (std::string const &);
	std::array <uint8_t, 32> bytes;
	std::array <char, 32> chars;
	std::array <uint64_t, 4> qwords;
	std::array <uint128_union, 2> owords;
	void clear ();
	bool is_zero () const;
	std::string to_string () const;
	paper::uint256_t number () const;
};
// All keys and hashes are 256 bit.
using block_hash = uint256_union;
using account = uint256_union;
using public_key = uint256_union;
using private_key = uint256_union;
using secret_key = uint256_union;
using checksum = uint256_union;
union uint512_union
{
	uint512_union () = default;
	uint512_union (paper::uint512_t const &);
	bool operator == (paper::uint512_union const &) const;
	bool operator != (paper::uint512_union const &) const;
	paper::uint512_union & operator ^= (paper::uint512_union const &);
	void encode_hex (std::string &) const;
	bool decode_hex (std::string const &);
	std::array <uint8_t, 64> bytes;
	std::array <uint32_t, 16> dwords;
	std::array <uint64_t, 8> qwords;
	std::array <uint256_union, 2> uint256s;
	void clear ();
	boost::multiprecision::uint512_t number () const;
};
// Only signatures are 512 bit.
using signature = uint512_union;
paper::uint512_union sign_message (paper::private_key const &, paper::public_key const &, paper::uint256_union const &);
bool validate_message (paper::public_key const &, paper::uint256_union const &, paper::uint512_union const &);
}


#include <mtpio/chain/platform_timer_accuracy.hpp>
#include <mtpio/chain/platform_timer.hpp>

#include <fc/time.hpp>
#include <fc/log/logger.hpp>

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/weighted_mean.hpp>
#include <boost/accumulators/statistics/weighted_variance.hpp>

#include <chrono>
#include <mutex>

namespace mtpio { namespace chain {

namespace bacc = boost::accumulators;

void compute_and_print_timer_accuracy(platform_timer& timer) {
   static std::mutex m;
   static bool once_is_enough;

   std::lock_guard guard(m);

   if(once_is_enough)
      return;

   bacc::accumulator_set<int, bacc::stats<bacc::tag::mean, bacc::tag::min, bacc::tag::max, bacc::tag::variance>, float> samples;

   //keep longest first in list. You're effectively going to take test_intervals[0]*sizeof(test_intervals[0])
   //time to do the the test
   int test_intervals[] = {50000, 10000, 5000, 1000, 500, 100, 50, 10};

   for(int& interval : test_intervals) {
      unsigned int loops = test_intervals[0]/interval;

      for(unsigned int i = 0; i < loops; ++i) {
         auto start = std::chrono::high_resolution_clock::now();
         timer.start(fc::time_point(fc::time_point::now().time_since_epoch() + fc::microseconds(interval)));
         while(!timer.expired) {}
         auto end = std::chrono::high_resolution_clock::now();
         int timer_slop = std::chrono::duration_cast<std::chrono::microseconds>(end-start).count() - interval;

         //since more samples are run for the shorter expirations, weigh the longer expirations accordingly. This
         //helps to make a few results more fair. Two such examples: AWS c4&i5 xen instances being rather stable
         //down to 100us but then struggling with 50us and 10us. MacOS having performance that seems to correlate
         //with expiry length; that is, long expirations have high error, short expirations have low error.
         //That said, for these platforms, a tighter tolerance may possibly be achieved by taking performance
         //metrics in mulitple bins and appliying the slop based on which bin a deadline resides in. Not clear
         //if that's worth the extra complexity at this point.
         samples(timer_slop, bacc::weight = interval/(float)test_intervals[0]);
      }
   }

   #define TIMER_STATS_FORMAT "min:${min}us max:${max}us mean:${mean}us stddev:${stddev}us"
   #define TIMER_STATS \
      ("min", bacc::min(samples))("max", bacc::max(samples)) \
      ("mean", (int)bacc::mean(samples))("stddev", (int)sqrt(bacc::variance(samples)))

   ilog("Checktime timer accuracy: " TIMER_STATS_FORMAT, TIMER_STATS);
   if(bacc::mean(samples) + sqrt(bacc::variance(samples))*2 > 250)
      wlog("Checktime timer accuracy on this platform and hardware combination is poor; accuracy of subjective transaction deadline enforcement will suffer");

   once_is_enough = true;
}

}}


#define BOOST_TEST_MODULE Test

#include "keyboard_optimizer/concepts.hpp"
#include "keyboard_optimizer/layout.hpp"
#include "keyboard_optimizer/standart_constraints.hpp"
#include "keyboard_optimizer/standart_effort_model.hpp"
#include "keyboard_optimizer/standart_optimizer.hpp"
#include "keyboard_optimizer/utils.hpp"
#include "utility/type_traits.hpp"
#include "tbb/concurrent_unordered_set.h"
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boykov_kolmogorov_max_flow.hpp>
#include <boost/graph/cycle_canceling.hpp>
#include <boost/graph/edmonds_karp_max_flow.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/kruskal_min_spanning_tree.hpp>
#include <boost/graph/prim_minimum_spanning_tree.hpp>
#include <boost/graph/push_relabel_max_flow.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/read_dimacs.hpp>
#include <boost/graph/successive_shortest_path_nonnegative_weights.hpp>
#include <boost/property_map/function_property_map.hpp>
#include <boost/test/included/unit_test.hpp>
#include <iostream>

using namespace KeyboardOptimizer;
BOOST_AUTO_TEST_CASE(compile_test) {
  using AllowedKeys = StandartConstraints::AllowedKeys;
  using SymBonds = StandartConstraints::SymBonds;
  using SymConstraints = StandartConstraints::SymConstraints;
  using SymLayers = StandartConstraints::SymLayers;
  auto key = Key(1, 1.0);
  auto layer = Layer{0};
  auto press = Press{key, layer};
  auto modifier = Modifier{};
  auto keyboard = Keyboard{key};
  auto mods = LayersMap<Modifier>{modifier};
  auto finger = Finger{FingerType::Index, Hand::Left};
  auto pfmap = PressFingerMap{{press, finger}};
  auto pemap = PressEffortMap{{press, 1.0}};
  auto sym = Sym{0};
  auto symLayers = SymLayers{layer};
  auto symBonds = SymBonds{sym};
  auto symConsts = SymConstraints{AllowedKeys{key}};
  auto layout = Layout{press};
  auto constParams = StandartConstraintsParameters{};
  auto constsPtr = std::unique_ptr<StandartConstraints>{};
  {
    auto consts2 = StandartConstraints{
        std::move(symLayers), std::move(symBonds), std::move(symConsts),
        Keyboard(keyboard), std::move(constParams)};
    constsPtr = std::make_unique<StandartConstraints>(consts2);
  }
  auto consts = std::move(*constsPtr);

  auto emparams = StandartEffortModelParameters{};
  auto emodelPtr = std::unique_ptr<StandartEffortModel>{};
  {
    auto fp = FingerPositions(NFingers, key);
    auto emodel2 =
        StandartEffortModel{std::move(mods), std::move(pfmap), std::move(pemap),
                            std::move(fp), std::move(emparams)};
    emodelPtr = std::make_unique<StandartEffortModel>(emodel2);
  }
  auto emodel = std::move(*emodelPtr);

  auto text = SymString(1000, sym);
  auto bench = StandartOptimizerBenchmarkParameters{};
  bench.genOverheadTextSize = 20;
  bench.genOverheadRuns = 1;
  bench.DNAOverheadPopulationSize = 10;
  bench.DNAOverheadRuns = 1;
  bench.errorFactorRuns = 5;

  auto opt = StandartOptimizer(SymString(text), StandartEffortModel{emodel},
                               StandartConstraints(consts), bench);
  auto modelParams = opt.GetModelParameters();
  auto opt1 =
      StandartOptimizer(SymString(text), StandartEffortModel{emodel},
                        StandartConstraints(consts), std::move(modelParams));
  opt.Optimize();
  opt1.Optimize();
}

BOOST_AUTO_TEST_CASE(copy_move_test) {
  using AllowedKeys = StandartConstraints::AllowedKeys;
  using SymBonds = StandartConstraints::SymBonds;
  using SymConstraints = StandartConstraints::SymConstraints;
  using SymLayers = StandartConstraints::SymLayers;
  static_assert(std::copyable<StandartConstraints>);
  static_assert(std::copyable<StandartEffortModel>);
  static_assert(std::copyable<
                StandartOptimizer<StandartEffortModel, StandartConstraints>>);

  auto modifier = Modifier{};
  auto mods = LayersMap<Modifier>{modifier};

  auto N = size_t{100};
  auto layer = Layer{0};
  auto syms = Utility::GetIndices(N);
  auto keyboard = Keyboard{};
  auto symLayers = SymLayers{};
  auto symBonds = SymBonds{};
  auto symConsts = SymConstraints{};
  symConsts.reserve(N);
  keyboard.reserve(N);
  symLayers.reserve(N);
  symBonds.reserve(N);
  for (auto i = size_t{0}; i != N; ++i) {
    keyboard.emplace_back(static_cast<int>(i), 1.0);
    symLayers.push_back(layer);
    symBonds.push_back(i);
  }
  for (auto i = size_t{0}; i != N; ++i)
    symConsts.emplace_back(keyboard.begin(), keyboard.end());
  auto antiDCE = size_t{0};
  auto consts = StandartConstraints{std::move(symLayers), std::move(symBonds),
                                    std::move(symConsts), Keyboard(keyboard)};
  auto copyTime = Utility::Benchmark([&]() { auto c2 = consts; }, 100);
  auto moveTime = Utility::Benchmark(
      [&]() {
        auto c2 = std::move(consts);
        consts = std::move(c2);
      },
      100);

  BOOST_TEST((copyTime > moveTime * 10));

  std::cout << std::endl;
  std::cout << "Benchmark of move and copy StandartConstraints:" << std::endl;
  std::cout << "With " << N << " syms and " << keyboard.size() << " keys"
            << std::endl;
  std::cout << "Copy time: " << copyTime.count() << " nanoseconds" << std::endl;
  std::cout << "Move time: " << moveTime.count() << " nanoseconds" << std::endl;
}

BOOST_AUTO_TEST_CASE(concepts_test) {
  static_assert(ConstraintsConcept<StandartConstraints>);
  static_assert(EffortModelConcept<StandartEffortModel>);
  static_assert(OptimizerConcept<
                StandartOptimizer<StandartEffortModel, StandartConstraints>>);
}

BOOST_AUTO_TEST_CASE(layout_hash_benchmark) {
  auto N = 1000;
  auto NSyms = 100;
  auto hashT = std::chrono::nanoseconds{0};

  for (auto i = 0; i != N; ++i) {
    auto layout = Layout{};
    for (auto j = 0; j != NSyms; ++j)
      layout.emplace_back(Key{i, static_cast<double>(j)}, 0);
    hashT += Utility::Benchmark([&]() { std::hash<Layout>{}(layout); });
  }
  auto time = hashT.count() / static_cast<double>(N);
  std::cout << std::endl;
  std::cout << "Layout hashing time " + std::to_string(time) + " nanoseconds"
            << std::endl;
}

BOOST_AUTO_TEST_CASE(random_benchmark) {
  auto N = 1000;
  auto timen = std::chrono::nanoseconds{0};
  auto d = 0.0;
  auto gen = std::mt19937();
  auto rand = std::uniform_real_distribution<>{};

  auto gentime = Utility::Benchmark([&]() { gen = std::mt19937(); }, N);
  auto randtime = Utility::Benchmark(
      [&]() { rand = std::uniform_real_distribution<>{}; }, N);
  auto time = Utility::Benchmark([&]() { d += rand(gen); }, N);
  std::cout << "Generator create time:   " + std::to_string(gentime.count()) +
                   " nanoseconds"
            << std::endl;
  std::cout << "Distribution create time " + std::to_string(randtime.count()) +
                   " nanoseconds"
            << std::endl;
  std::cout << "Random time              " + std::to_string(time.count()) +
                   " nanoseconds"
            << std::endl;
}

BOOST_AUTO_TEST_CASE(mst_benchmarks) {
  struct VertexProperty final {
    VertexProperty(){};
  };
  auto static lastind = size_t{0};
  struct EdgeProperty {
    size_t ind;
    EdgeProperty() { ind = lastind++; };
  };
  using Graph =
      boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
                            VertexProperty, EdgeProperty>;
  using Edge = boost::graph_traits<Graph>::edge_descriptor;
  using Vertex = boost::graph_traits<Graph>::vertex_descriptor;

  auto nSyms = size_t{33};
  auto nVertices = nSyms * 4;
  auto nEdges = 3 * nSyms * nSyms;
  auto g = Graph{};
  auto gdynptr = std::make_unique<Graph>();
  auto &&gdyn = *gdynptr;
  auto gen = std::mt19937{};
#pragma warning(disable : 4267)
  boost::generate_random_graph(g, nVertices, nEdges, gen);
  lastind = 0;
  boost::generate_random_graph(gdyn, nVertices, nEdges, gen);
#pragma warning(default : 4267)

  auto rand = std::uniform_real_distribution<double>{};
  auto antidce = size_t{0};
  auto wmtime = Utility::Benchmark(
      [&]() {
        auto weightmap = std::vector<double>(nEdges);
        for (auto i = 0; i < nEdges; ++i)
          weightmap[i] = rand(gen);
      },
      2);
  auto weightmap = std::vector<double>(nEdges);

  auto fmtime = Utility::Benchmark(
      [&]() {
        auto fmap = boost::make_function_property_map<Edge>(
            [&](auto e) { return weightmap[g[e].ind]; });
      },
      2);
  auto fmap = boost::make_function_property_map<Edge>(
      [&](auto e) { return weightmap[g[e].ind]; });
  auto ptime =
      Utility::Benchmark([&]() { auto props = boost::weight_map(fmap); }, 100);
  auto props = boost::weight_map(fmap);
  auto fmapdyn = boost::make_function_property_map<Edge>(
      [&](auto e) { return weightmap[gdyn[e].ind]; });
  auto propsdyn = boost::weight_map(fmapdyn);

  auto predMap = std::vector<Vertex>(nVertices);
  auto tprim = Utility::Benchmark(
      [&]() { boost::prim_minimum_spanning_tree(g, &predMap[0], props); }, 30);
  auto tprimdyn = Utility::Benchmark(
      [&]() { boost::prim_minimum_spanning_tree(gdyn, &predMap[0], propsdyn); },
      30);

  auto spanning_tree = std::vector<Edge>{};
  spanning_tree.reserve(nEdges);
  auto tkruskal = Utility::Benchmark(
      [&]() {
        spanning_tree.clear();
        boost::kruskal_minimum_spanning_tree(
            g, std::back_inserter(spanning_tree), props);
        antidce += spanning_tree.size();
      },
      10);
  auto tkruskaldyn = Utility::Benchmark(
      [&]() {
        spanning_tree.clear();
        boost::kruskal_minimum_spanning_tree(
            gdyn, std::back_inserter(spanning_tree), propsdyn);
        antidce += spanning_tree.size();
      },
      10);

  auto g2 = g;
  auto g3 = g;
  auto tmove = Utility::Benchmark(
                   [&]() {
                     g3 = std::move(g2);
                     g2 = std::move(g3);
                     antidce += boost::num_edges(g2);
                   },
                   10) /
               2;
  auto tcopy = Utility::Benchmark(
                   [&]() {
                     g3 = g2;
                     g2 = g3;
                     antidce += boost::num_edges(g2);
                   },
                   10) /
               2;

  BOOST_TEST(antidce);
  std::cout << std::endl;
  std::cout << "Benchmark of move and copy graphs:" << std::endl;
  std::cout << "With " << nVertices << " vertices and " << nEdges << " edges"
            << std::endl;
  std::cout << "Move time: " << tmove.count() << " nanoseconds" << std::endl;
  std::cout << "Copy time: " << tcopy.count() << " nanoseconds" << std::endl;

  std::cout << std::endl;
  std::cout << "Benchmark of prim and kruskal minimum spanning tree algorithms:"
            << std::endl;
  std::cout << "With " << nVertices << " vertices and " << nEdges << " edges"
            << std::endl;
  std::cout << "Weigth map creation time:            " << wmtime.count()
            << " nanoseconds" << std::endl;
  std::cout << "function_property_map creation time: " << fmtime.count()
            << " nanoseconds" << std::endl;
  std::cout << "Properties creation time:            " << ptime.count()
            << " nanoseconds" << std::endl;
  std::cout << "Prim                                 " << tprim.count()
            << " nanoseconds" << std::endl;
  std::cout << "Prim (dyn allocated graph)           " << tprimdyn.count()
            << " nanoseconds" << std::endl;
  std::cout << "Kruskal                              " << tkruskal.count()
            << " nanoseconds" << std::endl;
  std::cout << "Kruskal (dyn allocated graph)        " << tkruskaldyn.count()
            << " nanoseconds" << std::endl;
}

BOOST_AUTO_TEST_CASE(maxflow_benchmarks) {
  auto static vertlastind = size_t{0};
  struct VertexProperty final {
    size_t ind;
    VertexProperty() : ind(-1){};
    VertexProperty(size_t ind) : ind(ind){};
  };
  auto static edgelastind = size_t{0};
  using Edge = boost::graph_traits<boost::adjacency_list<
      boost::vecS, boost::vecS, boost::directedS>>::edge_descriptor;
  using Vertex = boost::graph_traits<boost::adjacency_list<
      boost::vecS, boost::vecS, boost::directedS>>::vertex_descriptor;

  using EdgeProperty = boost::property<
      boost::edge_capacity_t, size_t,
      boost::property<boost::edge_index_t, size_t,
                      boost::property<boost::edge_reverse_t, Edge>>>;
  using Graph =
      boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
                            VertexProperty, EdgeProperty>;

  auto nSyms = size_t{40};
  auto nTryEdges = static_cast<size_t>(nSyms * nSyms * 0.8);
  auto g = Graph{};
  auto &&edge_index_map = boost::get(boost::edge_index, g);
  auto &&capacity_map = boost::get(boost::edge_capacity, g);
  auto &&reverse_edge_map = boost::get(boost::edge_reverse, g);
  auto gen = std::mt19937{};
  auto A = std::vector<Vertex>{};
  auto B = std::vector<Vertex>{};
  auto RevEdges = std::unordered_set<size_t>(nTryEdges * 3);
  A.reserve(nSyms);
  B.reserve(nSyms);
  auto s = boost::add_vertex({vertlastind++}, g);
  auto t = boost::add_vertex({vertlastind++}, g);
  auto symRand = std::uniform_int_distribution<size_t>(0, nSyms - 1);
  for (auto i = size_t{0}; i != nSyms; ++i) {
    A.push_back(boost::add_vertex({vertlastind++}, g));
    B.push_back(boost::add_vertex({vertlastind++}, g));
  }
  for (auto i = size_t{0}; i != nTryEdges; ++i) {
    auto a = symRand(gen);
    auto b = symRand(gen);
    auto const &[tmp, isExist] = boost::edge(A[a], B[b], g);
    if (isExist)
      continue;
    auto const &[dir, tmp2] = boost::add_edge(A[a], B[b], g);
    auto const &[rev, success] = boost::add_edge(B[b], A[a], g);
    edge_index_map[dir] = edgelastind++;
    edge_index_map[rev] = edgelastind++;
    RevEdges.insert(edge_index_map[rev]);
  }
  for (auto i = size_t{0}; i != nSyms; ++i) {
    auto const &[dir1, tmp1] = boost::add_edge(s, A[i], g);
    auto const &[rev1, success1] = boost::add_edge(A[i], s, g);
    auto const &[dir2, tmp2] = boost::add_edge(B[i], t, g);
    auto const &[rev2, success2] = boost::add_edge(t, B[i], g);
    edge_index_map[dir1] = edgelastind++;
    edge_index_map[dir2] = edgelastind++;
    edge_index_map[rev1] = edgelastind++;
    edge_index_map[rev2] = edgelastind++;
    RevEdges.insert(edge_index_map[rev1]);
    RevEdges.insert(edge_index_map[rev2]);
  }
  auto nVertices = boost::num_vertices(g);
  auto nEdges = boost::num_edges(g);

  auto residualCapacityMap = std::vector<size_t>(nEdges);
  auto colorMap = std::vector<boost::default_color_type>(nVertices);
  auto predcessorMap = std::vector<Edge>(nVertices);
  auto distanceMap = std::vector<int>(nVertices);
  auto distanceMap2 = std::vector<int>(nVertices);
  auto weigthMap = std::vector<int>(nEdges);

  auto weightRand = std::uniform_int_distribution<>(0, 10);
  std::generate(weigthMap.begin(), weigthMap.end(),
                [&]() { return weightRand(gen); });
  for (auto const &e : boost::make_iterator_range(boost::edges(g))) {
    auto source = boost::source(e, g);
    auto target = boost::target(e, g);
    auto const &[revEdge, isExist] = boost::edge(target, source, g);
    assert(isExist);
    assert(revEdge != e);
    auto ind = edge_index_map[e];
    auto revind = edge_index_map[revEdge];
    reverse_edge_map[e] = revEdge;
    if (RevEdges.contains(ind)) {
      weigthMap[ind] = -weigthMap[revind];
      capacity_map[e] = 0;
    } else
      capacity_map[e] = 1;
  }
  auto vertex_index_map = boost::make_function_property_map<Vertex>(
      [&](auto const &v) { return g[v].ind; });
  auto residual_capacity = boost::make_iterator_property_map(
      residualCapacityMap.begin(), edge_index_map);
  auto color_map =
      boost::make_iterator_property_map(colorMap.begin(), vertex_index_map);
  auto predcessor_map = boost::make_iterator_property_map(predcessorMap.begin(),
                                                          vertex_index_map);
  auto distance_map =
      boost::make_iterator_property_map(distanceMap.begin(), vertex_index_map);
  auto distance_map2 =
      boost::make_iterator_property_map(distanceMap2.begin(), vertex_index_map);
  auto weight_map =
      boost::make_iterator_property_map(weigthMap.begin(), edge_index_map);
  auto props = boost::weight_map(weight_map)
                   .residual_capacity_map(residual_capacity)
                   .distance_map(distance_map)
                   .distance_map2(distance_map2)
                   .vertex_index_map(vertex_index_map)
                   .predecessor_map(predcessor_map)
                   .color_map(color_map);

  struct Filter final {
    bool x;
    Filter() {}
    Filter(bool x) : x(x) {}
    bool operator()(Edge const &e) const { return x; }
    bool operator()(Vertex const &v) const { return x; }
  };

  struct VertexFilter final {
    bool x;
    VertexFilter() {}
    VertexFilter(bool x) : x(x) {}
    bool operator()(Vertex const &v) const { return x; }
  };
  using FilteredGraph = boost::filtered_graph<Graph, Filter, Filter>;
  auto static volatile x = true; // anti-optimization
  auto copyTime = std::chrono::nanoseconds{};
  auto filtTime = std::chrono::nanoseconds{};
  {
    auto gv = std::vector<Graph>{};
    auto gv2 = std::vector<FilteredGraph>{};
    copyTime = Utility::Benchmark([&]() { gv.push_back(g); }, 1000);
    filtTime = Utility::Benchmark(
        [&]() { gv2.push_back(FilteredGraph(g, Filter(x), Filter(x))); }, 1000);
  }

  auto g2 = FilteredGraph(g, Filter(x), Filter(x));

  auto antiDCECounter = size_t{0};
  auto randedgetime = Utility::Benchmark(
      [&]() {
#pragma warning(disable : 4267)
        auto &&e = boost::random_edge(g, gen);
#pragma warning(default : 4267)
        antiDCECounter += weight_map[e];
      },
      1000);
  auto gettime = Utility::Benchmark(
                     [&]() {
#pragma warning(disable : 4267)
                       auto &&e = boost::random_edge(g, gen);
#pragma warning(default : 4267)
                       auto &&a = boost::get(boost::edge_index, g);
                       auto &&b = boost::get(boost::edge_capacity, g);
                       auto &&c = boost::get(boost::edge_reverse, g);
                       antiDCECounter += a[e];
                       antiDCECounter += b[e];
                       antiDCECounter += b[c[e]];
                     },
                     1000) -
                 randedgetime;

  auto rev1time = Utility::Benchmark(
      [&]() {
        auto e = boost::random_edge(g, gen);
        auto ind = edge_index_map[e];
        antiDCECounter += RevEdges.contains(ind);
      },
      1000);

  auto bktime = Utility::Benchmark(
      [&]() { boost::boykov_kolmogorov_max_flow(g, s, t, props); }, 10);
  auto bktime2 = Utility::Benchmark(
      [&]() { boost::boykov_kolmogorov_max_flow(g2, s, t, props); }, 10);

  auto ektime = Utility::Benchmark(
      [&]() { boost::edmonds_karp_max_flow(g, s, t, props); }, 10);
  auto ektime2 = Utility::Benchmark(
      [&]() { boost::edmonds_karp_max_flow(g2, s, t, props); }, 10);

  auto prtime = Utility::Benchmark(
      [&]() { boost::push_relabel_max_flow(g, s, t, props); }, 10);
  auto prtime2 = Utility::Benchmark(
      [&]() { boost::push_relabel_max_flow(g2, s, t, props); }, 10);

  auto cctime = Utility::Benchmark(
      [&]() {
        std::generate(weigthMap.begin(), weigthMap.end(),
                      [&]() { return weightRand(gen); });
        boost::cycle_canceling(g, props);
      },
      10);
  auto sstime = Utility::Benchmark(
      [&]() {
        std::generate(weigthMap.begin(), weigthMap.end(),
                      [&]() { return weightRand(gen); });
        boost::successive_shortest_path_nonnegative_weights(g, s, t, props);
      },
      10);
  auto sstime2 = Utility::Benchmark(
      [&]() {
        std::generate(weigthMap.begin(), weigthMap.end(),
                      [&]() { return weightRand(gen); });
        boost::successive_shortest_path_nonnegative_weights(g2, s, t, props);
      },
      10);

  std::cout << std::endl;
  std::cout << "Benchmark of max flow algorithms:" << std::endl;
  std::cout << "With " << nVertices << " vertices and " << nEdges << " edges"
            << std::endl;
  std::cout << "Graph copy time:   " << copyTime.count() << " nanoseconds"
            << std::endl;
  std::cout << "Graph filter time: " << filtTime.count() << " nanoseconds"
            << std::endl;
  std::cout << std::endl;
  std::cout << "Boykov-Kolmogorov        " << bktime.count() << " nanoseconds"
            << std::endl;
  std::cout << "Boykov-Kolmogorov (filt) " << bktime2.count() << " nanoseconds"
            << std::endl;
  std::cout << "Edmonds-Karp             " << ektime.count() << " nanoseconds"
            << std::endl;
  std::cout << "Edmonds-Karp (filt)      " << ektime2.count() << " nanoseconds"
            << std::endl;
  std::cout << "Push-relabel             " << prtime.count() << " nanoseconds"
            << std::endl;
  std::cout << "Push-relabel (filt)      " << prtime2.count() << " nanoseconds"
            << std::endl;
  std::cout << std::endl;
  std::cout << "Benchmark of min const algorithms:" << std::endl;
  std::cout << "Cycle canceling      " << cctime.count()
            << " nanoseconds (does not work actually)" << std::endl;
  std::cout << "Shortest path        " << sstime.count() << " nanoseconds"
            << std::endl;
  std::cout << "Shortest path (filt) " << sstime2.count() << " nanoseconds"
            << std::endl;
  std::cout << std::endl;
  std::cout << "Benchmark of support functions:" << std::endl;
  std::cout << "boost::random_edge  " << randedgetime.count() << std::endl;
  std::cout << "boost::get  " << gettime.count() << std::endl;

  auto bridges = 0;
  for (auto const &e : boost::make_iterator_range(boost::edges(g))) {
    if (RevEdges.contains(edge_index_map[e]))
      continue;
    bridges += static_cast<int>(capacity_map[e]) -
               static_cast<int>(residual_capacity[e]);
  }

  BOOST_TEST(bridges == nSyms * 3);
  BOOST_TEST(antiDCECounter);
}

BOOST_AUTO_TEST_CASE(cont_benchmark) {
  auto N = size_t{100};
  auto syms = Utility::GetIndices(N);
  auto gen = std::mt19937{};
  auto rand = std::uniform_int_distribution<size_t>(0, N - 1);
  auto layerRand = std::uniform_int_distribution<size_t>(0, 3);
  auto symmap = std::unordered_map<Sym, size_t>{};
  auto cnt = size_t{0};

  auto presses = std::vector<Press>{};
  auto pressmap = std::unordered_map<Press, size_t>{};
  for (auto i = size_t{0}; i != N; ++i) {
    auto press =
        Press{Key{static_cast<int>(i), static_cast<double>(layerRand(gen))},
              layerRand(gen)};
    presses.push_back(press);
    pressmap.emplace(press, i);
  }
  auto trand = Utility::Benchmark([&]() { cnt += rand(gen); }, 100000);
  auto tvec =
      Utility::Benchmark([&]() { cnt += syms[rand(gen)]; }, 10000) - trand;
  auto tmap =
      Utility::Benchmark([&]() { cnt += symmap[rand(gen)]; }, 10000) - trand;

  auto tpvec =
      Utility::Benchmark([&]() { cnt += presses[rand(gen)].key.row; }, 10000) -
      trand;
  auto tpmap = Utility::Benchmark(
                   [&]() {
                     auto &&press = presses[rand(gen)];
                     cnt += pressmap[press];
                   },
                   10000) -
               trand;
  BOOST_TEST(cnt);

  std::cout << std::endl;
  std::cout << "Benchmark of map and vector access where key is Sym:"
            << std::endl;
  std::cout << "With " << syms.size() << " Syms " << std::endl;
  std::cout << "Vector " << tvec.count() << " nanoseconds" << std::endl;
  std::cout << "Map    " << tmap.count() << " nanoseconds" << std::endl;
  std::cout << std::endl;
  std::cout << "Benchmark of map and vector access where key is Press:"
            << std::endl;
  std::cout << "With " << presses.size() << " Presses " << std::endl;
  std::cout << "Vector " << tpvec.count() << " nanoseconds" << std::endl;
  std::cout << "Map    " << tpmap.count() << " nanoseconds" << std::endl;
}

BOOST_AUTO_TEST_CASE(exprtk_example) {
  using SymbolTable = exprtk::symbol_table<Effort>;
  using Expression = exprtk::expression<Effort>;
  using Parser = exprtk::parser<Effort>;
  auto expr = std::string("sin(x)^8+cos(y)");
  auto x = 1.0;
  auto y = 2.0;

  auto symbolTable = SymbolTable{};
  auto expression = Expression{};
  symbolTable.add_constants();
  symbolTable.add_variable("x", x);
  symbolTable.add_variable("y", y);
  expression.register_symbol_table(symbolTable);
  auto parser = Parser{};

  auto cnt = 0.0;
  auto n = size_t{0};
  auto compileTime = Utility::Benchmark(
      [&]() {
        parser.compile(expr + std::to_string(n++), expression);
        cnt += expression.value();
      },
      5);

  std::cout << std::endl;
  std::cout << "ExprTk compile time benchmark" << std::endl;
  std::cout << "Time: " << compileTime.count() << " nanoseconds" << std::endl;
}


// Copyright © 2017-2019 Trust.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include <TrustWalletCore/TWUInt256.h>

#include <boost/lexical_cast.hpp>
#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::uint256_t;
using boost::multiprecision::cpp_int;

struct TWUInt256 TWUInt256Zero() {
    return TWUInt256{0};
}

struct TWUInt256 TWUInt256One(void) {
    TWUInt256 result{0};

    uint256_t i = 1;
    export_bits(i, std::make_reverse_iterator(result.bytes + 32), 8, false);

    return result;
}

bool TWUInt256InitWithData(struct TWUInt256 *_Nonnull instance, TWData *_Nonnull data) {
    const auto size = TWDataSize(data);
    if (size < 32) {
        memset(instance->bytes, 0, 32);
        TWDataCopyBytes(data, 0, TWDataSize(data), instance->bytes + 32 - TWDataSize(data));
    } else if (size == 32) {
        TWDataCopyBytes(data, 0, 32, instance->bytes);
    } else {
        return false;
    }
    return true;
}

bool TWUInt256InitWithString(struct TWUInt256 *_Nonnull instance, TWString *_Nonnull string) {
    try {
        uint256_t i(TWStringUTF8Bytes(string));
        export_bits(i, std::make_reverse_iterator(instance->bytes + 32), 8, false);
    } catch (std::runtime_error e) {
        return false;
    }
    return true;
}

void TWUInt256InitWithUInt32(struct TWUInt256 *_Nonnull instance, uint32_t value) {
    uint256_t i = value;
    std::fill(instance->bytes, instance->bytes + 32, 0);
    export_bits(i, std::make_reverse_iterator(instance->bytes + 32), 8, false);
}

void TWUInt256InitWithUInt64(struct TWUInt256 *_Nonnull instance, uint64_t value) {
    uint256_t i = value;
    std::fill(instance->bytes, instance->bytes + 32, 0);
    export_bits(i, std::make_reverse_iterator(instance->bytes + 32), 8, false);
}

bool TWUInt256IsZero(struct TWUInt256 value){
    uint256_t i;
    import_bits(i, value.bytes, value.bytes + 32);
    return i == 0;
}

uint32_t TWUInt256UInt32Value(struct TWUInt256 value) {
    uint256_t i;
    import_bits(i, value.bytes, value.bytes + 32);
    return static_cast<uint32_t>(i);
}

uint64_t TWUInt256UInt64Value(struct TWUInt256 value) {
    uint256_t i;
    import_bits(i, value.bytes, value.bytes + 32);
    return static_cast<uint32_t>(i);
}

TWData *_Nonnull TWUInt256Data(struct TWUInt256 value) {
    return TWDataCreateWithBytes(value.bytes, 32);
}

TWString *_Nonnull TWUInt256Description(struct TWUInt256 value) {
    uint256_t i;
    import_bits(i, value.bytes, value.bytes + 32);
    auto string = boost::lexical_cast<std::string>(i);
    return TWStringCreateWithUTF8Bytes(string.c_str());
}

bool TWUInt256Equal(struct TWUInt256 lhs, struct TWUInt256 rhs) {
    uint256_t i;
    uint256_t j;
    import_bits(i, lhs.bytes, lhs.bytes + 32);
    import_bits(j, rhs.bytes, rhs.bytes + 32);
    return i == j;
}

bool TWUInt256Less(struct TWUInt256 lhs, struct TWUInt256 rhs) {
    uint256_t i;
    uint256_t j;
    import_bits(i, lhs.bytes, lhs.bytes + 32);
    import_bits(j, rhs.bytes, rhs.bytes + 32);
    return i < j;
}

inline void formatPush(int n, std::string& s, int& decimals) {
    if (n > 0 || decimals <= 1) {
        s.push_back('0' + n);
    }
    if (decimals > 0 && decimals-- == 1) {
        s.push_back('.');
    }
}

TWString *_Nonnull TWUInt256Format(struct TWUInt256 value, int decimals) {
    uint256_t i;
    import_bits(i, value.bytes, value.bytes + 32);
    auto string = boost::lexical_cast<std::string>(i);
    if (decimals == 0) {
        return TWStringCreateWithUTF8Bytes(string.c_str());
    }

    if (string.size() <= decimals) {
        auto prefix = std::string("0.");
        auto padding = std::string(decimals - string.size(), '0');
        string.insert(string.begin(), padding.begin(), padding.end());
        string.insert(string.begin(), prefix.begin(), prefix.end());
    } else {
        string.insert(string.end() - decimals, '.');
    }

    auto zerosBegin = std::find_if(string.rbegin(), string.rbegin() + decimals - 1, [](auto& c){ return c != '0' && c != '.'; }).base();
    string.erase(zerosBegin, string.end());

    return TWStringCreateWithUTF8Bytes(string.c_str());
}


// Copyright Daniel Wallin 2006. Use, modification and distribution is
// subject to the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_TIME_SERIES_NUMERIC_FINE_GRAIN_060614_HPP
# define BOOST_TIME_SERIES_NUMERIC_FINE_GRAIN_060614_HPP

#include <boost/concept/requires.hpp>
#include <boost/detail/pod_singleton.hpp>
#include <boost/range_run_storage/algorithm/for_each.hpp>
#include <boost/time_series/concepts.hpp>
#include <boost/time_series/sparse_series.hpp>
#include <boost/time_series/piecewise_constant_series.hpp>
#include <boost/time_series/ordered_inserter.hpp>

namespace boost { namespace time_series
{

    namespace samplers
    {

        template <typename Derived>
        struct sampler_base
        {
            Derived const &cast() const
            {
                return static_cast<Derived const&>(*this);
            }
        };

        /// A fine_grain upsampler that makes a piecewise constant function
        /// with jumps at the coarse-grained points.
        ///
        struct piecewise_upsample
          : sampler_base<piecewise_upsample>
        {
            template <typename ValueType, typename Discretization>
            struct apply
            {
                typedef piecewise_constant_series<ValueType, Discretization> type;
            };

            template <typename Value, typename Offset, typename Out>
            void operator()(Out &out, Value value, Offset offset, Offset endoff, Offset factor) const
            {
                out(value, offset * factor, endoff * factor);
            }
        };

        /// A fine_grain upsampler that sets non-zero values at the coarse-grained
        /// points.
        ///
        struct sparse_upsample
          : sampler_base<sparse_upsample>
        {
            template <typename ValueType, typename Discretization>
            struct apply
            {
                typedef sparse_series<ValueType, Discretization> type;
            };

            template <typename Value, typename Offset, typename Out>
            void operator()(Out &out, Value value, Offset offset, Offset endoff, Offset factor) const
            {
                std::size_t length = endoff - offset;
                offset *= factor;

                while(length-- > 0)
                {
                    out(value, offset);
                    offset += factor;
                }
            }
        };

        /// INTERNAL ONLY
        template<typename UpSampler, typename Out, typename Offset>
        struct upsampler_functor
        {
            upsampler_functor(UpSampler upsampler, Out &out, Offset factor)
              : upsampler_(upsampler)
              , out_(out)
              , factor_(factor)
            {}

            template<typename Value>
            void operator ()(Value const &value, Offset start, Offset stop) const
            {
                this->upsampler_(this->out_, value, start, stop, this->factor_);
            }
        private:
            UpSampler upsampler_;
            Out &out_;
            Offset factor_;
        };

    } // namespace samplers

    namespace
    {
        samplers::piecewise_upsample const &piecewise_upsample
            = boost::detail::pod_singleton<samplers::piecewise_upsample>::instance;

        samplers::sparse_upsample const &sparse_upsample
            = boost::detail::pod_singleton<samplers::sparse_upsample>::instance;
    }

    /// \brief Generate a series of finer discretization from a series of coarser
    /// discretization according to a sampling policy.
    ///
    /// Generate a series of finer discretization from a series of coarser
    /// discretization according to a sampling policy.
    ///
    /// \param series The input series.
    /// \param discretization The finer discretization.
    /// \param upsampler The sampling policy. Could be \c piecewise_upsample or \c sparse_upsample.
    /// \param out An \c OrderedInserter into which to write the finer series.
    /// \return If you specify an \c OrderedInserter, the return value is a copy of the
    ///     \c OrderedInserter. Otherwise, if you specify the \c sparse_upsample, the return
    ///     value is a <tt>sparse_series\<\></tt> containing the finer-grained series. Otherwise,
    ///     if you specify the \c piecewise_upsample, the return value is a
    ///     <tt>piecewise_constant_series\<\></tt> containing the finer-grained series.
    /// \attention If using the version that takes an \c OrderedInserter, you must call
    ///     <tt>.commit()</tt> on the returned \c OrderedInserter when you are done with it.
    /// \pre The coarser discretization is a multiple of the finer discretization.
    template<typename Series, typename Discretization, typename UpSampler, typename Out>
    BOOST_CONCEPT_REQUIRES(
        ((concepts::TimeSeries<Series const>)),
    (ordered_inserter<Out>))
    fine_grain(
        Series const &series
      , Discretization discretization
      , samplers::sampler_base<UpSampler> const &upsampler
      , ordered_inserter<Out> out
    )
    {
        typedef typename concepts::TimeSeries<Series const>::offset_type offset_type;

        BOOST_ASSERT(series.discretization() > discretization);
        BOOST_ASSERT(series.discretization() % discretization == 0);

        offset_type factor = series.discretization() / discretization;
        samplers::upsampler_functor<UpSampler, ordered_inserter<Out>, offset_type>
            fun(upsampler.cast(), out, factor);

        range_run_storage::for_each(series, fun);
        return out;
    }

    /// \overload
    ///
    template <typename Series, typename Discretization, typename Out>
    BOOST_CONCEPT_REQUIRES(
        ((concepts::TimeSeries<Series const>)),
    (ordered_inserter<Out>))
    fine_grain(
        Series const &series
      , Discretization discretization
      , ordered_inserter<Out> out
    )
    {
        return time_series::fine_grain(
            series
          , discretization
          , piecewise_upsample
          , out
        );
    }

    /// \overload
    ///
    template<typename Series, typename Discretization, typename UpSampler>
    BOOST_CONCEPT_REQUIRES(
        ((concepts::TimeSeries<Series const>)),
    (typename mpl::apply_wrap2<
        UpSampler
      , typename concepts::TimeSeries<Series const>::value_type
      , Discretization
    >::type))
    fine_grain(
        Series const &series
      , Discretization discretization
      , samplers::sampler_base<UpSampler> const &upsampler
    )
    {
        typedef typename concepts::TimeSeries<Series const>::value_type value_type;

        BOOST_ASSERT(series.discretization() > discretization);
        BOOST_ASSERT(series.discretization() % discretization == 0);

        typedef typename mpl::apply_wrap2<
            UpSampler
          , value_type
          , Discretization
        >::type result_type;

        result_type result(time_series::discretization = discretization);

        time_series::fine_grain(
            series
          , discretization
          , upsampler.cast()
          , time_series::make_ordered_inserter(result)
        ).commit();

        return result;
    }

    /// \overload
    ///
    template <typename Series, typename Discretization>
    BOOST_CONCEPT_REQUIRES(
        ((concepts::TimeSeries<Series const>)),
    (typename mpl::apply_wrap2<
        samplers::piecewise_upsample
      , typename concepts::TimeSeries<Series const>::value_type
      , Discretization
    >::type))
    fine_grain(Series const &series, Discretization discretization)
    {
        return time_series::fine_grain(
            series
          , discretization
          , piecewise_upsample
        );
    }

}} // namespace boost::time_series

#endif // BOOST_TIME_SERIES_NUMERIC_FINE_GRAIN_060614_HPP


//          Copyright Oliver Kowalke 2009.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <iostream>
#include <cstdlib>
#include <stdexcept>

#include <boost/bind.hpp>

#include "boost/task.hpp"

namespace tsk = boost::tasks;

inline
int fibonacci_fn( int n)
{
	if ( n == 0) return 0;
	if ( n == 1) return 1;
	int k1( 1), k2( 0);
	for ( int i( 2); i <= n; ++i)
	{
		boost::this_thread::interruption_point();
		int tmp( k1);
		k1 = k1 + k2;
		k2 = tmp;
	}
	boost::this_thread::interruption_point();
	return k1;
}

int main( int argc, char *argv[])
{
	try
	{
		tsk::static_pool< tsk::unbounded_prio_queue< int > > pool( tsk::poolsize( 3) );

		tsk::task< int > t1( fibonacci_fn, 10);
		tsk::task< int > t2( fibonacci_fn, 10);
		tsk::task< int > t3( fibonacci_fn, 10);
		tsk::task< int > t4( fibonacci_fn, 10);

		tsk::handle< int > h1(
			tsk::async( boost::move( t1) ) );
		tsk::handle< int > h2(
			tsk::async(
				boost::move( t2),
				tsk::new_thread() ) );
		tsk::handle< int > h3(
			tsk::async(
				boost::move( t3),
				2,
				pool) );
		tsk::handle< int > h4(
			tsk::async(
				boost::move( t4),
				2,
				pool) );

		std::cout << h1.get() << std::endl;
		std::cout << h2.get() << std::endl;
		std::cout << h3.get() << std::endl;
		std::cout << h4.get() << std::endl;

		return EXIT_SUCCESS;
	}
	catch ( std::exception const& e)
	{ std::cerr << "exception: " << e.what() << std::endl; }
	catch ( ... )
	{ std::cerr << "unhandled" << std::endl; }

	return EXIT_FAILURE;
}


#include <boost/test/unit_test.hpp>

#include "moja/datarepository/tileblockcellindexer.h"
#include "moja/datarepository/datarepositoryexceptions.h"
#include "moja/datarepository/providerspatialrastertiled.h"
#include "moja/dynamicstruct.h"

#include <iomanip>
#include <iostream>
#include <vector>

using moja::datarepository::ProviderSpatialRasterTiled;
using moja::datarepository::FileNotFoundException;
using moja::datarepository::QueryException;
using namespace moja::datarepository;

struct ProviderSpatialRasterTiledTestsFixture {
	moja::DynamicObject settings;

	ProviderSpatialRasterTiledTestsFixture() {
	}

	~ProviderSpatialRasterTiledTestsFixture() {
	}
};

BOOST_FIXTURE_TEST_SUITE(TileBlockCellIteratorTests, ProviderSpatialRasterTiledTestsFixture);

BOOST_AUTO_TEST_CASE(DataRepository_Sanity_On_Creation) {
}

BOOST_AUTO_TEST_CASE(DataRepository_New_Iterator) {
	ProviderSpatialRasterTiled provider(settings);

	TileBlockCellIndexer indexer = { 1.0, 1.0, 0.1, 0.1, 0.00025, 0.00025 };	// 1.0 Degree Tiles
	moja::datarepository::Rectangle areaOfInterest{ Point{ 0.5, 34.5 }, { 1.0, 1.0 } };
	std::vector<std::vector<int>> tileBlockCellCount;
	tileBlockCellCount.resize(indexer.tileDesc.indexLimit);
	for (auto& tile : tileBlockCellCount)
	{
		tile.resize(indexer.blockDesc.indexLimit);
		for (auto& blockCount : tile) {
			blockCount = 0;
		}
	}

	//std::cout << "Tile\tblock\tcell\t" << std::endl;
	for (auto& cell : provider.cells(areaOfInterest))
	{
		auto& count = tileBlockCellCount[cell.tileIdx][cell.blockIdx];
		count++;
		////if (cell.cellIdx % 160000 == 0)
		//	std::cout << cell.tileIdx << "\t" << cell.blockIdx << "\t" << cell.cellIdx << std::endl;
	}

	std::cout << "Tile\tblock\tcell count\t" << std::endl;
	int tileIdx = 0;
	for (auto& tile : tileBlockCellCount)
	{
		int blockIdx = 0;
		for (auto& blockCount : tile) {
			if (blockCount > 0)
				std::cout << tileIdx << "\t" << blockIdx << "\t" << blockCount << std::endl;
			blockIdx++;
		}
		tileIdx++;
	}
}

BOOST_AUTO_TEST_SUITE_END();


/*
 * This module contains the implementation of several
 * kind of integrals used for non-adiabatic molecular dynamics,
 * including the overlaps integrals between different geometries
 * And the dipoles and quadrupoles to compute absorption spectra.
 * This module is based on libint, Eigen and pybind11.
 * Copyright (C) 2018-2020 the Netherlands eScience Center.
 */

#ifndef NAMD_H_
#define NAMD_H_

#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <thread>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>

// integrals library
#include <libint2.hpp>

#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>

// Eigen matrix algebra library
#include <Eigen/Dense>

// HDF5 funcionality
#include <highfive/H5DataSet.hpp>
#include <highfive/H5DataSpace.hpp>
#include <highfive/H5File.hpp>

#if defined(_OPENMP)
#include <omp.h>
#endif

namespace namd {

using real_t = libint2::scalar_type;
// import dense, dynamically sized Matrix type from Eigen;
// this is a matrix with row-major storage
// (http://en.wikipedia.org/wiki/Row-major_order) to meet the layout of the
// integrals returned by the Libint integral library
using Matrix =
    Eigen::Matrix<real_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;

struct CP2K_Basis_Atom {
  // Contains the basis specificationf for a given atom
  std::string symbol;
  libint2::svector<libint2::svector<double>> coefficients;
  libint2::svector<double> exponents;
  libint2::svector<int> basis_format;
};

// Map from atomic_number to symbol
std::unordered_map<int, std::string> map_elements = {
    {1, "h"},   {2, "he"},  {3, "li"},  {4, "be"},  {5, "b"},   {6, "c"},
    {7, "n"},   {8, "o"},   {9, "f"},   {10, "ne"}, {11, "na"}, {12, "mg"},
    {13, "al"}, {14, "si"}, {15, "p"},  {16, "s"},  {17, "cl"}, {18, "ar"},
    {19, "k"},  {20, "ca"}, {21, "sc"}, {22, "ti"}, {23, "v"},  {24, "cr"},
    {25, "mn"}, {26, "fe"}, {27, "co"}, {28, "ni"}, {29, "cu"}, {30, "zn"},
    {31, "ga"}, {32, "ge"}, {33, "as"}, {34, "se"}, {35, "br"}, {36, "kr"},
    {37, "rb"}, {38, "sr"}, {39, "y"},  {40, "zr"}, {41, "nb"}, {42, "mo"},
    {43, "tc"}, {44, "ru"}, {45, "rh"}, {46, "pd"}, {47, "ag"}, {48, "cd"},
    {49, "in"}, {50, "sn"}, {51, "sb"}, {52, "te"}, {53, "i"},  {54, "xe"},
    {55, "cs"}, {56, "ba"}, {57, "la"}, {58, "ce"}, {59, "pr"}, {60, "nd"},
    {61, "pm"}, {62, "sm"}, {63, "eu"}, {64, "gd"}, {65, "tb"}, {66, "dy"},
    {67, "ho"}, {68, "er"}, {69, "tm"}, {70, "yb"}, {71, "lu"}, {72, "hf"},
    {73, "ta"}, {74, "w"},  {75, "re"}, {76, "os"}, {77, "ir"}, {78, "pt"},
    {79, "au"}, {80, "hg"}, {81, "tl"}, {82, "pb"}, {83, "bi"}, {84, "po"},
    {85, "at"}, {86, "rn"}, {87, "fr"}, {88, "ra"}, {89, "ac"}, {90, "th"},
    {91, "pa"}, {92, "u"},  {93, "np"}, {94, "pu"}, {95, "am"}, {96, "cm"}};

} // namespace namd
#endif // NAMD_H_


#include <ripple/basics/strHex.h>
#include <ripple/crypto/KeyType.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/jss.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/SecretKey.h>
#include <ripple/protocol/Seed.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/handlers/WalletPropose.h>
#include <ed25519-donna/ed25519.h>
#include <boost/optional.hpp>
#include <cmath>
#include <map>

namespace ripple {

double
estimate_entropy (std::string const& input)
{
    std::map<int, double> freq;

    for (auto const& c : input)
        freq[c]++;

    double se = 0.0;

    for (auto const& f : freq)
    {
        auto x = f.second / input.length();
        se += (x) * log2(x);
    }

    return std::floor (-se * input.length());
}

Json::Value doWalletPropose (RPC::Context& context)
{
    return walletPropose (context.params);
}

Json::Value walletPropose (Json::Value const& params)
{
    boost::optional<KeyType> keyType;
    boost::optional<Seed> seed;
    bool rippleLibSeed = false;

    if (params.isMember (jss::key_type))
    {
        if (! params[jss::key_type].isString())
        {
            return RPC::expected_field_error (
                jss::key_type, "string");
        }

        keyType = keyTypeFromString (
            params[jss::key_type].asString());

        if (!keyType)
            return rpcError(rpcINVALID_PARAMS);
    }

    {
        if (params.isMember(jss::passphrase))
            seed = RPC::parseRippleLibSeed(params[jss::passphrase]);
        else if (params.isMember(jss::seed))
            seed = RPC::parseRippleLibSeed(params[jss::seed]);

        if(seed)
        {
            rippleLibSeed = true;

            if (keyType.value_or(KeyType::ed25519) != KeyType::ed25519)
                return rpcError(rpcBAD_SEED);

            keyType = KeyType::ed25519;
        }
    }

    if (!seed)
    {
        if (params.isMember(jss::passphrase) ||
            params.isMember(jss::seed) ||
            params.isMember(jss::seed_hex))
        {
            Json::Value err;

            seed = RPC::getSeedFromRPC(params, err);

            if (!seed)
                return err;
        }
        else
        {
            seed = randomSeed();
        }
    }

    if (!keyType)
        keyType = KeyType::secp256k1;

    auto const publicKey = generateKeyPair (*keyType, *seed).first;

    Json::Value obj (Json::objectValue);

    auto const seed1751 = seedAs1751 (*seed);
    auto const seedHex = strHex (*seed);
    auto const seedBase58 = toBase58 (*seed);

    obj[jss::master_seed] = seedBase58;
    obj[jss::master_seed_hex] = seedHex;
    obj[jss::master_key] = seed1751;
    obj[jss::account_id] = toBase58(calcAccountID(publicKey));
    obj[jss::public_key] = toBase58(TokenType::AccountPublic, publicKey);
    obj[jss::key_type] = to_string (*keyType);
    obj[jss::public_key_hex] = strHex (publicKey);

    if (!rippleLibSeed && params.isMember (jss::passphrase))
    {
        auto const passphrase = params[jss::passphrase].asString();

        if (passphrase != seed1751 &&
            passphrase != seedBase58 &&
            passphrase != seedHex)
        {
            if (estimate_entropy (passphrase) < 80.0)
                obj[jss::warning] =
                    "This wallet was generated using a user-supplied "
                    "passphrase that has low entropy and is vulnerable "
                    "to brute-force attacks.";
            else
                obj[jss::warning] =
                    "This wallet was generated using a user-supplied "
                    "passphrase. It may be vulnerable to brute-force "
                    "attacks.";
        }
    }

    return obj;
}

}