#include "../Common.h"

#include <carl/core/Variable.h>
#include <carl/core/Monomial.h>
#include <carl/core/Term.h>
#include <carl/core/MultivariatePolynomial.h>

#include <boost/concept_check.hpp>
#include "gtest/gtest.h"

using namespace carl;

template<typename T1, typename T2>
struct Comparison {
private:
	T1 lhs;
	T2 rhs;
public:
	BOOST_CONCEPT_USAGE(Comparison) {
		require_type<bool>(lhs == rhs);
		require_type<bool>(lhs == rhs);
		require_type<bool>(lhs != rhs);
		require_type<bool>(lhs < rhs);
		require_type<bool>(lhs <= rhs);
		require_type<bool>(lhs > rhs);
		require_type<bool>(lhs >= rhs);
	}
};

template<typename T1, typename T2, typename R>
struct Addition {
private:
	T1 lhs;
	T2 rhs;
public:
	BOOST_CONCEPT_USAGE(Addition) {
		require_type<R>(lhs + rhs);
		require_type<R>(lhs - rhs);
	}
};

template<typename T1>
struct UnaryMinus {
private:
	T1 lhs;
public:
	BOOST_CONCEPT_USAGE(UnaryMinus) {
		require_type<T1>(-lhs);
	}
};

template<typename T1, typename T2, typename R>
struct Multiplication {
private:
	T1 lhs;
	T2 rhs;
public:
	BOOST_CONCEPT_USAGE(Multiplication) {
		require_type<R>(lhs * rhs);
	}
};

template<typename T1, typename T2>
struct InplaceAddition {
private:
	T1 lhs;
	T2 rhs;
public:
	BOOST_CONCEPT_USAGE(InplaceAddition) {
		require_type<T1>(lhs += rhs);
		require_type<T1>(lhs -= rhs);
	}
};

template<typename T1, typename T2>
struct InplaceMultiplication {
private:
	T1 lhs;
	T2 rhs;
public:
	BOOST_CONCEPT_USAGE(InplaceMultiplication) {
		require_type<T1>(lhs *= rhs);
	}
};

template<typename T>
class Operators: public testing::Test {};

TYPED_TEST_CASE(Operators, RationalTypes);

TYPED_TEST(Operators, Comparison)
{
	BOOST_CONCEPT_ASSERT((Comparison<TypeParam, TypeParam>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::Variable>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::Variable>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::Monomial::Arg>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, TypeParam>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<TypeParam, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, TypeParam>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<TypeParam, carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(Operators, Addition)
{
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, TypeParam, TypeParam>));

	BOOST_CONCEPT_ASSERT((Addition<carl::Variable, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Addition<carl::Monomial::Arg, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Monomial::Arg, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Variable, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Variable, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(Operators, UnaryMinus)
{
	BOOST_CONCEPT_ASSERT((UnaryMinus<TypeParam>));
	BOOST_CONCEPT_ASSERT((UnaryMinus<carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((UnaryMinus<carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(Operators, Multiplication)
{
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, TypeParam, TypeParam>));

    BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::Variable, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, TypeParam, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::Variable, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::Monomial::Arg, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::Variable, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, TypeParam, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::Monomial::Arg, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::Monomial::Arg, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::Term<TypeParam>, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::Monomial::Arg, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::Variable, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, TypeParam, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::Term<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::Term<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::Term<TypeParam>, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(Operators, InplaceAddition)
{
	BOOST_CONCEPT_ASSERT((InplaceAddition<TypeParam, TypeParam>));

	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, TypeParam>));
}

TYPED_TEST(Operators, InplaceMultiplication)
{
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Monomial::Arg, carl::Monomial::Arg>));
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Monomial::Arg, carl::Variable>));

	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, TypeParam>));

	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, TypeParam>));
}

template<typename T>
class OperatorsInterval: public testing::Test {};

TYPED_TEST_CASE(OperatorsInterval, IntervalTypes);

TYPED_TEST(OperatorsInterval, Comparison)
{
	BOOST_CONCEPT_ASSERT((Comparison<TypeParam, TypeParam>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::Variable>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::Variable>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::Monomial::Arg>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, TypeParam>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<TypeParam, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::MultivariatePolynomial<TypeParam>, TypeParam>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Comparison<TypeParam, carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(OperatorsInterval, Addition)
{
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, TypeParam, TypeParam>));

	BOOST_CONCEPT_ASSERT((Addition<carl::Variable, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Addition<carl::Monomial::Arg, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Monomial::Arg, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Variable, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::MultivariatePolynomial<TypeParam>, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<carl::Variable, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Addition<TypeParam, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(OperatorsInterval, UnaryMinus)
{
	BOOST_CONCEPT_ASSERT((UnaryMinus<TypeParam>));
	BOOST_CONCEPT_ASSERT((UnaryMinus<carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((UnaryMinus<carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(OperatorsInterval, Multiplication)
{
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, TypeParam, TypeParam>));

    BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::Variable, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, TypeParam, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::Variable, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::Monomial::Arg, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::Variable, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, TypeParam, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::Monomial::Arg, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::Monomial::Arg, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::Term<TypeParam>, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::Monomial::Arg, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::Variable, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, TypeParam, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::Term<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::Term<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::Term<TypeParam>, carl::Term<TypeParam>>));

	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, carl::Variable, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::MultivariatePolynomial<TypeParam>, TypeParam, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Term<TypeParam>, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((Multiplication<carl::Monomial::Arg, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<carl::Variable, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((Multiplication<TypeParam, carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
}

TYPED_TEST(OperatorsInterval, InplaceAddition)
{
	BOOST_CONCEPT_ASSERT((InplaceAddition<TypeParam, TypeParam>));

	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((InplaceAddition<carl::MultivariatePolynomial<TypeParam>, TypeParam>));
}

TYPED_TEST(OperatorsInterval, InplaceMultiplication)
{
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Monomial::Arg, carl::Monomial::Arg>));
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Monomial::Arg, carl::Variable>));

	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::Term<TypeParam>, TypeParam>));

	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::MultivariatePolynomial<TypeParam>>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::Term<TypeParam>>));
	//BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::Monomial::Arg>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, carl::Variable>));
	BOOST_CONCEPT_ASSERT((InplaceMultiplication<carl::MultivariatePolynomial<TypeParam>, TypeParam>));
}


// $COPYRIGHT$

#ifndef MATH_CONCEPT_MAPS_INCLUDE
#define MATH_CONCEPT_MAPS_INCLUDE

#include <boost/numeric/linear_algebra/intrinsic_concept_maps.hpp>
#include <boost/numeric/linear_algebra/new_concepts.hpp>

namespace math {

#if 0 // Why this doesn't work???
    template <typename T>
        requires IntrinsicUnsignedIntegral<T>
    concept_map UnsignedIntegral<T> {}

    template <typename T>
        requires IntrinsicSignedIntegral<T>
    concept_map SignedIntegral<T> {}
#endif

    concept_map UnsignedIntegral<unsigned int> {}
    concept_map AdditiveMonoid<unsigned int> {}

    concept_map SignedIntegral<int> {}
    concept_map AdditiveSemiGroup<int> {}

    concept_map Commutative< add<int>, int > {}
    concept_map Monoid< add<int>, int > {}
    concept_map AbelianGroup< add<int>, int > {}

    concept_map Commutative< mult<int>, int > {}
    concept_map Monoid< mult<int>, int > {}
    concept_map PIMonoid< mult<int>, int > {}

    concept_map Commutative< min<int>, int > {}
    concept_map Monoid< min<int>, int > {}

    concept_map Commutative< max<int>, int > {}
    //concept_map Monoid< max<int>, int > {}

    concept_map Commutative< add<float>, float > {}
    concept_map Monoid< add<float>, float > {}
    concept_map AbelianGroup< add<float>, float > {}

    concept_map Commutative< mult<float>, float > {}
    concept_map Monoid< mult<float>, float > {}
    concept_map PIMonoid< mult<float>, float > {}

    concept_map AdditiveMonoid< float > {}
    // concept_map AdditiveAbelianGroup< float > {}

    // concept_map OperatorField<float> {}

    concept_map Commutative< min<float>, float > {}
    concept_map Monoid< min<float>, float > {}

    concept_map Commutative< max<float>, float > {}
    concept_map Monoid< max<float>, float > {}

    concept_map Commutative< add<double>, double > {}
    concept_map Monoid< add<double>, double > {}
    concept_map AbelianGroup< add<double>, double > {}

    concept_map Commutative< mult<double>, double > {}
    concept_map Monoid< mult<double>, double > {}
    concept_map PIMonoid< mult<double>, double > {}

    concept_map AdditiveMonoid< double > {}
    // concept_map AdditiveAbelianGroup< double > {}

    // concept_map OperatorField<double> {}

    concept_map Commutative< min<double>, double > {}
    concept_map Monoid< min<double>, double > {}

    concept_map Commutative< max<double>, double > {}
    concept_map Monoid< max<double>, double > {}

} // namespace math

#endif // MATH_CONCEPT_MAPS_INCLUDE


/*
 * @copyright Copyright (c) 2017-2020 CERN and the Allpix Squared authors.
 * This software is distributed under the terms of the MIT License, copied verbatim in the file "LICENSE.md".
 * In applying this license, CERN does not waive the privileges and immunities granted to it by virtue of its status as an
 * Intergovernmental Organization or submit itself to any jurisdiction.
 */

#include <map>
#include <memory>
#include <string>
#include <vector>

#include <TFile.h>
#include <TGraph.h>
#include <TH1D.h>
#include <TH2D.h>

#include <Eigen/Geometry>

#include "core/config/Configuration.hpp"
#include "core/geometry/GeometryManager.hpp"
#include "core/messenger/Messenger.hpp"
#include "core/module/Event.hpp"
#include "core/module/Module.hpp"

#include "objects/Pixel.hpp"
#include "objects/PixelCharge.hpp"
#include "objects/PropagatedCharge.hpp"

namespace allpix {
    /**
     * @ingroup Modules
     * @brief Module that directly converts propagated charges to charges on a pixel and its neighbours (simulating the
     * cross-coupling in CCPDs)
     * This module is based on the SimpleTransferModule. It does a mapping of the propagated charges to the nearest pixel in
     * the grid and copies this propageted charge, scaled by the cross-coupling matrix, to the neighbouring pixels.
     * The coupling matrix must be provided in the configuration file as a Matrix or as a matrix file.
     * Like the SimpleTransferModule, it only considers propagated charges within a certain distance from the implants and
     * within the pixel grid, charges in the rest of the sensor are ignored.
     * The cross hit created in the neighbouring pixels keeps the history, saving from where the original charge came from.
     */

    class CapacitiveTransferModule : public Module {
    public:
        /**
         * @brief Constructor for this detector-specific module
         * @param config Configuration object for this module as retrieved from the steering file
         * @param messenger Pointer to the messenger object to allow binding to messages on the bus
         * @param detector Pointer to the detector for this module instance
         */
        CapacitiveTransferModule(Configuration& config, Messenger* messenger, std::shared_ptr<Detector> detector);

        /**
         * @brief Initialize the module, creating the cross-coupling matrixs
         */
        void initialize() override;

        /**
         * @brief Transfer the propagated charges to the pixels and its neighbours
         */
        void run(Event*) override;

        /**
         * @brief Display statistical summary
         */
        void finalize() override;

    private:
        // Configuration config_;
        Messenger* messenger_;
        std::shared_ptr<Detector> detector_;
        std::shared_ptr<DetectorModel> model_;

        // Statistical information
        std::atomic<unsigned int> total_transferred_charges_{};

        // Matrix to store cross-coupling values
        std::vector<std::vector<double>> relative_coupling_;
        unsigned int matrix_rows_{};
        unsigned int matrix_cols_{};
        unsigned int max_row_{};
        unsigned int max_col_{};

        double normalization_{};
        double max_depth_distance_{};
        bool cross_coupling_{};

        void getCapacitanceScan(TFile* root_file);
        TGraph* capacitances_[9]{};

        Eigen::Hyperplane<double, 3> plane_;

        Histogram<TH2D> coupling_map;
        Histogram<TH2D> gap_map;
        Histogram<TH2D> capacitance_map;
        Histogram<TH2D> relative_capacitance_map;
    };
} // namespace allpix


// Copyright (c) 2015-2018 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "mapping_quality_divergence.hpp"

#include <algorithm>
#include <iterator>
#include <cmath>
#include <limits>
#include <numeric>

#include <boost/variant.hpp>

#include "io/variant/vcf_record.hpp"
#include "basics/aligned_read.hpp"
#include "../facets/read_assignments.hpp"

namespace octopus { namespace csr {

const std::string MappingQualityDivergence::name_ = "MQD";

std::unique_ptr<Measure> MappingQualityDivergence::do_clone() const
{
    return std::make_unique<MappingQualityDivergence>(*this);
}

namespace {

using MappingQualityVector = std::vector<AlignedRead::MappingQuality>;

auto extract_mapping_qualities(const std::vector<AlignedRead>& reads)
{
    MappingQualityVector result(reads.size());
    std::transform(std::cbegin(reads), std::cend(reads), std::begin(result),
                   [](const auto& read) { return read.mapping_quality(); });
    return result;
}

auto extract_mapping_qualities(const HaplotypeSupportMap& support)
{
    std::vector<MappingQualityVector> result{};
    result.reserve(support.size());
    for (const auto& p : support) {
        result.push_back(extract_mapping_qualities(p.second));
    }
    return result;
}

using MappingQualityHistogram = std::vector<unsigned>;

auto make_histogram(const MappingQualityVector& mapping_qualities, const unsigned prior = 1)
{
    using Q = AlignedRead::MappingQuality;
    static constexpr unsigned max_qualities {std::numeric_limits<Q>::max() - std::numeric_limits<Q>::min()};
    MappingQualityHistogram result(max_qualities, prior);
    for (auto quality : mapping_qualities) {
        ++result[quality];
    }
    return result;
}

auto convert_to_probabilities(const MappingQualityHistogram& counts)
{
    const auto norm = std::accumulate(std::cbegin(counts), std::cend(counts), 0.0);
    std::vector<double> result(counts.size());
    std::transform(std::cbegin(counts), std::cend(counts), std::begin(result),
                   [norm] (const auto c) { return static_cast<double>(c) / norm; });
    return result;
}

auto kl_divergence(const std::vector<double>& p, const std::vector<double>& q) noexcept
{
    return std::inner_product(std::cbegin(p), std::cend(p), std::cbegin(q), 0.0,
                              std::plus<> {}, [] (const auto a, const auto b) {
        return a * std::log(a / b);
    });
}

auto symmetric_kl_divergence(const std::vector<double>& p, const std::vector<double>& q) noexcept
{
    return kl_divergence(p, q) + kl_divergence(q, p);
}

double calculate_max_pairwise_kl_divergence(const std::vector<std::vector<double>>& mq_distributions)
{
    double result {0};
    const auto num_distributions = mq_distributions.size();
    if (num_distributions < 2) return result;
    for (std::size_t i {0}; i < num_distributions - 1; ++i) {
        for (auto j = i + 1; j < num_distributions; ++j) {
            result = std::max(result, symmetric_kl_divergence(mq_distributions[i], mq_distributions[j]));
        }
    }
    return result;
}

double calculate_max_pairwise_kl_divergence(const std::vector<MappingQualityVector>& mapping_qualities)
{
    std::vector<std::vector<double>> mq_distributions {};
    mq_distributions.reserve(mapping_qualities.size());
    for (const auto& mqs : mapping_qualities) {
        mq_distributions.push_back(convert_to_probabilities(make_histogram(mqs)));
    }
    return calculate_max_pairwise_kl_divergence(mq_distributions);
}

} // namespace

Measure::ResultType MappingQualityDivergence::do_evaluate(const VcfRecord& call, const FacetMap& facets) const
{
    const auto& assignments = get_value<ReadAssignments>(facets.at("ReadAssignments")).support;
    boost::optional<double> result {0};
    for (const auto& p : assignments) {
        if (call.is_heterozygous(p.first)) {
            auto mapping_qualities = extract_mapping_qualities(p.second);
            const auto kl = calculate_max_pairwise_kl_divergence(mapping_qualities);
            if (result) {
                result = std::max(*result, kl);
            } else {
                result = kl;
            }
        }
    }
    return result;
}

Measure::ResultCardinality MappingQualityDivergence::do_cardinality() const noexcept
{
    return ResultCardinality::one;
}

const std::string& MappingQualityDivergence::do_name() const
{
    return name_;
}

std::string MappingQualityDivergence::do_describe() const
{
    return "Symmetric KL divergence of reads supporting the REF verses ALT alleles";
}

std::vector<std::string> MappingQualityDivergence::do_requirements() const
{
    return {"ReadAssignments"};
}

} // namespace csr
} // namespace octopus


#include <algorithm>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <future>
#include <iostream>
#include <limits>
#include <random>
#include <thread>

#include <boost/math/special_functions/gamma.hpp>
#include <boost/math/tools/roots.hpp>

#include <RcppCommon.h>

#include "simple_sim.h"

class SimulationConfig;
RCPP_EXPOSED_CLASS(SimulationConfig)

#include <Rcpp.h>

using namespace Rcpp;

using StepGenerator =
    std::function<std::function<double(std::default_random_engine&)>()>;

class DiscreteStep{
 public:
  DiscreteStep(std::vector<double> values, std::vector<double> probs)
      : values(values), distribution(probs.begin(), probs.end()) {}

  double operator()(std::default_random_engine& rng) {
    return values[distribution(rng)];
  }

 private:
  const std::vector<double> values;
  std::discrete_distribution<> distribution;
};

bool NormalMixtureStrategy::can_reject(double mean, const RunningStats& stats,
                                       const Support support) const {
  const double s = stats.martingale(mean);
  const double v = v_factor(support) * stats.t;
  return get_mixture(support).log_superMG(s, v) >= log(1 / alpha_);
}

double NormalMixtureStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  const double v = v_factor(support) * stats.t;
  const double radius =
      get_mixture(support).bound(v, log(1 / alpha_)) / stats.t;
  return std::max(support.min, stats.mean() - radius);
}

bool BetaBinomialStrategy::can_reject(double mean, const RunningStats& stats,
                                      const Support support) const {
  const double s = stats.martingale(mean);
  const double v = (support.max - mean) * (mean - support.min) * stats.t;
  return get_mixture(mean, support).log_superMG(s, v) >= log(1 / alpha_);
}

double bernoulli_lower_bound(
    const double empirical_mean, const int num_trials, const Support support,
    const double alpha, const double v_opt, const double alpha_opt) {
  const double threshold = log(1 / alpha);
  const double empirical_p =
      (empirical_mean - support.min) / (support.max - support.min);
  if (empirical_p <= 1e-8) {
    return 0;
  }

  auto objective = [empirical_mean, num_trials, support, v_opt, alpha_opt,
                    threshold](const double p) {
    if (p <= 1e-8) {
      return 1.0;
    } else if (p >= 1 - 1e-8) {
      return -1.0;
    } else {
      const double mean = support.min + p * (support.max - support.min);
      const double g = mean - support.min;
      const double h = support.max - mean;
      const confseq::BetaBinomialMixture mixture(v_opt, alpha_opt, g, h, false);
      const double log_superMG = mixture.log_superMG(
          (empirical_mean - mean) * num_trials, g * h * num_trials);
      return log_superMG - threshold;
    }
  };

  assert(objective(empirical_p) < 0);
  boost::math::tools::eps_tolerance<double> tolerance(40);
  auto lower_bound_pair = boost::math::tools::bisect(
      objective, 0.0, empirical_p, tolerance);
  const double p_lower_bound =
      (lower_bound_pair.first + lower_bound_pair.second) / 2.0;
  return support.min + p_lower_bound * (support.max - support.min);
}

double BetaBinomialStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  return bernoulli_lower_bound(
      stats.mean(), stats.t, support, alpha_, v_opt_, alpha_);
}

bool EmpiricalBernsteinStrategy::can_reject(
    double mean, const RunningStats& stats, const Support support) const {
  const double s = abs(stats.martingale(mean));
  const double v = stats.sum_sq_prediction_errors;
  return get_mixture(support).log_superMG(s, v) >= log(2 / alpha_);
}

double EmpiricalBernsteinStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  const double radius = get_mixture(support).bound(
      stats.sum_sq_prediction_errors, log(2 / alpha_)) / stats.t;
  return std::max(support.min, stats.mean() - radius);
}

bool NaiveSelfNormalizedStrategy::can_reject(
    double mean, const RunningStats& stats, const Support support) const {
  const double s = stats.martingale(mean);
  const double v = stats.sum_sq_prediction_errors;
  return mixture_.log_superMG(s, v) >= log(1 / alpha_);
}

double NaiveSelfNormalizedStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  const double radius = mixture_.bound(
      stats.sum_sq_prediction_errors, log(1 / alpha_)) / stats.t;
  return std::max(support.min, stats.mean() - radius);
}

double ZTestStrategy::z_test_radius(const int t, const double mean,
                                    const Support support) const {
  double sample_p = (mean - support.min) / (support.max - support.min);
  double std_dev = sqrt(t * sample_p * (1 - sample_p))
      * (support.max - support.min);
  return z_factor_ * std_dev;
}

bool ZTestStrategy::can_reject(double mean, const RunningStats& stats,
                               const Support support) const {
  double radius = z_test_radius(stats.t, mean, support);
  return abs(stats.martingale(mean)) >= radius;
}

double ZTestStrategy::find_lower_confidence_bound(const RunningStats& stats,
                                                  const Support support) const {
  double ci_radius = z_test_radius(stats.t, stats.mean(), support)
      / stats.t;
  return std::max(stats.mean() - ci_radius, support.min);
}

double PointwiseBernoulliStrategy::bernoulli_psi_star(double u, double g,
                                                      double h) const {
  double q = g * (1 + h * u) / (g + h);
  assert(-1e-8 <= q && q <= 1 + 1e-8);
  double p = g / (g + h);
  if (q <= 0) {
    return -log(1 - p) / (g * h);
  } else if (q >= 1) {
    return -log(p) / (g * h);
  } else {
    return 1 / (g * h) * (q * log(q / p) + (1 - q) * log((1 - q) / (1 - p)));
  }
}

double PointwiseBernoulliStrategy::tail_bound_exponent(
    const double mean, const RunningStats& stats, const Support support) const {
  const double g = mean - support.min;
  const double h = support.max - mean;
  const double s = stats.martingale(mean);
  const double v = g * h * stats.t;
  return v * bernoulli_psi_star(s / v, g, h);
}

bool PointwiseBernoulliStrategy::can_reject(
    double mean, const RunningStats& stats, const Support support) const {
  return tail_bound_exponent(mean, stats, support) >= log(2 / alpha_);
}

double PointwiseBernoulliStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  const double threshold = log(2 / alpha_);
  boost::math::tools::eps_tolerance<double> tolerance(40);
  auto objective = [this, stats, support, threshold](const double mean) {
    if (mean <= support.min) {
      return 1.0;
    } else if (mean >= support.max) {
      return -1.0;
    } else {
      return tail_bound_exponent(mean, stats, support) - threshold;
    }
  };

  if (stats.mean() > support.min) {
    auto lower_bound_pair = boost::math::tools::bisect(
        objective, support.min, stats.mean(), tolerance);
    return (lower_bound_pair.first + lower_bound_pair.second) / 2.0;
  } else {
    return support.min;
  }
}

double PointwiseHoeffdingStrategy::radius(const int t, const Support support)
    const {
  return sqrt(2 * NormalMixtureStrategy::v_factor(support) * t *
              log(2 / alpha_));
}

bool PointwiseHoeffdingStrategy::can_reject(
    double mean, const RunningStats& stats, const Support support) const {
  return abs(stats.martingale(mean)) >= radius(stats.t, support);
}

double PointwiseHoeffdingStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  return std::max(stats.mean() - radius(stats.t, support) / stats.t,
                  support.min);
}

double LinearBoundStrategy::boundary(const int t, const Support support) const {
  const double v_factor = NormalMixtureStrategy::v_factor(support);
  const double m = v_factor * t_opt_;
  const double Vt = v_factor * t;
  const double x = sqrt(2 * m * log(2 / alpha_));
  return x + x / (2 * m) * (Vt - m);
}

bool LinearBoundStrategy::can_reject(
    double mean, const RunningStats& stats, const Support support) const {
  return abs(stats.martingale(mean)) >= boundary(stats.t, support);
}

double LinearBoundStrategy::find_lower_confidence_bound(
    const RunningStats& stats, const Support support) const {
  return std::max(stats.mean() - boundary(stats.t, support) / stats.t,
                  support.min);
}

double get_width(const Strategy& strategy, const RunningStats& stats,
                 const Support support) {
  double lower = strategy.find_lower_confidence_bound(stats, support);
  double upper = -strategy.find_lower_confidence_bound(stats.negate(),
                                                       support.negate());
  return upper - lower;
}

class SimulationConfig {
 public:
  SimulationConfig(int num_walks, int num_steps, int num_threads,
                   std::vector<double> evaluate_width_times)
      : num_threads(num_threads), num_walks(num_walks), num_steps(num_steps),
        evaluate_width_times(evaluate_width_times) {
    std::vector<double> values = {-1, 1};
    std::vector<double> probs = {0.5, 0.5};
    discrete_steps(values, probs);
    normal_mixture(10, 0.05);
  }

  SimulationConfig& discrete_steps(std::vector<double> values,
                                   std::vector<double> probs) {
    draw_step = [=]() {return DiscreteStep(values, probs);};
    support = Support(*std::min_element(values.begin(), values.end()),
                      *std::max_element(values.begin(), values.end()));
    return *this;
  }

  SimulationConfig& normal_mixture(double t_opt, double alpha) {
    strategy = std::make_shared<NormalMixtureStrategy>(t_opt, alpha);
    return *this;
  }

  SimulationConfig& beta_binomial_mixture(double v_opt, double alpha) {
    strategy = std::make_shared<BetaBinomialStrategy>(v_opt, alpha);
    return *this;
  }

  SimulationConfig& empirical_bernstein(double v_opt, double alpha) {
    strategy = std::make_shared<EmpiricalBernsteinStrategy>(v_opt, alpha);
    return *this;
  }

  SimulationConfig& naive_self_normalized(double v_opt, double alpha) {
    strategy = std::make_shared<NaiveSelfNormalizedStrategy>(v_opt, alpha);
    return *this;
  }

  SimulationConfig& pointwise_bernoulli(double alpha) {
    strategy = std::make_shared<PointwiseBernoulliStrategy>(alpha);
    return *this;
  }

  SimulationConfig& z_test(double alpha) {
    strategy = std::make_shared<ZTestStrategy>(alpha);
    return *this;
  }

  SimulationConfig& pointwise_hoeffding(double alpha) {
    strategy = std::make_shared<PointwiseHoeffdingStrategy>(alpha);
    return *this;
  }

  SimulationConfig& linear_bound(int t_opt, double alpha) {
    strategy = std::make_shared<LinearBoundStrategy>(t_opt, alpha);
    return *this;
  }

  const int num_threads;
  const int num_walks;
  const int num_steps;
  const std::vector<double> evaluate_width_times;
  StepGenerator draw_step;
  std::shared_ptr<const Strategy> strategy;
  Support support;
};

int simulate_one_walk(const SimulationConfig& config,
                      std::default_random_engine &rng,
                      std::vector<double>& walk_widths) {
  RunningStats stats;
  int stopping_time = -1;
  auto step_fn = config.draw_step();
  auto next_width_time = config.evaluate_width_times.begin();
  for (int t = 1; t <= config.num_steps; t++) {
    double step = step_fn(rng);
    stats.add(step);

    if (*next_width_time == t) {
      walk_widths.push_back(get_width(*config.strategy, stats, config.support));
      ++next_width_time;
    }

    if (stopping_time == -1
        && config.strategy->can_reject(0, stats, config.support)) {
      stopping_time = t;
    }
  }
  return stopping_time;
}

void simulate_many_walks(const int thread_id, const SimulationConfig* config,
                         const bool* const stop_all_threads,
                         std::vector<int>* stopping_times,
                         std::vector<std::vector<double>>* widths) {
  std::default_random_engine rng;
  const int walks_per_thread = config->num_walks / config->num_threads;
  const int start_index = thread_id * walks_per_thread;

  for (int walk_index = start_index;
       walk_index < start_index + walks_per_thread;
       walk_index++) {
    rng.seed(293887823 + walk_index);
    if (*stop_all_threads) break;

    (*widths)[walk_index].reserve(config->evaluate_width_times.size());
    int stopping_time = simulate_one_walk(*config, rng, (*widths)[walk_index]);
    (*stopping_times)[walk_index] += stopping_time;

    if (thread_id == 0 && ((walk_index + 1) % 10) == 0) {
      fprintf(stderr, "%d\r", (walk_index + 1) * config->num_threads);
      fflush(stderr);
    }
  }
}

List run_simulation(const SimulationConfig& config) {
  const int actual_num_walks =
      (config.num_walks / config.num_threads) * config.num_threads;
  bool stop_all_threads = false;
  std::future<void> futures[config.num_threads];
  std::vector<int> stopping_times(actual_num_walks);
  std::vector<std::vector<double>> widths(actual_num_walks);
  auto start = std::chrono::steady_clock::now();

  for (int thread_id = 0; thread_id < config.num_threads; thread_id++) {
    futures[thread_id] = std::async(
        std::launch::async, simulate_many_walks,
        thread_id, &config, &stop_all_threads, &stopping_times, &widths);
  }

  int num_finished = 0;
  while (!stop_all_threads && num_finished < config.num_threads) {
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
    try {
      Rcpp::checkUserInterrupt();
    } catch (Rcpp::internal::InterruptedException) {
      fprintf(stderr, "Caught interrupt, stopping threads\n");
      stop_all_threads = true;
      for (auto& future : futures) {
        future.wait();
      }
      throw;
    }
    num_finished = 0;
    for (auto& future : futures) {
      auto status = future.wait_for(std::chrono::milliseconds(0));
      if (status == std::future_status::ready) ++num_finished;
    }
  }
  for (auto& future : futures) future.get(); // check for exceptions

  int num_crossed = std::count_if(
      stopping_times.begin(),
      stopping_times.end(),
      [](int i){return i > 0;});
  auto end = std::chrono::steady_clock::now();
  int elapsed_ms =
      std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
      .count();
  fprintf(stderr, "%d / %d walks crossed, time elapsed=%.3fs\n",
          num_crossed, actual_num_walks, elapsed_ms / 1000.0);

  NumericMatrix width_matrix(
      actual_num_walks,
      config.evaluate_width_times.size()
                             );
  for (int walk_index = 0; walk_index < actual_num_walks; walk_index++) {
    std::vector<double> walk_widths = widths[walk_index];
    for (int step = 0; step < config.evaluate_width_times.size(); step++) {
      width_matrix(walk_index, step) =
          walk_widths.size() > step ? walk_widths[step] : NA_REAL;
    }
  }
  return List::create(
      _["num_walks"] = actual_num_walks,
      _["num_steps"] = config.num_steps,
      _["stopping_times"] = stopping_times,
      _["evaluate_width_times"] = config.evaluate_width_times,
      _["widths"] = width_matrix);
}

RCPP_MODULE(simple_sim_cpp) {
  class_<SimulationConfig>("SimulationConfig")
      .constructor<int, int, int, std::vector<double>>()
      .method("discrete_steps", &SimulationConfig::discrete_steps)
      .method("normal_mixture", &SimulationConfig::normal_mixture)
      .method("beta_binomial_mixture", &SimulationConfig::beta_binomial_mixture)
      .method("empirical_bernstein", &SimulationConfig::empirical_bernstein)
      .method("naive_self_normalized", &SimulationConfig::naive_self_normalized)
      .method("pointwise_bernoulli", &SimulationConfig::pointwise_bernoulli)
      .method("z_test", &SimulationConfig::z_test)
      .method("pointwise_hoeffding", &SimulationConfig::pointwise_hoeffding)
      .method("linear_bound", &SimulationConfig::linear_bound);

  function("run_simulation", &run_simulation);
}


#define GLM_FORCE_SWIZZLE
#include "comm.h"
#include "Pool.h"
#include "ai/physics.h"
#include "demo/demo.h"
#include "io.h"
#include <algorithm>
#include <fstream>
#include <glm/glm.hpp>
#include <sstream>
#define GLM_ENABLE_EXPERIMENTAL
#include <Eigen/Dense>
#include <glm/gtx/norm.hpp>

namespace comm {
/*static*/ Entity* select_nearest(Entity& i);
/*static*/ Entity* select_max_volume(Entity& i);
/*static*/ Entity* select_median_volume(Entity& i);
// x,y
//      = default
// x,y + mag
// #define NORM_REL
// y + mag
#define COSINE_REL // NOT AT THE SAME TIME AS NORM_REL
// 1 / p_mag
#define PROX // with either rel, not by itself
// g / (1 + |g|) (but g is always >0 so g/(1 + g))
#define SOFTSIGN_GOAL

// one (c+2)x(c+(5 or 7)) split into multiple for easy multiplication w/ glm
//     +
//   c c v v v v v
// + c c v v v v v
//   f f
//   f f         0
//
//   M_c M_v
//   M_f   0
//
//   M_c = cxc
//   M_v = cx2; (cx1;) cx2; (cx1;) cx1
//   M_f = 2xc

#ifdef NORM_REL
const unsigned N_INPUT = NCOMM + 7;
#else
const unsigned N_INPUT = NCOMM + 5;
#endif

Eigen::Matrix<float, NCOMM, N_INPUT> M_cv;

Eigen::Matrix<float, NCOMM, NCOMM> M_c; // c input/output

#if defined(NORM_REL) || !defined(COSINE_REL)
vecc M_vx; // relative velocity input
#endif
vecc M_vy; // relative velocity input
#ifdef COSINE_REL
vecc M_s; // magnitude of the relative velocity
#endif
#if defined(NORM_REL) || !defined(COSINE_REL)
vecc M_px; // relative position input
#endif
vecc M_py; // relative position input
#ifdef COSINE_REL
vecc M_d; // distance of the relative position of neighbor
#endif
vecc M_g; // goal distance input

Eigen::Matrix<float, 2, NCOMM> M_f;

std::string data_dir;

void init(std::string data_dir_) {
    comm::data_dir = data_dir_;
#ifndef NO_COMM
    std::string config_file = comm::data_dir + "/comms.config";
    std::string config_str = *read_file(config_file);

    std::stringstream ss(config_str);
    std::string line;

    for (int row = 0; row < NCOMM; ++row) {
        std::getline(ss, line, '\n');
        std::stringstream sl(line);
        std::string item;

        // M_c
        int col = 0;
        for (; col < NCOMM; ++col) {
            std::getline(sl, item, ' ');
            M_c(row, col) = std::stof(item);
        }

#    ifdef NORM_REL
        std::array<vecc*, 7> M_v = {
            &M_vx, &M_vy, &M_s, &M_px, &M_py, &M_d, &M_g};
#    else
        std::array<vecc*, 5> M_v = {&M_vy, &M_s, &M_py, &M_d, &M_g};
#    endif
        // (M_vx;) M_vy; M_s; (M_px;) M_py; M_d; M_g
#    ifdef NORM_REL
        for (col = 0; col < 7; ++col) {
#    else
        for (col = 0; col < 5; ++col) {
#    endif
            std::getline(sl, item, ' ');
            (*M_v[static_cast<size_t>(col)])(row) = std::stof(item);
        }
    }

    // Fill in M_f
    for (int row = 0; row < 2; ++row) {
        std::getline(ss, line, '\n');
        std::stringstream sl(line);
        std::string item;
        for (int col = 0; col < NCOMM; ++col) {
            std::getline(sl, item, ' ');
            M_f(row, col) = std::stof(item);
        }
    }

    // clang-format off
    M_cv << M_c,
#    if defined(NORM_REL)
        M_vx, M_vy, M_s, M_px, M_py, M_d, M_g;
#    elif defined(COSINE_REL)
        M_vy, M_s, M_py, M_d, M_g;
#    else
        M_vx, M_vy, M_px, M_py, M_g;
#    endif
#endif
    // clang-format on
} // namespace comm

void terminate() {
    std::ofstream results;
    results.open(comm::data_dir + "/comms.result");
    float group_time = demo::run_avg_time + demo::run_std_time * 3;
    results << physics::avg_velocity << "\n" << group_time << "\n";
    results.close();
}

static Eigen::Matrix<float, N_INPUT, 1> x(
    Agent& ai,
    CommComp& ci,
    Dynamics& di,
    CommComp& cj,
    glm::vec2 vj,
    glm::vec2 pj
) {
    // w.r.t. local coordinate frame
    glm::mat2x2 frame = glm::transpose(glm::mat2x2(ci.right(), ci.facing));

    // this assumes v_forward/v_right are normalized
    glm::vec2 diff_v = vj - glm::vec2(di.vel.x, di.vel.z);
    glm::vec2 rel_v = frame * diff_v;
#if defined(COSINE_REL) || defined(NORM_REL)
    float mag_rv = glm::length(rel_v);
    if (mag_rv > 0) {
        rel_v /= mag_rv;
    }
#endif

    glm::vec2 diff_p = pj - glm::vec2(di.pos.x, di.pos.z);
    glm::vec2 rel_p = frame * diff_p;
#if defined(COSINE_REL) || defined(NORM_REL)
    float mag_rp = glm::length(rel_p);
    if (mag_rp > 0) {
        rel_p /= mag_rp;
#    ifdef PROX
        mag_rp = 1 / mag_rp;
#    endif
    }
#endif

    float g = ai.goal_dist;
#ifdef SOFTSIGN_GOAL
    g /= 1 + g;
#endif

    Eigen::Matrix<float, N_INPUT, 1> x;
    x << cj.c,
#if defined(NORM_REL)
        rel_v, mag_rv, rel_p, mag_rp, g;
#elif defined(COSINE_REL)
        rel_v.y, mag_rv, rel_p.y, mag_rp, g;
#else
        rel_v, rel_p, g;
#endif
    return x;
}

static float volume2_j_to_i(Dynamics& d_j, CommComp& c_j, glm::vec2 p_i) {
    glm::vec2 p_j = glm::vec2(d_j.pos.x, d_j.pos.z);
    glm::vec2 diff = p_j - p_i;
    vecc atten = c_j.c / glm::length2(diff);
    return atten.sum();
}

/*static*/ Entity* select_nearest(Entity& i) {
    Dynamics& d = *POOL.get<Dynamics>(i);
    glm::vec2 p_i = d.pos.xz;
    float max_dist2 = std::numeric_limits<float>::min();
    Entity* closest = nullptr;

    // this is *really* inefficient, but oh well.
    POOL.for_<CommComp>([&](CommComp&, Entity& j) {
        if (i.id == j.id)
            return;

        Dynamics& d_j = *POOL.get<Dynamics>(j);
        float dist2 = glm::length2(d_j.pos.xz - p_i);
        if (dist2 > max_dist2) {
            max_dist2 = dist2;
            closest = &j;
        }
    });
    return closest;
}

/*static*/ Entity* select_max_volume(Entity& i) {
    Dynamics& d = *POOL.get<Dynamics>(i);
    glm::vec2 p_i = glm::vec2(d.pos.x, d.pos.z);
    float max_volume2 = std::numeric_limits<float>::min();
    Entity* loudest = nullptr;

    // this is *really* inefficient, but oh well.
    POOL.for_<CommComp>([&](CommComp& c_j, Entity& j) {
        if (i.id == j.id)
            return;

        Dynamics& d_j = *POOL.get<Dynamics>(j);
        float volume2 = volume2_j_to_i(d_j, c_j, p_i);
        if (volume2 > max_volume2) {
            max_volume2 = volume2;
            loudest = &j;
        }
    });
    return loudest;
}

/*static*/ Entity* select_median_volume(Entity& i) {
    Dynamics& d = *POOL.get<Dynamics>(i);
    glm::vec2 p_i = glm::vec2(d.pos.x, d.pos.z);

    // this is *really* inefficient, but oh well.
    std::vector<std::pair<float, Entity*>> v;
    POOL.for_<CommComp>([&](CommComp& c_j, Entity& j) {
        if (i.id == j.id)
            return;

        Dynamics& d_j = *POOL.get<Dynamics>(j);
        float volume2 = volume2_j_to_i(d_j, c_j, p_i);
        v.push_back(std::make_pair(volume2, &j));
    });

    long mid = static_cast<long>(v.size() / 2ul);
    std::nth_element(v.begin(), v.begin() + mid, v.end());
    if (v.size() > 1) {
        return v[v.size() / 2].second;
    } else {
        return nullptr;
    }
}

void run() {
    POOL.for_<CommComp>([&](CommComp& c_i, Entity& i) {
        // if, for some reason, a nearest neighbor is not found, treat them as
        // silent, not moving, and not distant.
        glm::vec2 p_j{0};
        glm::vec2 v_j{0};
        CommComp* c_j = nullptr;
        Entity* j = select_nearest(i);
        if (j) {
            p_j = POOL.get<Dynamics>(*j)->pos;
            v_j = POOL.get<Dynamics>(*j)->vel;
            c_j = POOL.get<CommComp>(*j);
        }

        Agent& a_i = *POOL.get<Agent>(i);
        Dynamics& d_i = *POOL.get<Dynamics>(i);

        auto x_i = x(a_i, c_i, d_i, *c_j, v_j, p_j);
        c_i.buf_in(M_cv * x_i, M_f * c_j->c);
    });

    POOL.for_<CommComp>([&](CommComp& c, Entity& e_c) {
        Dynamics& d = *POOL.get<Dynamics>(e_c);
        glm::vec2 diff = c.right() * c.f_buf.x + c.facing * c.f_buf.y;

        Agent* a = POOL.get<Agent>(e_c);
        if (a && !a->done()) {
            d.force += glm::vec3(diff.x, 0.f, diff.y);
        }

        if ( //
            glm::length(d.vel) > 0 //
            && glm::length(a->local_goal - a->start) > 0
        ) {
            c.facing = glm::normalize(a->local_goal - a->start);
        }
        c.swap();
    });
}

} // namespace comm


// Copyright (c) 2014 bushido
// Copyright (c) 2014 The Vertcoin developers
// Copyright (c) 2014 https://github.com/spesmilo/sx
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "stealth.h"
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/random_device.hpp>

hash_digest bitcoin_hash(const data_chunk& chunk)
{
    hash_digest first_hash;
    SHA256(chunk.data(), chunk.size(), first_hash.data());

    hash_digest second_hash;
    SHA256(first_hash.data(), first_hash.size(), second_hash.data());

    // The hash is in the reverse of the expected order.
    std::reverse(second_hash.begin(), second_hash.end());
    return second_hash;
}

uint32_t bitcoin_checksum(const data_chunk& chunk)
{
    hash_digest hash = bitcoin_hash(chunk);
    return from_little_endian<uint32_t>(hash.rbegin());
}

void append_checksum(data_chunk& data)
{
    uint32_t checksum = bitcoin_checksum(data);
    extend_data(data, to_little_endian(checksum));
}

std::string stealth_address::encoded() const
{
    data_chunk raw_addr;
    raw_addr.push_back(stealth_version_byte);
    raw_addr.push_back(options);
    extend_data(raw_addr, scan_pubkey);
    uint8_t number_spend_pubkeys = static_cast<uint8_t>(spend_pubkeys.size());
    raw_addr.push_back(number_spend_pubkeys);
    for (const ec_point& pubkey: spend_pubkeys)
        extend_data(raw_addr, pubkey);
    raw_addr.push_back(number_signatures);
    //assert_msg(prefix.number_bits == 0, "Not yet implemented!");
    raw_addr.push_back(0);
    append_checksum(raw_addr);
    return EncodeBase58(raw_addr);
}

bool verify_checksum(const data_chunk& data)
{
    data_chunk body(data.begin(), data.end() - 4);
    auto checksum = from_little_endian<uint32_t>(data.end() - 4);
    return bitcoin_checksum(body) == checksum;
}

bool stealth_address::set_encoded(const std::string& encoded_address)
{
    data_chunk raw_addr;
    DecodeBase58(encoded_address, raw_addr);
    if (!verify_checksum(raw_addr))
        return false;
    assert(raw_addr.size() >= 4);
    auto checksum_begin = raw_addr.end() - 4;
    // Delete checksum bytes.
    raw_addr.erase(checksum_begin, raw_addr.end());
    // https://wiki.unsystem.net/index.php/DarkWallet/Stealth#Address_format
    // [version] [options] [scan_key] [N] ... [Nsigs] [prefix_length] ...
    size_t estimated_data_size = 1 + 1 + 33 + 1 + 1 + 1;
    assert(raw_addr.size() >= estimated_data_size);
    auto iter = raw_addr.begin();
    uint8_t version = *iter;
    if (version != stealth_version_byte)
        return false;
    ++iter;
    options = *iter;
    ++iter;
    auto scan_key_begin = iter;
    iter += 33;
    scan_pubkey = data_chunk(scan_key_begin, iter);
    uint8_t number_spend_pubkeys = *iter;
    ++iter;
    estimated_data_size += number_spend_pubkeys * 33;
    assert(raw_addr.size() >= estimated_data_size);
    for (size_t i = 0; i < number_spend_pubkeys; ++i)
    {
        auto spend_key_begin = iter;
        iter += 33;
        spend_pubkeys.emplace_back(data_chunk(spend_key_begin, iter));
    }
    number_signatures = *iter;
    ++iter;
    prefix.number_bits = *iter;
    ++iter;
    size_t number_bitfield_bytes = 0;
    if (prefix.number_bits > 0)
        number_bitfield_bytes = prefix.number_bits / 8 + 1;
    estimated_data_size += number_bitfield_bytes;
    assert(raw_addr.size() >= estimated_data_size);
    // Unimplemented currently!
    assert(number_bitfield_bytes == 0);
    return true;
}

ec_secret generate_random_secret()
{
    using namespace boost::random;
    random_device rd;
    mt19937 generator(rd());
    uniform_int_distribution<uint8_t> dist(0, std::numeric_limits<uint8_t>::max());
    ec_secret secret;
    for (uint8_t& byte: secret)
        byte = dist(generator);
    return secret;
}

bool ec_multiply(ec_point& a, const ec_secret& b)
{
    init.init();
    return secp256k1_ec_pubkey_tweak_mul(init.getContext(), a.data(), a.size(), b.data());
}

hash_digest sha256_hash(const data_chunk& chunk)
{
    hash_digest hash;
    SHA256(chunk.data(), chunk.size(), hash.data());
    return hash;
}

ec_secret shared_secret(const ec_secret& secret, ec_point point)
{
    // diffie hellman stage
    bool success = ec_multiply(point, secret);
    assert(success);

    // start the second stage
    return sha256_hash(point);
}

bool ec_tweak_add(ec_point& a, const ec_secret& b)
{
    init.init();
    return secp256k1_ec_pubkey_tweak_add(init.getContext(), a.data(), a.size(), b.data());
}

ec_point secret_to_public_key(const ec_secret& secret, bool compressed)
{
    init.init();
    size_t size = ec_uncompressed_size;
    if (compressed)
        size = ec_compressed_size;

    ec_point out(size);
    int out_size;
    if (!secp256k1_ec_pubkey_create(init.getContext(), out.data(), &out_size, secret.data(),
            compressed))
        return ec_point();
    assert(size == static_cast<size_t>(out_size));
    return out;
}

ec_point initiate_stealth(
    const ec_secret& ephem_secret, const ec_point& scan_pubkey,
    const ec_point& spend_pubkey)
{
    ec_point final = spend_pubkey;

    // Generate shared secret
    ec_secret shared = shared_secret(ephem_secret, scan_pubkey);

    // Now generate address
    bool success = ec_tweak_add(final, shared);
    assert(success);
    return final;
}

short_hash bitcoin_short_hash(const data_chunk& chunk)
{
    hash_digest sha_hash;
    SHA256(chunk.data(), chunk.size(), sha_hash.data());

    short_hash ripemd_hash;
    RIPEMD160(sha_hash.data(), sha_hash.size(), ripemd_hash.data());

    return ripemd_hash;
}

void set_public_key(payment_address& address, const data_chunk& public_key)
{
    address.set(fTestNet ? CBitcoinAddress::PUBKEY_ADDRESS_TEST : CBitcoinAddress::PUBKEY_ADDRESS,
        bitcoin_short_hash(public_key));
}

payment_address::payment_address() : version_(invalid_version), hash_(null_short_hash)
{
}

payment_address::payment_address(uint8_t version, const short_hash& hash)
{
    payment_address();
    set(version, hash);
}

payment_address::payment_address(const std::string& encoded_address)
{
    payment_address();
    set_encoded(encoded_address);
}

void payment_address::set(uint8_t version, const short_hash& hash)
{
    version_ = version;
    hash_ = hash;
}

bool is_base58(const char c)
{
    auto last = std::end(base58_chars) - 1;
    // This works because the base58 characters happen to be in sorted order
    return std::binary_search(base58_chars, last, c);
}

bool is_base58(const std::string& text)
{
    return std::all_of(text.begin(), text.end(),
        [](const char c){ return is_base58(c); });
}

bool payment_address::set_encoded(const std::string& encoded_address)
{
    if (!is_base58(encoded_address))
        return false;
    data_chunk decoded_address;
    DecodeBase58(encoded_address, decoded_address);
    // version + 20 bytes short hash + 4 bytes checksum
    if (decoded_address.size() != 25)
        return false;
    if (!verify_checksum(decoded_address))
        return false;

    version_ = decoded_address[0];
    std::copy_n(decoded_address.begin() + 1, hash_.size(), hash_.begin());
    return true;
}

std::string payment_address::encoded() const
{
    data_chunk unencoded_address;
    unencoded_address.reserve(25);
    // Type, Hash, Checksum doth make thy address
    unencoded_address.push_back(version_);
    extend_data(unencoded_address, hash_);
    append_checksum(unencoded_address);
    assert(unencoded_address.size() == 25);
    return EncodeBase58(unencoded_address);
}

ec_point uncover_stealth(
    const ec_point& ephem_pubkey, const ec_secret& scan_secret,
    const ec_point& spend_pubkey)
{
    ec_point final = spend_pubkey;
    ec_secret shared = shared_secret(scan_secret, ephem_pubkey);
    bool success = ec_tweak_add(final, shared);
    assert(success);
    return final;
}

bool ec_add(ec_secret& a, const ec_secret& b)
{
    init.init();
    return secp256k1_ec_privkey_tweak_add(init.getContext(), a.data(), b.data());
}

ec_secret uncover_stealth_secret(
    const ec_point& ephem_pubkey, const ec_secret& scan_secret,
    const ec_secret& spend_secret)
{
    ec_secret final = spend_secret;
    ec_secret shared = shared_secret(scan_secret, ephem_pubkey);
    bool success = ec_add(final, shared);
    assert(success);
    return final;
}

std::string secret_to_wif(const ec_secret& secret, bool compressed)
{
    data_chunk data;
    data.reserve(1 + hash_size + 1 + 4);

    data.push_back(fTestNet ? CBitcoinSecret::PRIVKEY_ADDRESS_TEST : CBitcoinSecret::PRIVKEY_ADDRESS);
    extend_data(data, secret);
    if (compressed)
        data.push_back(0x01);

    append_checksum(data);
    return EncodeBase58(data);
}

data_chunk decode_hex(std::string hex)
{
    // Trim the fat.
    boost::algorithm::trim(hex);
    data_chunk result(hex.size() / 2);
    for (size_t i = 0; i + 1 < hex.size(); i += 2)
    {
        assert(hex.size() - i >= 2);
        auto byte_begin = hex.begin() + i;
        auto byte_end = hex.begin() + i + 2;
        // Perform conversion.
        int val = -1;
        std::stringstream converter;
        converter << std::hex << std::string(byte_begin, byte_end);
        converter >> val;
        if (val == -1)
            return data_chunk();
        assert(val <= 0xff);
        // Set byte.
        result[i / 2] = val;
    }
    return result;
}


/***************************************************************************
 *   Copyright (C) 2006 by Reed A. Cartwright                              *
 *   reed@scit.us                                                          *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#	include "config.h"
#endif

#include <boost/math/special_functions/zeta.hpp>

#include <algorithm>
#include <cfloat>
#include <iostream>

#include "hist_k2p.h"

#include "series.h"

inline double pow(double p, size_t x){return std::pow(p, (int)x);}
inline double log(size_t x) {return log((double)x);}
inline double zeta(double z) { return boost::math::zeta<double>(z); }
inline double sq(double s) { return s*s; }
inline double csch(double x) { return 1.0/sinh(x); }

using namespace std;

extern const int g_pupy[5];
extern const int g_atgc[5];

/***************************************************************************
 * class hist_k2p_zeta                                                     *
 ***************************************************************************/

void hist_k2p_zeta::preallocate(size_t maxa, size_t maxd)
{
	sz_height = maxa+1;
	sz_width = maxd+1;

	size_t sz_max = std::max(sz_height, sz_width);
	p_indel_size.resize(sz_max, 0.0);

	ll_nucs.resize(get_seqs().size());

	vector<double>::iterator llit = ll_nucs.begin();

	for(seq_pair_vector::const_iterator cit = get_seqs().begin();
		cit != get_seqs().end(); ++cit)
	{
		double ncount[nSize] = {0.0,0.0,0.0,0.0,0.0};
		for(sequence::const_iterator nit = cit->first.begin();
		    nit != cit->first.end(); ++nit)
			++ncount[(int)*nit];
		for(sequence::const_iterator nit = cit->second.begin();
		    nit != cit->second.end(); ++nit)
			++ncount[(int)*nit];
		*llit = log(0.25)*(ncount[nA]+ncount[nC]+ncount[nG]+ncount[nT])
			+ log(1.0)*(ncount[nN]);
		++llit;
	}
}

void hist_k2p_zeta::expectation_setup(const params_type &params)
{
	p_ts = 0.25-0.5*exp(-params[pT]*(2.0*params[pK]+1.0)/(params[pK]+1.0))
			+ 0.25*exp(-2.0*params[pT]/(params[pK]+1.0));
	p_tv = 0.5-0.5*exp(-2.0*params[pT]/(params[pK]+1.0));
	p_match = 1.0-p_ts-p_tv;

	p_end = 1.0/(params[pA]+1.0);
	p_2h =  (1.0-p_end)*exp(-2.0*params[pR]*params[pT]);
	p_2g =  (1.0-p_end)*0.5*(1.0-exp(-2.0*params[pR]*params[pT]));

	for(size_t i=0;i<nN;++i)
	{
		p_substitution[i][i] = 4.0*p_match*p_2h;
		for(size_t j=i+1;j<nN;++j)
		{
			p_substitution[j][i] = p_substitution[i][j] = p_2h*
				((g_pupy[i] == g_pupy[j]) ? 4.0*p_ts : 2.0*p_tv);
		}
		p_substitution[nN][i] = p_substitution[i][nN] = p_2h;
	}
	p_substitution[nN][nN] = p_2h;

	double z = params[pZ];
	double Z = p_2g/zeta(z);
	for(size_t u = 1; u < p_indel_size.size(); ++u)
		p_indel_size[u] = pow((double)u, -z)*Z;
}

void hist_k2p_zeta::expectation(ex_type &ex, ex_type &ex2, const sequence &seq_a, const sequence &seq_d, size_t index) const
{
	for(size_t u = 0; u < ex.size(); ++u)
	{
		double d, d2;
		expectation(d, d2, u, seq_a, seq_d, index);
		ex[u] = d;
		ex2[u] = d2;
	}
}

void hist_k2p_zeta::expectation(double &ex, double &ex2, size_t gap, const sequence &seq_a, const sequence &seq_d, size_t index) const
{
	size_t sz_anc = seq_a.size();
	size_t sz_dec = seq_d.size();

	xx_table table_w(sz_anc+1,sz_dec+1,ublas::zero_vector<double>(xSize));

	table_w(0,0)[xW] = prob_scale;
	double dt,dp;

	for(size_t d = 1; d <= sz_dec; ++d)
	{
		xx_type &W = table_w(0,d);
		for(size_t k = d; k > 0; --k)
		{
			dp = p_indel_size[k];
			xx_type &WK = table_w(0,d-k);
			noalias(W) += WK*dp;
			if(k == gap || gap == 0)
			{
				W[xX] += WK[xW]*dp;
				W[xXX] += 2.0*WK[xX]*dp + WK[xW]*dp;
			}
		}
	}

	for(size_t a = 1; a <= sz_anc; ++a)
	{
		xx_type &W = table_w(a,0);
		for(size_t k = a; k > 0; --k)
		{
			dp = p_indel_size[k];
			xx_type &WK = table_w(a-k,0);
			noalias(W) += WK*dp;
			if(k == gap || gap == 0)
			{
				W[xX] += WK[xW]*dp;
				W[xXX] += 2.0*WK[xX]*dp + WK[xW]*dp;
			}
		}
	}

	int nuc_a, nuc_d;

	for(size_t a=1;a<=sz_anc;++a)
	{
		for(size_t d=1;d<= sz_dec;++d)
		{
			nuc_a = seq_a[a-1];
			nuc_d = seq_d[d-1];

			xx_type &W = table_w(a,d);
			noalias(W) = table_w(a-1,d-1)*p_substitution[nuc_a][nuc_d];

			for(size_t k = d; k > 0; --k)
			{
				dp = p_indel_size[k];
				xx_type &WK = table_w(a,d-k);
				noalias(W) += WK*dp;
				if(k == gap || gap == 0)
				{
					W[xX] += WK[xW]*dp;
					W[xXX] += 2.0*WK[xX]*dp + WK[xW]*dp;
				}
			}
			for(size_t k = a; k > 0; --k)
			{
				dp = p_indel_size[k];
				xx_type &WK = table_w(a-k,d);
				noalias(W) += WK*dp;
				if(k == gap || gap == 0)
				{
					W[xX] += WK[xW]*dp;
					W[xXX] += 2.0*WK[xX]*dp + WK[xW]*dp;
				}
			}
		}
	}

	xx_type xx = table_w(sz_anc,sz_dec);
	double w = xx[xW];
	xx /= w;
	ex = xx[xX];
	ex2 = xx[xXX];
}

std::string hist_k2p_zeta::state() const
{
	ex_type mean, var;
	mean.clear();
	var.clear();

	for(size_t u = 0; u<exes.size();++u)
	{

		var += ex2s[u] + 2.0*element_prod(mean, exes[u]);
		mean += exes[u];
	}
	noalias(var) -= element_prod(mean, mean);

	ostringstream ostr;
	ostr << std::setprecision(DBL_DIG);
	ostr << "Size\tMean\tVariance" << endl;
	for(size_t u = 0; u<mean.size();++u)
	{
		ostr << u << "\t" << mean[u] << "\t" << var[u] << endl;
	}
	return ostr.str();
}

/***************************************************************************
 * class hist_k2p_geo                                                      *
 ***************************************************************************/

void hist_k2p_geo::preallocate(size_t maxa, size_t maxd)
{
	sz_height = maxa+1;
	sz_width = maxd+1;

	size_t sz_max = std::max(sz_height, sz_width);
	p_indel_size.resize(sz_max, 0.0);
	cache_size.resize(sz_max, 0.0);
	for(size_t u=1;u<sz_max;++u)
		cache_size[u] = double(u);

	ll_nucs.resize(get_seqs().size());

	vector<double>::iterator llit = ll_nucs.begin();

	for(seq_pair_vector::const_iterator cit = get_seqs().begin();
		cit != get_seqs().end(); ++cit)
	{
		double ncount[nSize] = {0.0,0.0,0.0,0.0,0.0};
		for(sequence::const_iterator nit = cit->first.begin();
		    nit != cit->first.end(); ++nit)
			++ncount[(int)*nit];
		for(sequence::const_iterator nit = cit->second.begin();
		    nit != cit->second.end(); ++nit)
			++ncount[(int)*nit];
		*llit = log(0.25)*(ncount[nA]+ncount[nC]+ncount[nG]+ncount[nT])
			+ log(1.0)*(ncount[nN]);
		++llit;
	}
}

void hist_k2p_geo::expectation_setup(const params_type &params)
{
	p_ts = 0.25-0.5*exp(-params[pT]*(2.0*params[pK]+1.0)/(params[pK]+1.0))
			+ 0.25*exp(-2.0*params[pT]/(params[pK]+1.0));
	p_tv = 0.5-0.5*exp(-2.0*params[pT]/(params[pK]+1.0));
	p_match = 1.0-p_ts-p_tv;

	p_end = 1.0/(params[pA]+1.0);
	p_2h =  (1.0-p_end)*exp(-2.0*params[pR]*params[pT]);
	p_2g =  (1.0-p_end)*0.5*(1.0-exp(-2.0*params[pR]*params[pT]));

	for(size_t i=0;i<nN;++i)
	{
		p_substitution[i][i] = 4.0*p_match*p_2h;
		for(size_t j=i+1;j<nN;++j)
		{
			p_substitution[j][i] = p_substitution[i][j] = 4.0*p_2h*
				((g_pupy[i] == g_pupy[j]) ? p_ts : 0.5*p_tv);
		}
		p_substitution[nN][i] = p_substitution[i][nN] = 4.0*p_2h;
	}
	p_substitution[nN][nN] = 16.0*p_2h;

	double qq = 1.0/params[pQ];
	double dp = 1.0/(1.0-qq);
	for(size_t u = 1; u < p_indel_size.size(); ++u)
	{
		dp *= 1.0-qq;
		p_indel_size[u] = p_2g*qq*dp;
	}
	p_open = qq*p_2g;
	p_extend = (1.0-qq);
}

void hist_k2p_geo::expectation(ex_type &ex, ex_type &ex2, const sequence &seq_a, const sequence &seq_d, size_t index) const
{
	for(size_t u = 0; u < ex.size(); ++u)
	{
		double d, d2;
		expectation(d, d2, u, seq_a, seq_d, index);
		ex[u] = d;
		ex2[u] = d2;
	}
}

void hist_k2p_geo::expectation(double &ex, double &ex2, size_t gap, const sequence &seq_a, const sequence &seq_d, size_t index) const
{
	size_t sz_anc = seq_a.size();
	size_t sz_dec = seq_d.size();

	double p_gap = p_open*pow(p_extend, gap);

	xx_table table_w(sz_anc+1,sz_dec+1,ublas::zero_vector<double>(xSize));

	table_w(0,0)[xW] = prob_scale;

	xx_type row_cache;
	vector<xx_type> col_cache(sz_dec+1, ublas::zero_vector<double>(xSize));
	row_cache.clear();
	for(size_t d = 1; d <= sz_dec; ++d)
	{
		xx_type &W = table_w(0,d);

		row_cache *= p_extend;
		noalias(row_cache) += table_w(0,d-1)*p_open;
		if(gap == 0)
		{
			xx_type &WK = table_w(0,d-1);
			row_cache[xX] += WK[xW]*p_open;
			row_cache[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_open;
		}
		else if(d >= gap)
		{
			xx_type &WK = table_w(0,d-gap);
			W[xX] += WK[xW]*p_gap;
			W[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_gap;
		}
		noalias(W) += row_cache;
	}

	row_cache.clear();
	for(size_t a = 1; a <= sz_anc; ++a)
	{
		xx_type &W = table_w(a,0);

		row_cache *= p_extend;
		noalias(row_cache) += table_w(a-1,0)*p_open;
		if(gap == 0)
		{
			xx_type &WK = table_w(a-1,0);
			row_cache[xX] += WK[xW]*p_open;
			row_cache[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_open;
		}
		else if(a >= gap)
		{
			xx_type &WK = table_w(a-gap,0);
			W[xX] += WK[xW]*p_gap;
			W[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_gap;
		}
		noalias(W) += row_cache;
	}

	int nuc_a, nuc_d;

	for(size_t a=1;a<=sz_anc;++a)
	{
		row_cache.clear();
		nuc_a = seq_a[a-1];
		for(size_t d=1;d<= sz_dec;++d)
		{
			nuc_d = seq_d[d-1];

			xx_type &W = table_w(a,d);
			noalias(W) = table_w(a-1,d-1)*p_substitution[nuc_a][nuc_d];

			row_cache *= p_extend;
			noalias(row_cache) += table_w(a,d-1)*p_open;

			if(gap == 0)
			{
				xx_type &WK = table_w(a,d-1);
				row_cache[xX] += WK[xW]*p_open;
				row_cache[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_open;
			}
			else if(d >= gap)
			{
				xx_type &WK = table_w(a,d-gap);
				W[xX] += WK[xW]*p_gap;
				W[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_gap;
			}
			noalias(W) += row_cache;

			col_cache[d] *= p_extend;
			noalias(col_cache[d]) += table_w(a-1,d)*p_open;

			if(gap == 0)
			{
				xx_type &WK = table_w(a-1,d);
				col_cache[d][xX] += WK[xW]*p_open;
				col_cache[d][xXX] += (WK[xX]+WK[xX]+WK[xW])*p_open;
			}
			else if(a >= gap)
			{
				xx_type &WK = table_w(a-gap,d);
				W[xX] += WK[xW]*p_gap;
				W[xXX] += (WK[xX]+WK[xX]+WK[xW])*p_gap;
			}
			noalias(W) += col_cache[d];

		}
	}
	xx_type xx = table_w(sz_anc,sz_dec);
	double w = xx[xW];
	xx /= w;
	ex = xx[xX];
	ex2 = xx[xXX];
	cout << gap << " " << ex << " " << ex2 << endl;
}

std::string hist_k2p_geo::state() const
{
	ex_type mean, var;
	mean.clear();
	var.clear();

	for(size_t u = 0; u<exes.size();++u)
	{
		var += ex2s[u] + 2.0*element_prod(mean,exes[u]);
		mean += exes[u];
	}
	noalias(var) -= element_prod(mean,mean);

	ostringstream ostr;
	ostr << std::setprecision(DBL_DIG);
	ostr << "Size\tExpected\tVariance" << endl;
	for(size_t u = 0; u<mean.size();++u)
	{
		ostr << u << "\t" << mean[u] << "\t" << var[u] << endl;
	}
	return ostr.str();
}