// Copyright © 2020-2021 CJM Screws, LLC
// 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 OR COPYRIGHT HOLDERS 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.
//
// CJM Screws, LLC is a Maryland Limited Liability Company.
// No copyright claimed to unmodified original work of others.
// The original, unmodified work of others, to the extent included in this library,
// is licensed to you under the same terms under which it was licensed to CJM Screws, LLC.
// For information about copyright and licensing of the original work of others,
// see Notices file in cjm/ folder.
//
// This library is modeled on the abseil C++ numerics library.  MOST OF ABSEIL'S CODE HAS BEEN MODIFIED SIGNIFICANTLY BY THIS LIBRARY.
// Some verbatim reproduction may remain in places.  The following notice was found in abseil's uint128 source code and is reproduced here in
// accordance with the terms of the Apache 2.0 license, under which Abseil's code was licensed to CJM Screws, LLC:
#ifndef CJM_BOOST_UINT_COMPARE_TEST_HPP_
#define CJM_BOOST_UINT_COMPARE_TEST_HPP_
#include "fixed_uint.hpp"
#include "testing.hpp"
#include <boost/multiprecision/cpp_int.hpp>
#include <utility>
#include <random>
#include <memory>
#include <vector>
#include <functional>
#include <optional>

namespace cjm::testing
{

	using ctrtl_u128 = boost::multiprecision::uint128_t;
	using test_u128 = numerics::uint128;
	using ctrtl_u256 = boost::multiprecision::uint256_t;
	using test_u256 = numerics::uint256_t;
	using byte = std::uint8_t;
	using arr_256_t = test_u256::byte_array;
	using arr_128_t = test_u128::byte_array;
	using test_pair_t = std::pair<ctrtl_u256, test_u256>;
	using test_pair_128_t = std::pair<ctrtl_u128, test_u128>;
	using test_pair_col_t = std::vector<test_pair_t>;
	template<typename NumberType>
	using binary_op_pair_t = std::pair<NumberType, NumberType>;
	using namespace boost::multiprecision::literals;
	using namespace numerics::uint128_literals;
	using namespace numerics::fixed_uint_literals;
	using binary_op_set_uint256_t = std::pair<binary_op_pair_t<ctrtl_u256>, binary_op_pair_t<test_u256>>;
	using binary_op_set_uint256_col_t = std::vector<binary_op_set_uint256_t>;
	enum class BinaryOperationType : unsigned short;
	template<typename TestOperandType, typename ControlOperandType>
	class BinaryOperation;
	class BinaryU256Operation;
	using binary_u256_operation_col_t = std::vector<BinaryU256Operation>;
	enum class ComparisonOperationType : unsigned short;
	template<typename TestOperandType, typename ControlOperandType>
	class ComparisonOperation;
	using ComparisonOperation256 = ComparisonOperation<test_u256, ctrtl_u256>;
	using comparison_op_set_uint256_t = std::vector<ComparisonOperation256>;
	template<typename TestOperandType, typename ControlOperandType>
	class UnaryOperation;
	using UnaryOperation256 = UnaryOperation<test_u256, ctrtl_u256>;
	using unary_op_set_uint256_t = std::vector<UnaryOperation256>;

	inline std::mt19937_64 init_twister()
	{
		std::random_device rd;
		auto seed = rd();
		return std::mt19937_64{ seed };
	}

	inline std::mt19937_64 Random_Engine = init_twister();

	test_pair_t get_pair();

	binary_u256_operation_col_t get_binary_op_u256_collection(size_t elems);

	binary_u256_operation_col_t execute_test(const binary_u256_operation_col_t& testSet);

	BinaryOperationType get_random_op_type();

	binary_op_set_uint256_t get_binary_256_op_set();

	UnaryOperation256 get_random_unary_256_op();

	unary_op_set_uint256_t get_random_unary_256_opset256(size_t numElems);

	BinaryU256Operation get_binary_op_for_u256();

	test_pair_col_t create_random_test_collection(size_t numElems);

	ComparisonOperation256 create_random_comparison_op256();

	comparison_op_set_uint256_t create_random_comparison_opset256(size_t numElems);

	test_pair_t create_random_test_pair();

	test_pair_128_t create_random_128_test_pair();

	ctrtl_u128 to_control(const test_u128& convertMe);

	ctrtl_u256 to_control(const test_u256& convertMe);

	ctrtl_u256 from_little_endian_array(const arr_256_t& source);

	ctrtl_u128 from_little_endian_array(const arr_128_t& source);

	arr_256_t to_little_endian_array(const ctrtl_u256& convertMe);

	arr_128_t to_little_endian_array(const ctrtl_u128& convertMe);

	test_u256 to_test(const ctrtl_u256& convertMe);

	test_u256 random_u256();

	test_u128 to_test(const ctrtl_u128& convertMe);

	arr_256_t random_array();

	arr_128_t random_128_array();

	namespace boost_cjm_uint_comparison
	{
		bool operator==(const ctrtl_u256& lhs, const test_u256& rhs);

		bool operator==(const test_u256& lhs, const ctrtl_u256& rhs);

		bool operator!=(const ctrtl_u256& lhs, const test_u256& rhs);

		bool operator!=(const test_u256& lhs, const ctrtl_u256& rhs);

		bool operator>(const ctrtl_u256& lhs, const test_u256& rhs);

		bool operator>(const test_u256& lhs, const ctrtl_u256& rhs);

		bool operator<(const ctrtl_u256& lhs, const test_u256& rhs);

		bool operator<(const test_u256& lhs, const ctrtl_u256& rhs);

		bool operator>=(const ctrtl_u256& lhs, const test_u256& rhs);

		bool operator>=(const test_u256& lhs, const ctrtl_u256& rhs);

		bool operator<=(const ctrtl_u256& lhs, const test_u256& rhs);

		bool operator<=(const test_u256& lhs, const ctrtl_u256& rhs);

		//////////////////////////////////////////////////////////////////////////
		bool operator==(const ctrtl_u128& lhs, const test_u128& rhs);

		bool operator==(const test_u128& lhs, const ctrtl_u128& rhs);

		bool operator!=(const ctrtl_u128& lhs, const test_u128& rhs);

		bool operator!=(const test_u128& lhs, const ctrtl_u128& rhs);

		bool operator>(const ctrtl_u128& lhs, const test_u128& rhs);

		bool operator>(const test_u128& lhs, const ctrtl_u128& rhs);

		bool operator<(const ctrtl_u128& lhs, const test_u128& rhs);

		bool operator<(const test_u128& lhs, const ctrtl_u128& rhs);

		bool operator>=(const ctrtl_u128& lhs, const test_u128& rhs);

		bool operator>=(const test_u128& lhs, const ctrtl_u128& rhs);

		bool operator<=(const ctrtl_u128& lhs, const test_u128& rhs);

		bool operator<=(const test_u128& lhs, const ctrtl_u128& rhs);

	}

	enum class BinaryOperationType : unsigned short
	{
		Addition = 0,
		Subtraction = 1,
		Multiplication = 2,
		Division = 3,
		Modulus = 4,
		Xor = 5,
		Or = 6,
		And = 7,
		LeftShift = 8,
		RightShift = 9,
	};
	template <typename TestOperandType, typename ControlOperandType>
	class BinaryOperation
	{

	public:
		static_assert(std::numeric_limits<TestOperandType>::is_specialized && std::numeric_limits<ControlOperandType>::is_specialized, "Types must be number-types.");
		static_assert(std::numeric_limits<TestOperandType>::digits == std::numeric_limits<ControlOperandType>::digits, "Both numbers must have same # of digits.");

		static constexpr auto MaxDefinedShift = std::numeric_limits<TestOperandType>::digits -1;

		using control_pair = std::pair<ControlOperandType, ControlOperandType>;
		using test_pair = std::pair<TestOperandType, TestOperandType>;
		using result_pair = std::optional<std::pair<std::optional<TestOperandType>, std::optional<ControlOperandType>>>;

		bool operator() ()
		{
			return is_result_successful();
		}

		BinaryOperationType operation_type() const noexcept { return m_op_type; }

		std::string operation_name() const { return m_op_name; }

		result_pair get_result_pair();

		BinaryOperation(BinaryOperationType opType, const test_pair& testPair, const control_pair& controlPair);
		BinaryOperation() = delete;
		BinaryOperation(const BinaryOperation& other) = default;
		BinaryOperation(BinaryOperation&& other) noexcept = default;
		virtual ~BinaryOperation() = default;
		BinaryOperation& operator=(const BinaryOperation& other) = default;
		BinaryOperation& operator=(BinaryOperation&& other) noexcept = default;

		friend bool operator==(const BinaryOperation& lhs, const BinaryOperation& rhs)
		{
			return lhs.m_op_type == rhs.m_op_type && lhs.m_test_pair == rhs.m_test_pair && lhs.m_control_pair == rhs.m_control_pair;
		}

		friend bool operator!=(const BinaryOperation& lhs, const BinaryOperation& rhs) { return !(lhs == rhs); }
	protected:
		virtual bool is_result_successful()
		{
			bool ret;
			const auto& temp = get_result_pair();
			assert(temp.has_value());
			const auto& actualPair = *temp;
			if (actualPair.first.has_value() != actualPair.second.has_value())
				ret = false;
			else if (actualPair.first.has_value())
			{
				const auto& testRes = *(actualPair.first);
				const auto& controlRes = *(actualPair.second);
				ret = are_equal(testRes, controlRes);
			}
			else
				ret = m_thrown_exception_control.has_value() == m_thrown_exception_test.has_value();
			return ret;
		}

		virtual bool are_equal(const TestOperandType& lhs, const ControlOperandType& rhs);

	private:
		BinaryOperationType m_op_type;
		test_pair m_test_pair;
		control_pair m_control_pair;
		std::string m_op_name;
		std::optional<std::exception> m_thrown_exception_test;
		std::optional<std::exception> m_thrown_exception_control;
		result_pair m_result_pair;
	};

	class BinaryU256Operation final : public BinaryOperation<test_u256, ctrtl_u256>
	{
	public:
		BinaryU256Operation(BinaryOperationType opType, const test_pair& testPair, const control_pair& controlPair)
			: BinaryOperation<test_u256, ctrtl_u256>(opType, testPair, controlPair) {}
	};

	enum class ComparisonOperationType : unsigned short
	{
		Equal = 0,
		NotEqual = 1,
		GreaterThan = 2,
		LessThan = 3,
		GreaterThanOrEqual = 4,
		LessThanOrEqual = 5,
	};

	template<typename TestOperandType, typename ControlOperandType>
	class ComparisonOperation
	{
	public:
		static void release_static_memory() { s_operation_lookup = nullptr; }
		static constexpr size_t NumberOfComparisonOps{ 6 };
		using test_type_t = std::pair<TestOperandType, TestOperandType>;
		using control_type_t = std::pair<ControlOperandType, ControlOperandType>;
		using result_type_t = std::pair<bool, bool>;
		using comparison_op_t = std::function<result_type_t(const test_type_t&, const control_type_t&)>;
		using result_coll_t = std::unordered_map<ComparisonOperationType, result_type_t>;
		using name_lookup_t = std::array<std::string_view, NumberOfComparisonOps>;
		using op_lookup_t = std::unordered_map<ComparisonOperationType, comparison_op_t>;
		using indiv_result = std::pair<bool, std::string>;

		test_type_t get_test_operaands() const { return m_test_pair; }
		control_type_t get_control_operands() const { return m_control_pair; }
		indiv_result operator[](ComparisonOperationType cot) const;
		const result_coll_t& all_results() const
		{
			execute_tests();
			return m_results;
		}
		bool all_pass() const
		{
			execute_tests();
			for (const auto kvp : m_results)
			{
				if (kvp.second.first != kvp.second.second)
					return false;
			}
			return true;
		}

		ComparisonOperation(const std::pair<TestOperandType, TestOperandType>& testOperands,
			const std::pair<ControlOperandType, ControlOperandType>& controlOperands) : m_results{},
				m_test_pair{testOperands}, m_control_pair{controlOperands} { }
		ComparisonOperation() = delete;
		ComparisonOperation(const ComparisonOperation& other) = default;
		ComparisonOperation(ComparisonOperation&& other) noexcept = default;
		ComparisonOperation& operator=(const ComparisonOperation& other) = default;
		ComparisonOperation& operator=(ComparisonOperation&& other) noexcept = default;
		~ComparisonOperation() = default;
	private:

		void execute_tests() const
		{
			if (m_results.size() != 6)
			{
				op_lookup_t& lookup = *s_operation_lookup;
				m_results.clear();
				comparison_op_t equalsOp = lookup[ComparisonOperationType::Equal];
				comparison_op_t notEqualOp = lookup[ComparisonOperationType::NotEqual];
				comparison_op_t greaterOp = lookup[ComparisonOperationType::GreaterThan];
				comparison_op_t lessOp = lookup[ComparisonOperationType::LessThan];
				comparison_op_t greaterOrEqualOp = lookup[ComparisonOperationType::GreaterThanOrEqual];
				comparison_op_t lessThanOrEqualOp = lookup[ComparisonOperationType::LessThanOrEqual];

				m_results[ComparisonOperationType::Equal] = equalsOp(m_test_pair, m_control_pair);
				m_results[ComparisonOperationType::NotEqual] = notEqualOp(m_test_pair, m_control_pair);
				m_results[ComparisonOperationType::GreaterThan] = greaterOp(m_test_pair, m_control_pair);
				m_results[ComparisonOperationType::LessThan] = lessOp(m_test_pair, m_control_pair);
				m_results[ComparisonOperationType::GreaterThanOrEqual] = greaterOrEqualOp(m_test_pair, m_control_pair);
				m_results[ComparisonOperationType::LessThanOrEqual] = lessThanOrEqualOp(m_test_pair, m_control_pair);
			}
		}

		static op_lookup_t init_comparison_op_lookup();
		mutable result_coll_t m_results;
		test_type_t m_test_pair;
		control_type_t m_control_pair;
		inline const static std::array<std::string_view, NumberOfComparisonOps> s_operation_names
		{ "is equal to", "is not equal to", "is greater than", "is less than",
			"is greater than or equal to", "is less than or equal to" };
		inline static std::unique_ptr<op_lookup_t> s_operation_lookup = std::make_unique<op_lookup_t>(init_comparison_op_lookup());
	};

	template<typename TestOperandType, typename ControlOperandType>
	class UnaryOperation
	{
		static_assert(std::numeric_limits<TestOperandType>::is_integer && !std::numeric_limits<TestOperandType>::is_signed &&
			std::numeric_limits<ControlOperandType>::is_integer && !std::numeric_limits<ControlOperandType>::is_signed &&
			std::numeric_limits<TestOperandType>::digits == std::numeric_limits<ControlOperandType>::digits,
			"Operand types must be unsigned integers that are the same width.");
	public:
		using test_type_t = TestOperandType;
		using control_type_t = ControlOperandType;

		bool all_passed() const { return m_all_passed; }
		bool plus_passed() const { return m_plus_passed; }
		bool minus_passed() const { return m_minus_passed; }
		bool bitwise_neg_passed() const { return m_bitwise_not_passed; }
		bool logical_neg_passed() const { return m_logical_not_passed; }

		test_type_t test_operand() const { return m_test_operand; }
		control_type_t control_operand() const { return m_control_operand; }

		UnaryOperation() = delete;
		UnaryOperation(const test_type_t& testOperand, const control_type_t& controlOperand);
		UnaryOperation(const UnaryOperation& other) = default;
		UnaryOperation(UnaryOperation&& other) noexcept = default;
		UnaryOperation& operator=(const UnaryOperation& other) = default;
		UnaryOperation& operator=(UnaryOperation&& other) noexcept = default;
		~UnaryOperation() = default;

	private:
		bool m_all_passed;
		bool m_plus_passed;
		bool m_minus_passed;
		bool m_bitwise_not_passed;
		bool m_logical_not_passed;
		test_type_t m_test_operand;
		control_type_t m_control_operand;
		static control_type_t twos_compl(control_type_t invert_me);
	};

	template <typename TestOperandType, typename ControlOperandType>
	typename BinaryOperation<TestOperandType, ControlOperandType>::result_pair BinaryOperation<TestOperandType,
	ControlOperandType>::get_result_pair()
	{
		if (m_result_pair.has_value())
		{
			return m_result_pair.value();
		}
		std::function<ControlOperandType(control_pair)> controlFunc;
		std::function<TestOperandType(test_pair)> testFunc;
		switch (m_op_type)
		{
		case BinaryOperationType::Addition:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first + p.second;};
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first + p.second; };
			break;
		case BinaryOperationType::Subtraction:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first - p.second; };
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first - p.second; };
			break;
		case BinaryOperationType::Multiplication:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first * p.second; };
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first * p.second; };
			break;
		case BinaryOperationType::Division:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first / p.second; };
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first / p.second; };
			break;
		case BinaryOperationType::Modulus:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first % p.second; };
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first % p.second; };
			break;
		case BinaryOperationType::Or:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return ((p.first) | (p.second)); };
			testFunc = [](const test_pair& p) -> TestOperandType {return ((p.first) | (p.second)); };
			break;
		case BinaryOperationType::Xor:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return ((p.first) ^ (p.second)); };
			testFunc = [](const test_pair& p) -> TestOperandType {return ((p.first) ^ (p.second)); };
			break;
		case BinaryOperationType::And:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return ((p.first) & (p.second)); };
			testFunc = [](const test_pair& p) -> TestOperandType {return ((p.first) & (p.second)); };
			break;
		case BinaryOperationType::LeftShift:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first << static_cast<unsigned int>(p.second % (MaxDefinedShift + 1)); };
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first << (p.second % (MaxDefinedShift + 1)); };
			break;
		case BinaryOperationType::RightShift:
			controlFunc = [](const control_pair& p) -> ControlOperandType { return p.first >> static_cast<unsigned int>(p.second % (MaxDefinedShift + 1)); };
			testFunc = [](const test_pair& p) -> TestOperandType {return p.first >> (p.second % (MaxDefinedShift + 1)); };
			break;
		}
		std::optional<TestOperandType> testRes;
		std::optional<ControlOperandType> controlRes;
		try
		{
			testRes = testFunc(m_test_pair);
		}
		catch (const std::exception& ex)
		{
			testRes = std::nullopt;
			m_thrown_exception_test = ex;
		}

		try
		{
			controlRes = controlFunc(m_control_pair);
		}
		catch (const std::exception& ex)
		{
			controlRes = std::nullopt;
			m_thrown_exception_control = ex;
		}

		m_result_pair.emplace(std::make_pair(testRes, controlRes));
		assert(m_result_pair.has_value());
		return m_result_pair;
	}

	template <typename TestOperandType, typename ControlOperandType>
	BinaryOperation<TestOperandType, ControlOperandType>::BinaryOperation(BinaryOperationType opType,
		const test_pair& testPair, const control_pair& controlPair) : m_op_type{opType}, m_test_pair{testPair},
																	  m_control_pair{controlPair}, m_thrown_exception_test{},
																	  m_thrown_exception_control{}, m_result_pair{}
	{

		switch (opType)
		{
		case BinaryOperationType::Addition:
			m_op_name = "Addition";
			break;
		case BinaryOperationType::Subtraction:
			m_op_name = "Subtraction";
			break;
		case BinaryOperationType::Multiplication:
			m_op_name = "Multiplication";
			break;
		case BinaryOperationType::Division:
			m_op_name = "Division";
			if (testPair.second == 0 || controlPair.second == 0)
				throw std::invalid_argument("If division is specified, it is illegal to have zero-valued divisors.");
			break;
		case BinaryOperationType::Modulus:
			m_op_name = "Modulus";
			if (testPair.second == 0 || controlPair.second == 0)
				throw std::invalid_argument("If modulus is specified, it is illegal to have zero-valued divisors.");
			break;
		case BinaryOperationType::Or:
			m_op_name = "Bitwise Or";
			break;
		case BinaryOperationType::Xor:
			m_op_name = "Bitwise Exclusive Or";
			break;
		case BinaryOperationType::And:
			m_op_name = "Bitwise And";
			break;
		case BinaryOperationType::LeftShift:
			m_op_name = "Left Shift";
			break;
		case BinaryOperationType::RightShift:
			m_op_name = "Right Shift";
			break;
		default:
			throw std::invalid_argument("The specified operation type is not supported.");
			break;
		}
	}

	template <typename TestOperandType, typename ControlOperandType>
	bool BinaryOperation<TestOperandType, ControlOperandType>::are_equal(const TestOperandType& lhs,
	                                                                     const ControlOperandType& rhs)
	{
		using namespace boost_cjm_uint_comparison;
		return lhs == rhs;
	}

	template <typename TestOperandType, typename ControlOperandType>
	typename ComparisonOperation<TestOperandType, ControlOperandType>::indiv_result ComparisonOperation<TestOperandType,
		ControlOperandType>::operator[](ComparisonOperationType cot) const
	{
		execute_tests();
		const unsigned short& shortVal = static_cast<unsigned short>(cot);
		if (shortVal > NumberOfComparisonOps)
		{
			throw std::invalid_argument("The supplied ComparisonOperationType argument is not supported.");
		}
		auto res = m_results[cot];
		std::string name = std::string(s_operation_names[shortVal]);
		return std::make_pair(res.first == res.second, name);
	}

	template <typename TestOperandType, typename ControlOperandType>
	typename ComparisonOperation<TestOperandType, ControlOperandType>::op_lookup_t ComparisonOperation<TestOperandType,
	ControlOperandType>::init_comparison_op_lookup()
	{
		op_lookup_t ret;
		comparison_op_t equal = [](const test_type_t& test, const control_type_t& control) -> result_type_t
		{
			result_type_t innerRet;
			innerRet.first = test.first == test.second;
			innerRet.second = control.first == control.second;
			return innerRet;
		};
		comparison_op_t not_equal = [](const test_type_t& test, const control_type_t& control) -> result_type_t
		{
			result_type_t innerRet;
			innerRet.first = test.first != test.second;
			innerRet.second = control.first != control.second;
			return innerRet;
		};
		comparison_op_t greater = [](const test_type_t& test, const control_type_t& control) -> result_type_t
		{
			result_type_t innerRet;
			innerRet.first = test.first > test.second;
			innerRet.second = control.first > control.second;
			return innerRet;
		};
		comparison_op_t lesser = [](const test_type_t& test, const control_type_t& control)->result_type_t
		{
			result_type_t innerRet;
			innerRet.first = test.first < test.second;
			innerRet.second = control.first < control.second;
			return innerRet;
		};

		comparison_op_t greaterOrEqual = [](const test_type_t& test, const control_type_t& control) -> result_type_t
		{
			result_type_t innerRet;
			innerRet.first = test.first >= test.second;
			innerRet.second = control.first >= control.second;
			return innerRet;
		};
		comparison_op_t lessOrEqual = [](const test_type_t& test, const control_type_t& control) -> result_type_t
		{
			result_type_t innerRet;
			innerRet.first = test.first <= test.second;
			innerRet.second = control.first <= control.second;
			return innerRet;
		};

		ret[ComparisonOperationType::Equal] = equal;
		ret[ComparisonOperationType::NotEqual] = not_equal;
		ret[ComparisonOperationType::GreaterThan] = greater;
		ret[ComparisonOperationType::LessThan] = lesser;
		ret[ComparisonOperationType::GreaterThanOrEqual] = greaterOrEqual;
		ret[ComparisonOperationType::LessThanOrEqual] = lessOrEqual;
		assert(ret.size() == NumberOfComparisonOps);
		return ret;
	}

	template <typename TestOperandType, typename ControlOperandType>
	UnaryOperation<TestOperandType, ControlOperandType>::UnaryOperation(const test_type_t& testOperand,
	                                                                    const control_type_t& controlOperand):
		m_test_operand{testOperand}, m_control_operand{controlOperand}
	{
		using namespace boost_cjm_uint_comparison;
		if (testOperand != controlOperand)
			throw std::invalid_argument("test and control operands must be equal.");
		test_type_t testUnaryPlus = +testOperand;
		control_type_t controlUnaryPlus = +controlOperand;
		test_type_t testUnaryMinus = -testOperand;
		control_type_t controlUnaryMinus = twos_compl(m_control_operand);
		test_type_t testBitwiseNegation = ~testOperand;
		control_type_t controlBitwiseNegation = ~controlOperand;
		bool testLogicalNegation = static_cast<bool>(!testOperand);
		bool controlLogicalNegation = static_cast<bool>(!controlOperand);
		m_plus_passed = testUnaryPlus == controlUnaryPlus;
		m_minus_passed = testUnaryMinus == controlUnaryMinus;
		m_bitwise_not_passed = testBitwiseNegation == controlBitwiseNegation;
		m_logical_not_passed = testLogicalNegation == controlLogicalNegation;
		m_all_passed = m_plus_passed && m_minus_passed && m_bitwise_not_passed && m_logical_not_passed;
	}

	template <typename TestOperandType, typename ControlOperandType>
	typename UnaryOperation<TestOperandType, ControlOperandType>::control_type_t UnaryOperation<TestOperandType,
	ControlOperandType>::twos_compl(control_type_t invert_me)
	{
		invert_me = ~invert_me;
		return ++invert_me;
	}
}
#endif // CJM_BOOST_UINT_COMPARE_TEST_HPP_


#include "Common/Common.h"
#include "GL/glew.h"
#include "Demos/Visualization/MiniGL.h"
#include "Demos/Visualization/Selection.h"
#include "GL/glut.h"
#include "Demos/Simulation/TimeManager.h"
#include <Eigen/Dense>
#include "FluidModel.h"
#include "TimeStepFluidModel.h"
#include <iostream>
#include "Demos/Utils/Utilities.h"
#include "Demos/Utils/Timing.h"
#define _USE_MATH_DEFINES
#include "math.h"

// Enable memory leak detection
#if defined(_DEBUG) && !defined(EIGEN_ALIGN)
	#define new DEBUG_NEW
#endif

using namespace PBD;
using namespace Eigen;
using namespace std;

void timeStep ();
void buildModel ();
void createBreakingDam();
void addWall(const Vector3r &minX, const Vector3r &maxX, std::vector<Vector3r> &boundaryParticles);
void initBoundaryData(std::vector<Vector3r> &boundaryParticles);
void render ();
void cleanup();
void reset();
void hsvToRgb(float h, float s, float v, float *rgb);
void selection(const Eigen::Vector2i &start, const Eigen::Vector2i &end);
void createSphereBuffers(Real radius, int resolution);
void renderSphere(const Vector3r &x, const float color[]);
void releaseSphereBuffers();
void TW_CALL setTimeStep(const void *value, void *clientData);
void TW_CALL getTimeStep(void *value, void *clientData);
void TW_CALL setVelocityUpdateMethod(const void *value, void *clientData);
void TW_CALL getVelocityUpdateMethod(void *value, void *clientData);
void TW_CALL setViscosity(const void *value, void *clientData);
void TW_CALL getViscosity(void *value, void *clientData);

FluidModel model;
TimeStepFluidModel simulation;

const Real particleRadius = 0.025;
const unsigned int width = 15;
const unsigned int depth = 15;
const unsigned int height = 20;
const Real containerWidth = (width + 1)*particleRadius*2.0 * 5.0;
const Real containerDepth = (depth + 1)*particleRadius*2.0;
const Real containerHeight = 4.0;
bool doPause = true;
std::vector<unsigned int> selectedParticles;
Vector3r oldMousePos;
// initiate buffers
GLuint elementbuffer;
GLuint normalbuffer;
GLuint vertexbuffer;
int vertexBufferSize = 0;
GLint context_major_version, context_minor_version;
string exePath;
string dataPath;

// main
int main( int argc, char **argv )
{
	REPORT_MEMORY_LEAKS

	exePath = Utilities::getFilePath(argv[0]);
	dataPath = exePath + "/" + std::string(PBD_DATA_PATH);

	// OpenGL
	MiniGL::init (argc, argv, 1024, 768, 0, 0, "Fluid demo");
	MiniGL::initLights ();
	MiniGL::setClientIdleFunc (50, timeStep);
	MiniGL::setKeyFunc(0, 'r', reset);
	MiniGL::setSelectionFunc(selection);

	MiniGL::getOpenGLVersion(context_major_version, context_minor_version);

	MiniGL::setClientSceneFunc(render);
	MiniGL::setViewport (40.0, 0.1f, 500.0, Vector3r (0.0, 3.0, 8.0), Vector3r (0.0, 0.0, 0.0));

	TwAddVarRW(MiniGL::getTweakBar(), "Pause", TW_TYPE_BOOLCPP, &doPause, " label='Pause' group=Simulation key=SPACE ");
	TwAddVarCB(MiniGL::getTweakBar(), "TimeStepSize", TW_TYPE_REAL, setTimeStep, getTimeStep, &model, " label='Time step size'  min=0.0 max = 0.1 step=0.001 precision=4 group=Simulation ");
	TwType enumType = TwDefineEnum("VelocityUpdateMethodType", NULL, 0);
	TwAddVarCB(MiniGL::getTweakBar(), "VelocityUpdateMethod", enumType, setVelocityUpdateMethod, getVelocityUpdateMethod, &simulation, " label='Velocity update method' enum='0 {First Order Update}, 1 {Second Order Update}' group=Simulation");
	TwAddVarCB(MiniGL::getTweakBar(), "Viscosity", TW_TYPE_REAL, setViscosity, getViscosity, &model, " label='Viscosity'  min=0.0 max = 0.5 step=0.001 precision=4 group=Simulation ");

	buildModel();

	if (context_major_version >= 3)
		createSphereBuffers((Real)particleRadius, 8);

	glutMainLoop ();

	cleanup ();

	Timing::printAverageTimes();

	return 0;
}

void cleanup()
{
	delete TimeManager::getCurrent();
	if (context_major_version >= 3)
		releaseSphereBuffers();
}

void reset()
{
	Timing::printAverageTimes();
	Timing::reset();

	model.reset();
	simulation.reset();
	TimeManager::getCurrent()->setTime(0.0);
}

void mouseMove(int x, int y)
{
	Vector3r mousePos;
	MiniGL::unproject(x, y, mousePos);
	const Vector3r diff = mousePos - oldMousePos;

	TimeManager *tm = TimeManager::getCurrent();
	const Real h = tm->getTimeStepSize();

	ParticleData &pd = model.getParticles();
	for (unsigned int j = 0; j < selectedParticles.size(); j++)
	{
		pd.getVelocity(selectedParticles[j]) += 5.0*diff/h;
	}
	oldMousePos = mousePos;
}

void selection(const Eigen::Vector2i &start, const Eigen::Vector2i &end)
{
	std::vector<unsigned int> hits;
	selectedParticles.clear();
	ParticleData &pd = model.getParticles();
	Selection::selectRect(start, end, &pd.getPosition(0), &pd.getPosition(pd.size() - 1), selectedParticles);
	if (selectedParticles.size() > 0)
		MiniGL::setMouseMoveFunc(GLUT_MIDDLE_BUTTON, mouseMove);
	else
		MiniGL::setMouseMoveFunc(-1, NULL);

	MiniGL::unproject(end[0], end[1], oldMousePos);
}

void timeStep ()
{
	if (doPause)
		return;

	// Simulation code
	for (unsigned int i = 0; i < 8; i++)
		simulation.step(model);
}

void buildModel ()
{
	TimeManager::getCurrent ()->setTimeStepSize (0.0025);

	createBreakingDam();
}

void hsvToRgb(float h, float s, float v, float *rgb)
{
	int i = (int)floor(h * 6);
	float f = h * 6 - i;
	float p = v * (1 - s);
	float q = v * (1 - f * s);
	float t = v * (1 - (1 - f) * s);

	switch (i % 6)
	{
	case 0: rgb[0] = v, rgb[1] = t, rgb[2] = p; break;
	case 1: rgb[0] = q, rgb[1] = v, rgb[2] = p; break;
	case 2: rgb[0] = p, rgb[1] = v, rgb[2] = t; break;
	case 3: rgb[0] = p, rgb[1] = q, rgb[2] = v; break;
	case 4: rgb[0] = t, rgb[1] = p, rgb[2] = v; break;
	case 5: rgb[0] = v, rgb[1] = p, rgb[2] = q; break;
	}
}

void render ()
{
	MiniGL::coordinateSystem();

	// Draw simulation model

	const ParticleData &pd = model.getParticles();
	const unsigned int nParticles = pd.size();

	float surfaceColor[4] = { 0.2f, 0.6f, 0.8f, 1 };
	float speccolor[4] = { 1.0, 1.0, 1.0, 1.0 };
	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, surfaceColor);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, surfaceColor);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, speccolor);
	glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 100.0);
	glColor3fv(surfaceColor);

	glPointSize(4.0);

	const Real supportRadius = model.getSupportRadius();
	Real vmax = 0.4*2.0*supportRadius / TimeManager::getCurrent()->getTimeStepSize();
	Real vmin = 0.0;

	if (context_major_version > 3)
	{
		for (unsigned int i = 0; i < nParticles; i++)
		{
			Real v = pd.getVelocity(i).norm();
			v = 0.5*((v - vmin) / (vmax - vmin));
			v = min(128.0*v*v, 0.5);
			float fluidColor[4] = { 0.2f, 0.2f, 0.2f, 1.0 };
			hsvToRgb(0.55f, 1.0f, 0.5f + (float)v, fluidColor);
			renderSphere(pd.getPosition(i), fluidColor);
		}

// 		for (unsigned int i = 0; i < model.numBoundaryParticles(); i++)
// 			renderSphere(model.getBoundaryX(i), surfaceColor);
	}
	else
	{
		glDisable(GL_LIGHTING);
		glBegin(GL_POINTS);
		for (unsigned int i = 0; i < nParticles; i++)
		{
			Real v = pd.getVelocity(i).norm();
			v = 0.5*((v - vmin) / (vmax - vmin));
			v = min(128.0*v*v, 0.5);
			float fluidColor[4] = { 0.2f, 0.2f, 0.2f, 1.0 };
			hsvToRgb(0.55f, 1.0f, 0.5f + (float)v, fluidColor);

			glColor3fv(fluidColor);
			glVertex3v(&pd.getPosition(i)[0]);
		}
		glEnd();

		// 	glBegin(GL_POINTS);
		// 	for (unsigned int i = 0; i < model.numBoundaryParticles(); i++)
		// 	{
		// 		glColor3fv(surfaceColor);
		// 		glVertex3fv(&model.getBoundaryX(i)[0]);
		// 	}
		// 	glEnd();

		glEnable(GL_LIGHTING);
	}

	float red[4] = { 0.8f, 0.0f, 0.0f, 1 };
	for (unsigned int j = 0; j < selectedParticles.size(); j++)
	{
		MiniGL::drawSphere(pd.getPosition(selectedParticles[j]), 0.08f, red);
	}

	MiniGL::drawTime( TimeManager::getCurrent ()->getTime ());
}

/** Create a breaking dam scenario
*/
void createBreakingDam()
{
	std::cout << "Initialize fluid particles\n";
	const Real diam = 2.0*particleRadius;
	const Real startX = -0.5*containerWidth + diam;
	const Real startY = diam;
	const Real startZ = -0.5*containerDepth + diam;
	const Real yshift = sqrt(3.0) * particleRadius;

	std::vector<Vector3r> fluidParticles;
	fluidParticles.resize(width*height*depth);

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int i = 0; i < (int)width; i++)
		{
			for (unsigned int j = 0; j < height; j++)
			{
				for (unsigned int k = 0; k < depth; k++)
				{
					fluidParticles[i*height*depth + j*depth + k] = diam*Vector3r((Real)i, (Real)j, (Real)k) + Vector3r(startX, startY, startZ);
				}
			}
		}
	}

	model.setParticleRadius(particleRadius);

	std::vector<Vector3r> boundaryParticles;
	initBoundaryData(boundaryParticles);

	model.initModel((unsigned int)fluidParticles.size(), fluidParticles.data(), (unsigned int)boundaryParticles.size(), boundaryParticles.data());

	std::cout << "Number of particles: " << width*height*depth << "\n";
}

void addWall(const Vector3r &minX, const Vector3r &maxX, std::vector<Vector3r> &boundaryParticles)
{
	const Real particleDistance = 2.0*model.getParticleRadius();

	const Vector3r diff = maxX - minX;
	const unsigned int stepsX = (unsigned int)(diff[0] / particleDistance) + 1u;
	const unsigned int stepsY = (unsigned int)(diff[1] / particleDistance) + 1u;
	const unsigned int stepsZ = (unsigned int)(diff[2] / particleDistance) + 1u;

	const unsigned int startIndex = (unsigned int) boundaryParticles.size();
	boundaryParticles.resize(startIndex + stepsX*stepsY*stepsZ);

	#pragma omp parallel default(shared)
	{
		#pragma omp for schedule(static)
		for (int j = 0; j < (int)stepsX; j++)
		{
			for (unsigned int k = 0; k < stepsY; k++)
			{
				for (unsigned int l = 0; l < stepsZ; l++)
				{
					const Vector3r currPos = minX + Vector3r(j*particleDistance, k*particleDistance, l*particleDistance);
					boundaryParticles[startIndex + j*stepsY*stepsZ + k*stepsZ + l] = currPos;
				}
			}
		}
	}
}

void initBoundaryData(std::vector<Vector3r> &boundaryParticles)
{
	const Real x1 = -containerWidth / 2.0;
	const Real x2 = containerWidth / 2.0;
	const Real y1 = 0.0;
	const Real y2 = containerHeight;
	const Real z1 = -containerDepth / 2.0;
	const Real z2 = containerDepth / 2.0;

	const Real diam = 2.0*particleRadius;

	// Floor
	addWall(Vector3r(x1, y1, z1), Vector3r(x2, y1, z2), boundaryParticles);
	// Top
	addWall(Vector3r(x1, y2, z1), Vector3r(x2, y2, z2), boundaryParticles);
	// Left
	addWall(Vector3r(x1, y1, z1), Vector3r(x1, y2, z2), boundaryParticles);
	// Right
	addWall(Vector3r(x2, y1, z1), Vector3r(x2, y2, z2), boundaryParticles);
	// Back
	addWall(Vector3r(x1, y1, z1), Vector3r(x2, y2, z1), boundaryParticles);
	// Front
	addWall(Vector3r(x1, y1, z2), Vector3r(x2, y2, z2), boundaryParticles);
}

void createSphereBuffers(Real radius, int resolution)
{
	Real PI = static_cast<Real>(M_PI);
	// vectors to hold our data
	// vertice positions
	std::vector<Vector3r> v;
	// normals
	std::vector<Vector3r> n;
	std::vector<unsigned short> indices;

	// initiate the variable we are going to use
	Real X1, Y1, X2, Y2, Z1, Z2;
	Real inc1, inc2, inc3, inc4, radius1, radius2;

	for (int w = 0; w < resolution; w++)
	{
		for (int h = (-resolution / 2); h < (resolution / 2); h++)
		{
			inc1 = (w / (Real)resolution) * 2 * PI;
			inc2 = ((w + 1) / (Real)resolution) * 2 * PI;
			inc3 = (h / (Real)resolution)*PI;
			inc4 = ((h + 1) / (Real)resolution)*PI;

			X1 = sin(inc1);
			Y1 = cos(inc1);
			X2 = sin(inc2);
			Y2 = cos(inc2);

			// store the upper and lower radius, remember everything is going to be drawn as triangles
			radius1 = radius*cos(inc3);
			radius2 = radius*cos(inc4);

			Z1 = radius*sin(inc3);
			Z2 = radius*sin(inc4);

			// insert the triangle coordinates
			v.push_back(Vector3r(radius1*X1, Z1, radius1*Y1));
			v.push_back(Vector3r(radius1*X2, Z1, radius1*Y2));
			v.push_back(Vector3r(radius2*X2, Z2, radius2*Y2));

			indices.push_back((unsigned short)v.size() - 3);
			indices.push_back((unsigned short)v.size() - 2);
			indices.push_back((unsigned short)v.size() - 1);

			v.push_back(Vector3r(radius1*X1, Z1, radius1*Y1));
			v.push_back(Vector3r(radius2*X2, Z2, radius2*Y2));
			v.push_back(Vector3r(radius2*X1, Z2, radius2*Y1));

			indices.push_back((unsigned short)v.size() - 3);
			indices.push_back((unsigned short)v.size() - 2);
			indices.push_back((unsigned short)v.size() - 1);

			// insert the normal data
			n.push_back(Vector3r(X1, Z1, Y1));
			n.push_back(Vector3r(X2, Z1, Y2));
			n.push_back(Vector3r(X2, Z2, Y2));
			n.push_back(Vector3r(X1, Z1, Y1));
			n.push_back(Vector3r(X2, Z2, Y2));
			n.push_back(Vector3r(X1, Z2, Y1));
		}
	}

	for (unsigned int i = 0; i < n.size(); i++)
		n[i].normalize();

	glGenBuffersARB(1, &vertexbuffer);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexbuffer);
	glBufferDataARB(GL_ARRAY_BUFFER_ARB, v.size() * sizeof(Vector3r), &v[0], GL_STATIC_DRAW);

	glGenBuffersARB(1, &normalbuffer);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, normalbuffer);
	glBufferDataARB(GL_ARRAY_BUFFER_ARB, n.size() * sizeof(Vector3r), &n[0], GL_STATIC_DRAW);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

	// Generate a buffer for the indices as well
	glGenBuffersARB(1, &elementbuffer);
	glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, elementbuffer);
	glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, indices.size() * sizeof(unsigned short), &indices[0], GL_STATIC_DRAW);
	glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);

	// store the number of indices for later use
	vertexBufferSize = (unsigned int)indices.size();

	// clean up after us
	indices.clear();
	n.clear();
	v.clear();
}

void renderSphere(const Vector3r &x, const float color[])
{
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, color);

	glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertexbuffer);
	glVertexPointer(3, GL_REAL, 0, 0);

	glBindBufferARB(GL_ARRAY_BUFFER_ARB, normalbuffer);
	glNormalPointer(GL_REAL, 0, 0);

	glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, elementbuffer);

	glPushMatrix();
	glTranslated(x[0], x[1], x[2]);
	glDrawElements(GL_TRIANGLES, (GLsizei)vertexBufferSize, GL_UNSIGNED_SHORT, 0);
	glPopMatrix();
	glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
	glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
}

void releaseSphereBuffers()
{
	if (elementbuffer != 0)
	{
		glDeleteBuffersARB(1, &elementbuffer);
		elementbuffer = 0;
	}
	if (normalbuffer != 0)
	{
		glDeleteBuffersARB(1, &normalbuffer);
		normalbuffer = 0;
	}
	if (vertexbuffer != 0)
	{
		glDeleteBuffersARB(1, &vertexbuffer);
		vertexbuffer = 0;
	}
}

void TW_CALL setTimeStep(const void *value, void *clientData)
{
	const Real val = *(const Real *)(value);
	TimeManager::getCurrent()->setTimeStepSize(val);
}

void TW_CALL getTimeStep(void *value, void *clientData)
{
	*(Real *)(value) = TimeManager::getCurrent()->getTimeStepSize();
}

void TW_CALL setVelocityUpdateMethod(const void *value, void *clientData)
{
	const short val = *(const short *)(value);
	((TimeStepFluidModel*)clientData)->setVelocityUpdateMethod((unsigned int)val);
}

void TW_CALL getVelocityUpdateMethod(void *value, void *clientData)
{
	*(short *)(value) = (short)((TimeStepFluidModel*)clientData)->getVelocityUpdateMethod();
}

void TW_CALL setViscosity(const void *value, void *clientData)
{
	const Real val = *(const Real *)(value);
	((FluidModel*)clientData)->setViscosity(val);
}

void TW_CALL getViscosity(void *value, void *clientData)
{
	*(Real *)(value) = ((FluidModel*)clientData)->getViscosity();
}


#include <boost/random/shuffle_order.hpp>


/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "itkNumericTraits.h"

#include "otbProspectModel.h"
#include "otb_boost_expint_header.h"
#include <boost/shared_ptr.hpp>
#include "otbMath.h"

//TODO check EPSILON matlab
#define EPSILON 0.0000000000000000000000001

namespace otb
{

/** Constructor */
ProspectModel
::ProspectModel()
{
   this->ProcessObject::SetNumberOfRequiredInputs(1);
   this->ProcessObject::SetNumberOfRequiredOutputs(2);

   SpectralResponseType::Pointer outputRefl = static_cast<SpectralResponseType *>(this->MakeOutput(0).GetPointer());
   this->itk::ProcessObject::SetNthOutput(0, outputRefl.GetPointer());

   SpectralResponseType::Pointer outputTrans = static_cast<SpectralResponseType *>(this->MakeOutput(1).GetPointer());
   this->itk::ProcessObject::SetNthOutput(1, outputTrans.GetPointer());
}

/** Destructor */
ProspectModel
::~ProspectModel()
{}

/** Set Input */
void
ProspectModel
::SetInput(const LeafParametersType * object)
{
   this->itk::ProcessObject::SetNthInput(0, const_cast<LeafParametersType *>(object));
}

/** Get Input */
ProspectModel::LeafParametersType *
ProspectModel
::GetInput()
{
   if(this->GetNumberOfInputs() != 1)
   {
      //exit
      return nullptr;
   }
   return static_cast<LeafParametersType *>(this->itk::ProcessObject::GetInput(0));
}

/** Make outputs */
ProspectModel::DataObjectPointer
ProspectModel
::MakeOutput(DataObjectPointerArraySizeType)
{
   return static_cast<itk::DataObject *>(SpectralResponseType::New().GetPointer());
}

/** Get Reflectance */
ProspectModel::SpectralResponseType *
ProspectModel
::GetReflectance()
{
   if(this->GetNumberOfOutputs() < 2)
   {
      //exit
      return nullptr;
   }
   return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetOutput(0));
}

/** Get Transmittance */
ProspectModel::SpectralResponseType *
ProspectModel
::GetTransmittance()
{
   if(this->GetNumberOfOutputs() < 2)
   {
      //exit
      return nullptr;
   }
   return static_cast<SpectralResponseType *>(this->itk::ProcessObject::GetOutput(1));
}

/** Set Parameters */
void
ProspectModel
::SetInput(const ParametersType & params)
{
//    m_Parameters = params;
   if(params.Size()!=6) itkExceptionMacro( << "Must have 6 parameters in that order : Cab, Car, CBrown, Cw, Cm, N" );
   LeafParametersType::Pointer leafParams = LeafParametersType::New();
   leafParams->SetCab(params[0]);
   leafParams->SetCar(params[1]);
   leafParams->SetCBrown(params[2]);
   leafParams->SetCw(params[3]);
   leafParams->SetCm(params[4]);
   leafParams->SetN(params[5]);

   this->itk::ProcessObject::SetNthInput(0, leafParams);
}

/** Plant Leaf Reflectance and Transmittance computation from 400nm to 2500 nm*/
void
ProspectModel
::GenerateData()
{

   LeafParametersType::Pointer leafParameters = this->GetInput();
   SpectralResponseType::Pointer outRefl = this->GetReflectance();
   SpectralResponseType::Pointer outTrans = this->GetTransmittance();

   unsigned int alpha=40;
   double lambda, n, k, trans, t12, temp, t21, r12, r21, x, y, ra, ta, r90, t90;
   double delta, beta, va, vb, vbNN, vbNNinv, vainv, s1, s2, s3, RN, TN;
   double N, Cab, Car, CBrown, Cw, Cm;

   N = leafParameters->GetN();
   Cab = leafParameters->GetCab();
   Car = leafParameters->GetCar();
   CBrown = leafParameters->GetCBrown();
   Cw = leafParameters->GetCw();
   Cm = leafParameters->GetCm();

   int nbdata = sizeof(DataSpecP5B) / sizeof(DataSpec);
   for (int i = 0; i < nbdata; ++i)
   {
      lambda = DataSpecP5B[i].lambda;
      n = DataSpecP5B[i].refLeafMatInd;

      k = Cab*DataSpecP5B[i].chlAbsCoef+Car*DataSpecP5B[i].carAbsCoef+CBrown*DataSpecP5B[i].brownAbsCoef+Cw*DataSpecP5B[i].waterAbsCoef;
      k = k + Cm*DataSpecP5B[i].dryAbsCoef;
      k = k / N;
      if(k == itk::NumericTraits<double>::ZeroValue() ) k=EPSILON;

      trans=(1.-k)*exp(-k)+k*k*boost::math::expint(1, k);

      t12 = this->Tav(alpha, n);
      temp = this->Tav(90, n);

      t21 = temp/(n*n);
      r12 = 1.-t12;
      r21 = 1.-t21;
      x = t12/temp;
      y = x*(temp-1)+1-t12;

      ra = r12+(t12*t21*r21*(trans*trans))/(1.-r21*r21*trans*trans);
      ta = (t12*t21*trans)/(1.-r21*r21*trans*trans);
      r90 = (ra-y)/x;
      t90 = ta/x;

      delta = (t90*t90-r90*r90-1.)*(t90*t90-r90*r90-1.) - 4.*r90*r90;
      if(delta < 0) delta = EPSILON;
      else delta=std::sqrt(delta);

      beta = (1.+r90*r90-t90*t90-delta)/(2.*r90);
      va=(1.+r90*r90-t90*t90+delta)/(2.*r90);
      if ((beta-r90)<=0)
         vb=std::sqrt(beta*(va-r90)/(va*EPSILON));
      else
         vb=std::sqrt(beta*(va-r90)/(va*(beta-r90)));

      vbNN = std::pow(vb, N-1.);
      vbNNinv = 1./vbNN;
      vainv = 1./va;
      s1=ta*t90*(vbNN-vbNNinv);
      s2=ta*(va-vainv);
      s3=va*vbNN-vainv*vbNNinv-r90*(vbNN-vbNNinv);

      RN=ra+s1/s3;
      TN=s2/s3;

      SpectralResponseType::PairType rrefl;
      SpectralResponseType::PairType ttrans;
      rrefl.first=lambda/1000.0;
      rrefl.second=RN;
      ttrans.first=lambda/1000.0;
      ttrans.second=TN;
      outRefl->GetResponse().push_back(rrefl);
      outTrans->GetResponse().push_back(ttrans);
   }
}

double
ProspectModel
::Tav(const int theta, double ref)
{

   double theta_rad = theta*CONST_PI/180;
   double r2, rp, rm, a, k, ds, k2, rm2, res, b1, b2, b;
   double ts, tp1, tp2, tp3, tp4, tp5, tp;

   r2=ref*ref;
   rp=r2+1;
   rm=r2-1;
   a=(ref+1)*(ref+1)/2;
   k=-(r2-1)*(r2-1)/4;
   ds=sin(theta_rad);

   k2=k*k;
   rm2=rm*rm;

   if(theta_rad==0) res=4*ref/((ref+1)*(ref+1));
   else
   {
      if(theta_rad==CONST_PI/2) b1=itk::NumericTraits<double>::ZeroValue();
      else b1=std::sqrt((ds*ds-rp/2)*(ds*ds-rp/2)+k);

      b2=ds*ds-rp/2;
      b=b1-b2;
      ts=(k2/(6*std::pow(b, 3))+k/b-b/2)-(k2/(6*std::pow(a, 3))+k/a-a/2);
      tp1=-2*r2*(b-a)/(rp*rp);
      tp2=-2*r2*rp*log(b/a)/rm2;
      tp3=r2*(1./b-1./a)/2;
      tp4=16*r2*r2*(r2*r2+1)*log((2*rp*b-rm2)/(2*rp*a-rm2))/(std::pow(rp, 3)*rm2);
      tp5=16*std::pow(r2, 3)*(1./(2*rp*b-rm2)-1./(2*rp*a-rm2))/std::pow(rp, 3);
      tp=tp1+tp2+tp3+tp4+tp5;
      res=(ts+tp)/(2*ds*ds);
   }
   return res;

}

void
ProspectModel
::PrintSelf(std::ostream& os, itk::Indent indent) const
{
   Superclass::PrintSelf(os, indent);

}
} // end namespace otb


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_EXPONENTIAL_FUNCTIONS_SIMD_COMMON_REALPOW_HPP_INCLUDED
#define NT2_EXPONENTIAL_FUNCTIONS_SIMD_COMMON_REALPOW_HPP_INCLUDED
#include <nt2/exponential/functions/realpow.hpp>
#include <boost/assert.hpp>
#include <boost/simd/operator/functions/details/assert_utils.hpp>
#include <nt2/include/functions/simd/is_flint.hpp>
#include <nt2/include/functions/simd/is_nltz.hpp>
#include <nt2/include/functions/simd/logical_or.hpp>
#include <nt2/include/functions/simd/pow.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( realpow_, tag::cpu_
                            , (A0)(X)
                            , ((simd_<floating_<A0>,X>))
                              ((simd_<floating_<A0>,X>))
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL_REPEAT(2)
    {
      BOOST_ASSERT_MSG( boost::simd::assert_all(logical_or(is_nltz(a0), is_flint(a1))),
                        "realpow cannot produce complex result." );
      return pow(a0, a1);
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( realpow_, tag::cpu_
                            , (A0)(A1)(X)
                            , ((simd_<floating_<A0>,X>))
                              ((simd_<integer_<A1>,X>))
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL(2)
    {
      return pow(a0, a1);
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( realpow_, tag::cpu_
                            , (A0)(A1)(X)
                            , ((simd_<floating_<A0>,X>))
                              (scalar_< integer_<A1> >)
                            )
  {

    typedef A0 result_type;

    NT2_FUNCTOR_CALL(2)
    {
      return pow(a0, a1);
    }
  };
} }

#endif


//==================================================================================================
/*!
    @file

    @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_SSE2_SIMD_FUNCTION_IS_GTZ_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_SSE2_SIMD_FUNCTION_IS_GTZ_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/as_logical.hpp>
#include <boost/simd/function/logical_and.hpp>
#include <boost/simd/function/is_gez.hpp>
#include <boost/simd/function/is_nez.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd =  boost::dispatch;
  namespace bs =  boost::simd;
  BOOST_DISPATCH_OVERLOAD ( is_gtz_
                          , (typename A0)
                          , bs::sse2_
                          , bs::pack_<bd::int64_<A0>, bs::sse_>
                         )
  {
    BOOST_FORCEINLINE bs::as_logical_t<A0> operator() ( const A0 & a0 ) const BOOST_NOEXCEPT
    {
      return logical_and(is_gez(a0), is_nez(a0));
    }
  };
} } }

#endif


// Copyright (c) 2020 CNES
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include <array>
#include <complex>
#include <memory>
#include <string>
#include <vector>
#include <Eigen/Core>
#include "angle.hpp"
#include "astronomic_angle.hpp"
#include "math.hpp"

/// @brief Wave definition
class Wave : public std::enable_shared_from_this<Wave> {
 public:
  /// Typename to a function pointer for calculate the nodal factor
  using NodalFactor = double (AstronomicAngle::*)() const;

  /// @brief Possible type of tidal wave.
  enum TidalType {
    kLongPeriod = 0,  //!< Long period tidal waves
    kShortPeriod      //!< Short period tidal waves
  };

  /// @brief Index to access the wave in the internal table
  enum Ident : size_t {
    kMm = 0,        //!< %Mm
    kMf = 1,        //!< %Mf
    kMtm = 2,       //!< %Mtm
    kMsqm = 3,      //!< %Msqm
    k2Q1 = 4,       //!< 2Q₁
    kSigma1 = 5,    //!< σ₁
    kQ1 = 6,        //!< Q₁
    kRho1 = 7,      //!< ρ₁
    kO1 = 8,        //!< O₁
    kMP1 = 9,       //!< MP₁
    kM11 = 10,      //!< M₁₁
    kM12 = 11,      //!< M₁₂
    kM13 = 12,      //!< M₁₃
    kChi1 = 13,     //!< χ₁
    kPi1 = 14,      //!< π₁
    kP1 = 15,       //!< P₁
    kS1 = 16,       //!< S₁
    kK1 = 17,       //!< K₁
    kPsi1 = 18,     //!< ψ₁
    kPhi1 = 19,     //!< φ₁
    kTheta1 = 20,   //!< θ₁
    kJ1 = 21,       //!< J₁
    kOO1 = 22,      //!< OO₁
    kMNS2 = 23,     //!< MNS₂
    kEps2 = 24,     //!< ε₂
    k2N2 = 25,      //!< 2N₂
    kMu2 = 26,      //!< µ₂
    k2MS2 = 27,     //!< 2MS₂
    kN2 = 28,       //!< N₂
    kNu2 = 29,      //!< ν₂
    kM2 = 30,       //!< M₂
    kMKS2 = 31,     //!< MKS₂
    kLambda2 = 32,  //!< λ₂
    kL2 = 33,       //!< L₂
    k2MN2 = 34,     //!< 2MN₂
    kT2 = 35,       //!< T₂
    kS2 = 36,       //!< S₂
    kR2 = 37,       //!< R₂
    kK2 = 38,       //!< K₂
    kMSN2 = 39,     //!< MSN₂
    kEta2 = 40,     //!< η₂
    k2SM2 = 41,     //!< 2SM₂
    kMO3 = 42,      //!< MO₃
    k2MK3 = 43,     //!< 2MK₃
    kM3 = 44,       //!< M₃
    kMK3 = 45,      //!< MK₃
    kN4 = 46,       //!< N₄
    kMN4 = 47,      //!< MN₄
    kM4 = 48,       //!< M₄
    kSN4 = 49,      //!< SN₄
    kMS4 = 50,      //!< MS₄
    kMK4 = 51,      //!< MK₄
    kS4 = 52,       //!< S₄
    kSK4 = 53,      //!< SK₄
    kR4 = 54,       //!< R₄
    k2MN6 = 55,     //!< 2MN₆
    kM6 = 56,       //!< M₆
    kMSN6 = 57,     //!< MSN₆
    k2MS6 = 58,     //!< 2MS₆
    k2MK6 = 59,     //!< 2MK₆
    k2SM6 = 60,     //!< 2SM₆
    kMSK6 = 61,     //!< MSK₆
    kS6 = 62,       //!< S₆
    kM8 = 63,       //!< %M8
    kMSf = 64,      //!< %MSf
    kSsa = 65,      //!< %Ssa
    kSa = 66,       //!< %Sa
  };

 protected:
  /// nodal correction for phase
  double u_{std::numeric_limits<double>::quiet_NaN()};

 private:
  /// Wave ident
  Ident ident_;

  /// Type of tide.
  TidalType type_;

  /// Function to call for computing the node factor
  NodalFactor calculate_node_factor_;

  /// Wave frequency.
  double freq_;

  /// greenwich argument
  double v_{std::numeric_limits<double>::quiet_NaN()};

  /// Nodal correction for amplitude.
  double f_{std::numeric_limits<double>::quiet_NaN()};

  /// Harmonic constituents (T, s, h, p, N′, p₁, shift, ξ, ν, ν′, ν″)
  std::array<int16_t, 11> argument_;

  /// Computes the wave frequency from the doodson arguments
  ///
  /// @param t Mean solar angle relative to Greenwich
  /// @param s moon's mean longitude
  /// @param h sun's mean longitude
  /// @param p longitude of moon's perigee
  /// @param n longitude of moon's ascending node
  /// @param p1 longitude of sun's perigee
  static constexpr double frequency(const int16_t t, const int16_t s,
                                    const int16_t h, const int16_t p,
                                    const int16_t n, const int16_t p1) {
    return ((frequency::tau() + frequency::s() - frequency::h()) * t +
            frequency::s() * s + frequency::h() * h + frequency::p() * p +
            frequency::n() * n + frequency::p1() * p1) *
           360;
  }

 public:
  /// Initializes the properties of the wave (frequency, doodson's coefficients,
  /// etc.).
  ///
  /// @param ident Index of the wave in the internal table
  /// @param t Mean solar angle relative to Greenwich
  /// @param s moon's mean longitude
  /// @param h sun's mean longitude
  /// @param p longitude of moon's perigee
  /// @param n longitude of moon's ascending node
  /// @param p1 longitude of sun's perigee
  /// @param shift TODO
  /// @param eps Coefficient for the longitude in moon's orbit of lunar
  ///   intersection
  /// @param nu Coefficient for the right ascension of lunar intersection
  /// @param nuprim Coefficient for the term in argument of lunisolar
  ///   constituent K₁
  /// @param nusec Coefficient for the term in argument of lunisolar constituent
  ///   K₂
  /// @param type Type of tidal wave
  /// @param calculate_node_factor Function used to calculate the nodal factor
  Wave(const Ident ident, const int16_t t, const int16_t s, const int16_t h,
       const int16_t p, const int16_t n, const int16_t p1, const int16_t shift,
       const int16_t eps, const int16_t nu, const int16_t nuprim,
       const int16_t nusec, TidalType type, NodalFactor calculate_node_factor)
      : ident_(ident),
        type_(type),
        calculate_node_factor_(calculate_node_factor),
        freq_(radians(frequency(t, s, h, p, n, p1)) / 3600.0) {
    argument_[0] = t;
    argument_[1] = s;
    argument_[2] = h;
    argument_[3] = p;
    argument_[4] = n;
    argument_[5] = p1;
    argument_[6] = shift;
    argument_[7] = eps;
    argument_[8] = nu;
    argument_[9] = nuprim;
    argument_[10] = nusec;
  }

  /// Default destructor
  virtual ~Wave() = default;

  /// Default copy constructor
  Wave(const Wave&) = default;

  /// Default copy assignment operator
  Wave& operator=(const Wave&) = default;

  /// Move constructor
  Wave(Wave&&) noexcept = default;

  /// Move assignment operator
  Wave& operator=(Wave&&) noexcept = default;

  /// Compute nodal corrections from SCHUREMAN (1958).
  ///
  /// @param a Astronomic angle
  void nodal_a(const AstronomicAngle& a) { f_ = (a.*calculate_node_factor_)(); }

  /// Compute nodal corrections from SCHUREMAN (1958).
  ///
  /// @param a Astronomic angle
  virtual void nodal_g(const AstronomicAngle& a);

  /// Gets the wave ident
  constexpr Ident ident() const noexcept { return ident_; }

  /// Gets the wave frequency (radians per seconds)
  constexpr double freq() const noexcept { return freq_; }

  /// Gets the wave type
  constexpr TidalType type() const noexcept { return type_; }

  /// Gets v (greenwich argument) + u (nodal correction for phase)
  double vu() const noexcept { return std::fmod(v_ + u_, two_pi<double>()); }

  /// Gets v0 (greenwich argument)
  double v() const noexcept { return v_; }

  /// Gets the nodal correction for amplitude
  constexpr double f() const noexcept { return f_; }

  /// Gets the nodal correction for phase
  constexpr double u() const noexcept { return u_; }

  /// Gets the wave name
  std::string name() const;
};

/// Mm
///
/// V = s - p;
/// u = 0;
/// f = f(Mm)
class Mm : public Wave {
 public:
  Mm()
      : Wave(kMm, 0, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, kLongPeriod,
             &AstronomicAngle::f_mm) {}
};

/// Mf
///
/// V = 2s
/// u = -2ξ
/// f = f(Mf)
class Mf : public Wave {
 public:
  Mf()
      : Wave(kMf, 0, 2, 0, 0, 0, 0, 0, -2, 0, 0, 0, kLongPeriod,
             &AstronomicAngle::f_mf) {}
};

/// Mtm
///
/// V = 3s - p
/// u = -2ξ
/// f = f(Mf)
class Mtm : public Wave {
 public:
  Mtm()
      : Wave(kMtm, 0, 3, 0, -1, 0, 0, 0, -2, 0, 0, 0, kLongPeriod,
             &AstronomicAngle::f_mf) {}
};

/// Msqm
///
/// V = 4s - 2h
/// u = -2ξ
/// f = f(Mf)
class Msqm : public Wave {
 public:
  Msqm()
      : Wave(kMsqm, 0, 4, -2, 0, 0, 0, 0, -2, 0, 0, 0, kLongPeriod,
             &AstronomicAngle::f_mf) {}
};

/// Ssa
///
/// V = 2h
/// u = 0
/// f = 1
class Ssa : public Wave {
 public:
  Ssa()
      : Wave(kSsa, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, kLongPeriod,
             &AstronomicAngle::f_1) {}
};

/// Sa
///
/// V = h
/// u = 0
/// f = 1
class Sa : public Wave {
 public:
  Sa()
      : Wave(kSa, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, kLongPeriod,
             &AstronomicAngle::f_1) {}
};

/// 2Q₁
///
/// V = T - 4s + h + 2p + 90°
/// u = +2ξ - ν
/// f = f(O₁)
class _2Q1 : public Wave {
 public:
  _2Q1()
      : Wave(k2Q1, 1, -4, 1, 2, 0, 0, 1, 2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_o1) {}
};

/// σ₁
///
/// V = T - 4s + 3h + 90°
/// u = +2ξ - ν
/// f = f(O₁)
class Sigma1 : public Wave {
 public:
  Sigma1()
      : Wave(kSigma1, 1, -4, 3, 0, 0, 0, 1, 2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_o1) {}
};

/// Q₁
///
/// V = T - 3s + h + p + 90°
/// u = +2ξ - ν
/// f = f(O₁)
class Q1 : public Wave {
 public:
  Q1()
      : Wave(kQ1, 1, -3, 1, 1, 0, 0, 1, 2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_o1) {}
};

/// ρ₁
///
/// V = T - 3s + 3h - p + 90°
/// u = +2ξ - ν
/// f = f(O₁)
class Rho1 : public Wave {
 public:
  Rho1()
      : Wave(kRho1, 1, -3, 3, -1, 0, 0, 1, 2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_o1) {}
};

// O₁
///
/// V = T - 2s + h + 90°
/// u = +2ξ - ν
/// f = f(O₁)
///
class O1 : public Wave {
 public:
  O1()
      : Wave(kO1, 1, -2, 1, 0, 0, 0, 1, 2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_o1) {}
};

/// MP₁
///
/// V = T - 2s + 3h - 90°
/// u = -ν
/// f = f(J₁)
class MP1 : public Wave {
 public:
  MP1()
      : Wave(kMP1, 1, -2, 3, 0, 0, 0, -1, 0, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_j1) {}
};

/// M₁₂ (Formula A16)
///
/// V = T - s + h - p - 90°
/// u = +2ξ - ν
/// f = f(O₁)
class M12 : public Wave {
 public:
  M12()
      : Wave(kM12, 1, -1, 1, -1, 0, 0, -1, 2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_o1) {}
};

/// M₁₃ (= M11 + M12)
///
/// V = T - s + h + p - 90
/// u = -ν
/// f = f(M₁₃)
class M13 : public Wave {
 public:
  M13()
      : Wave(kM13, 1, -1, 1, 1, 0, 0, -1, 0, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m13) {}

  /// Compute nodal corrections from SCHUREMAN (1958).
  ///
  /// @param a Astronomic angle
  void nodal_g(const AstronomicAngle& a) final {
    Wave::nodal_g(a);
    u_ -= radians(1.0 /
                  std::sqrt(2.310 + 1.435 * std::cos(2 * (a.p() - a.xi()))));
  }
};

/// M₁₁ (Formula A23)
///
/// V = T - s + h + p - 90°
/// u = -ν
/// f = f(J₁)
class M11 : public Wave {
 public:
  M11()
      : Wave(kM11, 1, -1, 1, 1, 0, 0, -1, 0, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_j1) {}
};

/// χ₁
///
/// V = T - s + 3h - p - 90°
/// u = -ν
/// f = f(J₁)
class Chi1 : public Wave {
 public:
  Chi1()
      : Wave(kChi1, 1, -1, 3, -1, 0, 0, -1, 0, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_j1) {}
};

/// π₁
///
/// V = T - 2h + p1 + 90°
/// u = 0
/// f = 1
class Pi1 : public Wave {
 public:
  Pi1()
      : Wave(kPi1, 1, 0, -2, 0, 0, 1, 1, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// P₁
///
/// V = T - h + 90°
/// u = 0
/// f = 1
class P1 : public Wave {
 public:
  P1()
      : Wave(kP1, 1, 0, -1, 0, 0, 0, 1, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// S₁
///
/// V = T
/// u = 0
/// f = 1
class S1 : public Wave {
 public:
  S1()
      : Wave(kS1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// K₁
///
/// V = T + h - 90°
/// u = - ν'
/// f = f(k₁)
class K1 : public Wave {
 public:
  K1()
      : Wave(kK1, 1, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, kShortPeriod,
             &AstronomicAngle::f_k1) {}
};

/// ψ₁
///
/// V = T + 2h - p1 - 90°
/// u = 0
/// f = 1
class Psi1 : public Wave {
 public:
  Psi1()
      : Wave(kPsi1, 1, 0, 2, 0, 0, -1, -1, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// φ₁
///
/// V = T + 3h - 90°
/// u = 0
/// f = 1
class Phi1 : public Wave {
 public:
  Phi1()
      : Wave(kPhi1, 1, 0, 3, 0, 0, 0, -1, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// θ₁
///
/// V = T + s - h + p - 90°
/// u = -ν
/// f = f(J₁)
class Theta1 : public Wave {
 public:
  Theta1()
      : Wave(kTheta1, 1, 1, -1, 1, 0, 0, -1, 0, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_j1) {}
};

/// J₁
///
/// V = T + s + h - p - 90°
/// u = -ν
/// f = f(J₁)
class J1 : public Wave {
 public:
  J1()
      : Wave(kJ1, 1, 1, 1, -1, 0, 0, -1, 0, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_j1) {}
};

/// OO₁
///
/// V = T + 2s + h - 90°
/// u = -2ξ - ν
/// f = f(OO₁)
class OO1 : public Wave {
 public:
  OO1()
      : Wave(kOO1, 1, 2, 1, 0, 0, 0, -1, -2, -1, 0, 0, kShortPeriod,
             &AstronomicAngle::f_oo1) {}
};

/// MNS₂ = M₂ + N₂ + S₂
///
/// V = 2T - 5s + 4h + p
/// u = +4ξ - 4ν
/// f = f(M₂)²
class MNS2 : public Wave {
 public:
  MNS2()
      : Wave(kMNS2, 2, -5, 4, 1, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// ε₂
///
/// V = 2T - 5s + 4h + p
/// u = +2ξ - 2ν
/// f = f(M₂)
class Eps2 : public Wave {
 public:
  Eps2()
      : Wave(kEps2, 2, -5, 4, 1, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// 2N₂
///
/// V = 2T - 4s + 2h + 2p
/// u = +2ξ - 2ν
/// f = f(M₂)
class _2N2 : public Wave {
 public:
  _2N2()
      : Wave(k2N2, 2, -4, 2, 2, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// µ₂
///
/// V = 2T - 4s + 4h
/// u = +2ξ - 2ν
/// f = f(M₂)
class Mu2 : public Wave {
 public:
  Mu2()
      : Wave(kMu2, 2, -4, 4, 0, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// 2MS₂ = 2M₂ - S₂
///
/// V = 2T - 4s + 4h
/// u = +4ξ - 4ν
/// f = f(M₂)²
class _2MS2 : public Wave {
 public:
  _2MS2()
      : Wave(k2MS2, 2, -4, 4, 0, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// N₂
///
/// V = 2T - 3s + 2h + p
/// u = +2ξ - 2ν
/// f = f(M₂)
class N2 : public Wave {
 public:
  N2()
      : Wave(kN2, 2, -3, 2, 1, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// ν₂
///
/// V = 2T - 3s + 4h - p
/// u = +2ξ - 2ν
/// f = f(M₂)
class Nu2 : public Wave {
 public:
  Nu2()
      : Wave(kNu2, 2, -3, 4, -1, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// M₂
///
/// V = 2T - 2s + 2h
/// u = +2ξ - 2ν
/// f = f(M₂)
class M2 : public Wave {
 public:
  M2()
      : Wave(kM2, 2, -2, 2, 0, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// MKS₂ = M₂ + K₂ - S₂
///
/// V = 2T - 2s + 4h
/// u = +2ξ - 2ν -2ν''
/// f = f(M₂) × f(K₂)
class MKS2 : public Wave {
 public:
  MKS2()
      : Wave(kMKS2, 2, -2, 4, 0, 0, 0, 0, 2, -2, 0, -2, kShortPeriod,
             &AstronomicAngle::f_m2_k2) {}
};

/// λ₂
///
/// V = 2T - s + p + 180°
/// u = +2ξ - 2ν
/// f = f(M₂)
class Lambda2 : public Wave {
 public:
  Lambda2()
      : Wave(kLambda2, 2, -1, 0, 1, 0, 0, 2, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// L₂
///
/// V = 2T - s + 2h - p + 180°
/// u = +2ξ - 2ν - R
/// f = f(L₂)
class L2 : public Wave {
 public:
  L2()
      : Wave(kL2, 2, -1, 2, -1, 0, 0, 2, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_l2) {}

  /// Compute nodal corrections from SCHUREMAN (1958).
  ///
  /// @param a Astronomic angle
  void nodal_g(const AstronomicAngle& a) final {
    Wave::nodal_g(a);
    u_ -= a.r();
  }
};

/// 2MN₂ = 2M₂ - N₂
///
/// V = 2T - s + 2h - p + 180°
/// u = +2ξ - 2ν
/// f = f(M₂)³
class _2MN2 : public Wave {
 public:
  _2MN2()
      : Wave(k2MN2, 2, -1, 2, -1, 0, 0, 2, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m23) {}
};

/// T₂
///
/// V = 2T - h + p₁
/// u = 0
/// f = 1
class T2 : public Wave {
 public:
  T2()
      : Wave(kT2, 2, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// S₂
///
/// V = 2T
/// u = 0
/// f = 1
class S2 : public Wave {
 public:
  S2()
      : Wave(kS2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// R₂
///
/// V = 2T + h - p1 + 180°
/// u = 0
/// f = 1
class R2 : public Wave {
 public:
  R2()
      : Wave(kR2, 2, 0, 1, 0, 0, -1, 2, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// K₂
///
/// V = 2T + 2h
/// u = -2ν″
/// f = f(K₂)
class K2 : public Wave {
 public:
  K2()
      : Wave(kK2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, -2, kShortPeriod,
             &AstronomicAngle::f_k2) {}
};

/// MSN₂ = M2 + S2 - N2
///
/// V = 2T + s -p
/// u = 0
/// f = f(M₂)²
class MSN2 : public Wave {
 public:
  MSN2()
      : Wave(kMSN2, 2, 1, 0, -1, 0, 0, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// η₂ = KJ₂
///
/// V = 2T + s + 2h - p
/// u = -2ν
/// f = f(KJ₂)
class Eta2 : public Wave {
 public:
  Eta2()
      : Wave(kEta2, 2, 1, 2, -1, 0, 0, 0, 0, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_kj2) {}
};

/// 2SM₂ = 2S₂ - M₂
///
/// V = 2T + 2s - 2h
/// u = -2ξ + 2ν
/// f = f(M₂)
class _2SM2 : public Wave {
 public:
  _2SM2()
      : Wave(k2SM2, 2, 2, -2, 0, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// MO₃ = M₂ + O₁
///
/// V = 3T - 4s + 3h + 90°
/// u = 4ξ - 3ν
/// f = f(M₂) × f(O₁)
class MO3 : public Wave {
 public:
  MO3()
      : Wave(kMO3, 3, -4, 3, 0, 0, 0, 1, 4, -3, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2_o1) {}
};

/// 2MK₃ = 2M₂ - K₁
///
/// V = 3T - 4s + 3h + 90°
/// u = 4ξ - 4ν + ν′
/// f = f(M₂)² × f(K₁)
class _2MK3 : public Wave {
 public:
  _2MK3()
      : Wave(k2MK3, 3, -4, 3, 0, 0, 0, 1, 4, -4, 1, 0, kShortPeriod,
             &AstronomicAngle::f_m22_k1) {}
};

/// M₃
///
/// V = 3T - 3s + 3h
/// u = +3ξ - 3ν
/// f = f(M₃)
class M3 : public Wave {
 public:
  M3()
      : Wave(kM3, 3, -3, 3, 0, 0, 0, 0, 3, -3, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m3) {}
};

/// MK₃ = M₂ + K₁
///
/// V = 3T - 2s + 3h - 90°
/// u = 2ξ - 2ν - ν′
/// f = f(M₂) × f(K₁)
class MK3 : public Wave {
 public:
  MK3()
      : Wave(kMK3, 3, -2, 3, 0, 0, 0, -1, 2, -2, -1, 0, kShortPeriod,
             &AstronomicAngle::f_m2_k1) {}
};

/// N4 = N₂ + N₂
///
/// V = 4T - 6s + 4h + 2p
/// u = +4ξ - 4ν
/// f = f(M₂)²
class N4 : public Wave {
 public:
  N4()
      : Wave(kN4, 4, -6, 4, 2, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// MN₄ = M₂ + N₂
///
/// V = 4T - 5s + 4h + p
/// u = +4ξ - 4ν
/// f = f(M₂)²
class MN4 : public Wave {
 public:
  MN4()
      : Wave(kMN4, 4, -5, 4, 1, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// M₄ = 2M₂
///
/// V = 4T - 4s + 4h
/// u = +4ξ - 4ν
/// f = f²(M₂)
class M4 : public Wave {
 public:
  M4()
      : Wave(kM4, 4, -4, 4, 0, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// SN₄ = S₂ + N₂
///
/// V = 4T - 3s + 2h + p
/// u = 2ξ - 2ν
/// f = f(M₂)
class SN4 : public Wave {
 public:
  SN4()
      : Wave(kSN4, 4, -3, 2, 1, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// MS₄ = M₂ + S₂
///
/// V = 4T - 2s + 2h
/// u = +2ξ - 2ν
/// f = f(M₂)
class MS4 : public Wave {
 public:
  MS4()
      : Wave(kMS4, 4, -2, 2, 0, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// MK₄ = M₂ + K₂
///
/// V = 4T - 2s + 4h
/// u = 2ξ - 2ν - 2ν''
/// f = f(MK₄)
class MK4 : public Wave {
 public:
  MK4()
      : Wave(kMK4, 4, -2, 4, 0, 0, 0, 0, 2, -2, -2, 0, kShortPeriod,
             &AstronomicAngle::f_m2_k2) {}
};

/// S₄ = S₂ + S₂
///
/// V = 4T
/// u = 0
/// f = 1
class S4 : public Wave {
 public:
  S4()
      : Wave(kS4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// SK₄ = S₂ + K₂
///
/// V = 4T + 2h
/// u = -2ν''
/// f = f(K₂)
class SK4 : public Wave {
 public:
  SK4()
      : Wave(kSK4, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, -2, kShortPeriod,
             &AstronomicAngle::f_k2) {}
};

/// R₄ = R₂ + R₂
///
/// V = 4T + 2h - 2p1
/// u = 0
/// f = 1
class R4 : public Wave {
 public:
  R4()
      : Wave(kR4, 4, 0, 2, 0, 0, -2, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// 2MN₆ = 2M₂ + N₂
///
/// V = 6T - 7s + 6h + p
/// u = 6ξ - 6ν
/// f = f(M₂)³
class _2MN6 : public Wave {
 public:
  _2MN6()
      : Wave(k2MN6, 6, -7, 6, 1, 0, 0, 0, 6, -6, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m23) {}
};

/// M₆ = 3M₂
///
/// V = 6T - 6s + 6h
/// u = +6ξ - 6ν
/// f = f(M₂)³
class M6 : public Wave {
 public:
  M6()
      : Wave(kM6, 6, -6, 6, 0, 0, 0, 0, 6, -6, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m23) {}
};

/// MSN₆ = M₂ + S₂ + N₂
///
/// V = 6T - 5s + 4h + p
/// u = 4ξ - 4ν
/// f = f(M₂)²
class MSN6 : public Wave {
 public:
  MSN6()
      : Wave(kMSN6, 6, -5, 4, 1, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// 2MS₆ = 2M₂ + S₂
///
/// V = 6T - 4s + 4h
/// u = 4ξ - 4ν
/// f = f(M₂)²
class _2MS6 : public Wave {
 public:
  _2MS6()
      : Wave(k2MS6, 6, -4, 4, 0, 0, 0, 0, 4, -4, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m22) {}
};

/// 2MK₆ = 2M₂ + K₂
///
/// V = 6T - 4s + 6h
/// u = 4ξ - 4ν - 2ν''
/// f = f(M₂)² × f(K₂)
class _2MK6 : public Wave {
 public:
  _2MK6()
      : Wave(k2MK6, 6, -4, 6, 0, 0, 0, 0, 4, -4, 0, -2, kShortPeriod,
             &AstronomicAngle::f_m23_k2) {}
};

/// 2SM₆ = 2S₂ + M₂
///
/// V = 6T - 2s + 2h
/// u = 2ξ - 2ν
/// f = f(M₂)
class _2SM6 : public Wave {
 public:
  _2SM6()
      : Wave(k2SM6, 6, -2, 2, 0, 0, 0, 0, 2, -2, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m2) {}
};

/// MSK₆ = M₂ + K₂ + S₂
///
/// V = 6T - 2s + 4h
/// u = 2ξ - 2ν - 2ν''
/// f = f(M₂) × f(K₂)
class MSK6 : public Wave {
 public:
  MSK6()
      : Wave(kMSK6, 6, -2, 4, 0, 0, 0, 0, 2, -2, -2, 0, kShortPeriod,
             &AstronomicAngle::f_m2_k2) {}
};

/// S₆ = 3S₂
///
/// V = 6T
/// u = 0
/// f = 1
class S6 : public Wave {
 public:
  S6()
      : Wave(kS6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, kShortPeriod,
             &AstronomicAngle::f_1) {}
};

/// M₈ = 4M₂
///
/// V = 8T - 8s + 8h
/// u = 8ξ - 8ν
/// f = f(M₂)⁴
class M8 : public Wave {
 public:
  M8()
      : Wave(kM8, 8, -8, 8, 0, 0, 0, 0, 8, -8, 0, 0, kShortPeriod,
             &AstronomicAngle::f_m24) {}
};

/// MSf = M₂ - S₂
///
/// V = 2s - 2h
/// u = 2ξ - 2ν
/// f = f(M₂) * f(S2) = f(M₂)
///
/// @warning Same frequency as MSf LP : 2s -2h
class MSf : public Wave {
 public:
  MSf()
      : Wave(kMSf, 0, 2, -2, 0, 0, 0, 0, 2, -2, 0, 0, kLongPeriod,
             &AstronomicAngle::f_m2) {}
};

/// Properties of tide waves computed
class WaveTable {
 private:
  std::vector<std::shared_ptr<Wave>> waves_{};

  /// Compute nodal corrections from SCHUREMAN (1958).
  ///
  /// Indexes used in this routine are internal to the code
  /// and corresponds to the "original" ondes.dat file.
  ///
  /// @param a Astronomic angle
  void nodal_a(const AstronomicAngle& a) {
    for (auto& item : waves_) {
      item->nodal_a(a);
    }
  }

  /// Compute nodal corrections from SCHUREMAN (1958).
  ///
  /// Indexes used in this routine are internal to the code and corresponds to
  /// the "original" ondes.dat file.
  ///
  /// @param a Astronomic angle
  void nodal_g(const AstronomicAngle& a) {
    for (auto& item : waves_) {
      item->nodal_g(a);
    }
  }

 public:
  /// Default constructor
  WaveTable(const std::vector<std::string>& waves = {});

  /// Gets the tidal waves known
  static std::vector<std::string> known_constituents();

  /// Compute nodal corrections.
  ///
  /// @param epoch Desired UTC time expressed in number of seconds elapsed since
  /// 1970-01-01T00:00:00
  /// @return the astronomic angle, indicating the date on which the tide is to
  /// be calculated.
  AstronomicAngle compute_nodal_corrections(const double epoch) {
    auto angles = AstronomicAngle(epoch);

    nodal_a(angles);
    nodal_g(angles);

    return angles;
  }

  /// Gets the wave properties
  std::shared_ptr<Wave> wave(const Wave::Ident ident) const {
    auto it = find(ident);
    return it != end() ? *it : nullptr;
  }

  /// Gets the wave properties
  const std::shared_ptr<Wave>& operator[](const size_t index) const {
    return waves_.at(index);
  }

  /// Gets the wave properties
  std::shared_ptr<Wave> wave(const std::string& ident) const {
    auto it = find(ident);
    return it != end() ? *it : nullptr;
  }

  /// Gets the wave properties
  std::shared_ptr<Wave>& wave(const Wave::Ident ident) { return waves_[ident]; }

  /// Returns an iterator to the beginning of the wave table
  std::vector<std::shared_ptr<Wave>>::const_iterator begin() const {
    return waves_.begin();
  }

  /// Returns an iterator to the end of the wave table
  std::vector<std::shared_ptr<Wave>>::const_iterator end() const {
    return waves_.end();
  }

  /// Returns an iterator to the beginning of the wave table
  std::vector<std::shared_ptr<Wave>>::iterator begin() {
    return waves_.begin();
  }

  /// Returns an iterator to the end of the wave table
  std::vector<std::shared_ptr<Wave>>::iterator end() { return waves_.end(); }

  /// Searches the properties of a wave from its name.
  std::vector<std::shared_ptr<Wave>>::const_iterator find(
      const std::string& name) const {
    for (auto it = begin(); it != end(); ++it) {
      if (name == (*it)->name()) {
        return it;
      }
    }
    return end();
  }

  /// Searches the properties of a wave from its identifier.
  std::vector<std::shared_ptr<Wave>>::const_iterator find(
      const Wave::Ident& ident) const {
    for (auto it = begin(); it != end(); ++it) {
      if (ident == (*it)->ident()) {
        return it;
      }
    }
    return end();
  }

  /// Returns the size of the table
  size_t size() const { return waves_.size(); }

  static Eigen::VectorXcd harmonic_analysis(
      const Eigen::Ref<const Eigen::VectorXd>& h,
      const Eigen::Ref<const Eigen::MatrixXd>& f,
      const Eigen::Ref<const Eigen::MatrixXd>& vu);
};


#pragma once

#include <Eigen/Dense>
#include <bphash/Hasher.hpp>

namespace bphash {

template<typename S,  // Scalar
         int R,       // RowsAtCompileTime
         int C,       // ColsAtCompileTime
         int O,       // Options
         int MR,      // MaxRows
         int MC>      // MaxCols
void hash_object(const Eigen::Matrix<S, R, C, O, MR, MC> & mat, Hasher & h)
{
    h(mat.rows(), mat.cols());
    h(hash_pointer(mat.data(), mat.rows() * mat.cols()));
}

} // close namespace bphash


// Boost.Geometry

// Copyright (c) 2019, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_POINT_ORDER_HPP
#define BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_POINT_ORDER_HPP

#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/srs/spheroid.hpp>

#include <boost/geometry/strategies/geographic/parameters.hpp>
#include <boost/geometry/strategies/point_order.hpp>
#include <boost/geometry/strategies/spherical/point_in_point.hpp>

#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/select_calculation_type.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace point_order
{

template
    <
        typename FormulaPolicy = strategy::andoyer,
        typename Spheroid = srs::spheroid<double>,
        typename CalculationType = void
    >
struct geographic
{
    typedef azimuth_tag version_tag;

    template <typename Geometry>
    struct result_type
    {
        typedef typename geometry::select_calculation_type_alt
            <
                CalculationType, Geometry
            >::type type;
    };

    geographic()
    {}

    explicit geographic(Spheroid const& spheroid)
        : m_spheroid(spheroid)
    {}

    template <typename Point>
    inline bool apply(Point const& p1, Point const& p2,
                      typename result_type<Point>::type & azi,
                      typename result_type<Point>::type & razi) const
    {
        typedef typename result_type<Point>::type calc_t;

        if (equals_point_point(p1, p2))
        {
            return false;
        }

        formula::result_inverse<calc_t> res = FormulaPolicy::template inverse
            <
                calc_t, false, true, true, false, false
            >::apply(geometry::get_as_radian<0>(p1),
                     geometry::get_as_radian<1>(p1),
                     geometry::get_as_radian<0>(p2),
                     geometry::get_as_radian<1>(p2),
                     m_spheroid);

        azi = res.azimuth;
        razi = res.reverse_azimuth;

        return true;
    }

    template <typename Point>
    inline typename result_type<Point>::type
    apply(Point const& /*p0*/, Point const& /*p1*/, Point const& /*p2*/,
          typename result_type<Point>::type const& azi1,
          typename result_type<Point>::type const& azi2) const
    {
        // TODO: support poles
        return math::longitude_distance_signed<radian>(azi1, azi2);
    }

private:
    template <typename Point>
    static bool equals_point_point(Point const& p0, Point const& p1)
    {
        return strategy::within::spherical_point_point::apply(p0, p1);
    }

    Spheroid m_spheroid;
};

namespace services
{

template <>
struct default_strategy<geographic_tag>
{
    typedef geographic<> type;
};

} // namespace services

}} // namespace strategy::point_order

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_POINT_ORDER_HPP


// Testing availability of boost headers

#include <iostream>
#include <boost/math/distributions.hpp>
#include <boost/static_assert.hpp>

void test() {
  BOOST_STATIC_ASSERT(1 == 1);
  boost::math::binomial_distribution<double> d(10, 0.5);
}


//
// Copyright (c) 2011-2012 Krzysztof Jusiak (krzysztof at jusiak dot net)
//
// 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)
//
#ifndef QFSM_BACK_AUX_STATES_HPP
#define QFSM_BACK_AUX_STATES_HPP

#include <boost/mpl/set.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/fold.hpp>
#include <boost/mpl/insert.hpp>
#include <boost/mpl/push_back.hpp>
#include "QFsm/Back/Aux/TransitionTypes.hpp"

namespace QFsm
{
namespace Back
{
namespace Aux
{

template<typename TransitionTable>
struct States : boost::mpl::fold
    <
        typename boost::mpl::fold
        <
            TransitionTable,
            boost::mpl::set0<>,
            boost::mpl::insert
            <
                boost::mpl::insert
                <
                    boost::mpl::_1,
                    TargetState<boost::mpl::_2>
                >,
                InitState<boost::mpl::_2>
            >
        >::type,
        boost::mpl::vector0<>,
        boost::mpl::push_back
        <
            boost::mpl::_1, boost::mpl::_2
        >
    >
{ };

} // namespace Aux
} // namespace Back
} // namespace QFsm

#endif


/******************************************************************************
 * Copyright (C) 2014 by Jerome Maye                                          *
 * jerome.maye@gmail.com                                                      *
 ******************************************************************************/

#include "aslam/calibration/car/error-terms/ErrorTermWheel.h"

#include <cmath>

#include <Eigen/Dense>

using namespace aslam::backend;

namespace aslam {
  namespace calibration {

/******************************************************************************/
/* Constructors and Destructor                                                */
/******************************************************************************/

    ErrorTermWheel::ErrorTermWheel(const EuclideanExpression& v_v_mw, const
        ScalarExpression& k, double measurement, const Covariance& sigma2Wheel,
        bool frontEnabled) :
        _v_v_mw(v_v_mw),
        _k(k),
        _measurement(measurement),
        _sigma2Wheel(sigma2Wheel),
        _frontEnabled(frontEnabled) {
      setInvR(_sigma2Wheel.inverse());
      DesignVariable::set_t dv;
      v_v_mw.getDesignVariables(dv);
      k.getDesignVariables(dv);
      setDesignVariablesIterator(dv.begin(), dv.end());
    }

    ErrorTermWheel::ErrorTermWheel(const ErrorTermWheel& other) :
        ErrorTermFs<3>(other),
        _v_v_mw(other._v_v_mw),
        _k(other._k),
        _measurement(other._measurement),
        _sigma2Wheel(other._sigma2Wheel),
        _frontEnabled(other._frontEnabled) {
    }

    ErrorTermWheel& ErrorTermWheel::operator = (const ErrorTermWheel& other) {
      if (this != &other) {
        ErrorTermFs<3>::operator=(other);
       _v_v_mw = other._v_v_mw;
       _k = other._k;
       _measurement = other._measurement;
       _sigma2Wheel = other._sigma2Wheel;
       _frontEnabled = other._frontEnabled;
      }
      return *this;
    }

    ErrorTermWheel::~ErrorTermWheel() {
    }

/******************************************************************************/
/* Accessors                                                                  */
/******************************************************************************/

    double ErrorTermWheel::getMeasurement() const {
      return _measurement;
    }

    void ErrorTermWheel::setMeasurement(double measurement) {
      _measurement = measurement;
    }

    const ErrorTermWheel::Covariance& ErrorTermWheel::getCovariance() const {
      return _sigma2Wheel;
    }

    void ErrorTermWheel::setCovariance(const Covariance& sigma2Wheel) {
      _sigma2Wheel = sigma2Wheel;
    }

    bool ErrorTermWheel::getFrontEnabled() const {
      return _frontEnabled;
    }

    void ErrorTermWheel::setFrontEnabled(bool enabled) {
      _frontEnabled = enabled;
    }

/******************************************************************************/
/* Methods                                                                    */
/******************************************************************************/

    double ErrorTermWheel::evaluateErrorImplementation() {
      error_t error;
      const double v0 = _v_v_mw.toValue()(0);
      const double v1 = _v_v_mw.toValue()(1);
      const double v2 = _v_v_mw.toValue()(2);
      const double k = _k.toScalar();
      if (_frontEnabled) {
        const double temp = std::sqrt(v1 * v1 / (v0 * v0) + 1);
        error(0) = _measurement - k * (v0 / temp + v1 * v1 / (v0 * temp));
        error(1) = 0.0;
        error(2) = -v2;
      }
      else {
        error(0) = _measurement - k * v0;
        error(1) = -v1;
        error(2) = -v2;
      }
      setError(error);
      return evaluateChiSquaredError();
    }

    void ErrorTermWheel::evaluateJacobiansImplementation(JacobianContainer&
        jacobians) {
      const double k = _k.toScalar();
      const double v0 = _v_v_mw.toValue()(0);
      Eigen::Matrix<double, 3, 3> J_v_v_mw =
        Eigen::Vector3d(k, 1.0, 1.0).asDiagonal();
      Eigen::Matrix<double, 3, 1> J_k(v0, 0.0, 0.0);
      if (_frontEnabled) {
        const double v1 = _v_v_mw.toValue()(1);
        const double temp1 = std::sqrt(v0 * v0 + v1 * v1);
        J_v_v_mw(0, 0) = k * v0 / temp1;
        J_v_v_mw(0, 1) = k * v1 / temp1;
        J_v_v_mw(1, 1) = 0.0;
        const double temp2 = std::sqrt(v1 * v1 / (v0 * v0) + 1);
        J_k(0, 0) = (v0 / temp2 + v1 * v1 / (v0 * temp2));
      }
      _v_v_mw.evaluateJacobians(jacobians, -J_v_v_mw);
      _k.evaluateJacobians(jacobians, -J_k);
    }

  }
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  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_FUNCTION_ITRUNC_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_ITRUNC_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-arithmetic
    Function object implementing itrunc capabilities

    Convert to integer by saturated truncation.

    This is a convenient alias of @ref toints
  **/
  const boost::dispatch::functor<tag::itrunc_> itrunc = {};
} }
#endif

#include <boost/simd/function/scalar/toints.hpp>
#include <boost/simd/function/simd/itrunc.hpp>

#endif


#if !defined(BALSA_EIGEN_CONTAINER_SIZE_HPP)
#define BALSA_EIGEN_CONTAINER_SIZE_HPP
#include "../types/has_tuple_size.hpp"
#include <Eigen/Core>

namespace balsa::eigen {
// outputs the size of a STL container like we would use for an eigen type. In particular, for static size objects it returns their size and for dynamic size ones it outputs dynamic
template<typename T>
constexpr int container_size() {
    if constexpr (types::has_tuple_size<T>) {
        return std::tuple_size<T>();
    } else {
        return Eigen::Dynamic;
    }
}
template<typename T>
constexpr int container_size(T &&) {
    return container_size<T>();
}
}// namespace balsa::eigen
#endif


#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include "p2p.hpp"
using namespace Crowd;

// class RocksyT : public Rocksy {};

// BOOST_FIXTURE_TEST_SUITE(DbFolderPath, RocksyT)

// // use any protected methods inside your tests
// BOOST_AUTO_TEST_CASE(PathTest)
// {
//     BOOST_CHECK(boost::algorithm::ends_with(usersdb_folder_path, ".config/onzehub/usersdb"));
// }
// BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE(ProtocolTest)

Protocol p;

BOOST_AUTO_TEST_CASE(LayerManagement)
{
    std::map<uint32_t, uint256_t>result1;
    for (int i = 1; i <= 100; i++)
    {
        if (i <= 10)
        {
            result1[i] = 1;
        }
        else
        {
            result1[i] = 0;
        }
    }
    uint256_t s = 10;
    BOOST_CHECK(p.layers_management(s) == result1);

    std::map<uint32_t, uint256_t> result2;
    for (int i = 1; i <= 100; i++)
    {
        if (i <= 1)
        {
            result2[i] = 3;
        }
        else
        {
            result2[i] = 1;
        }
    }
    s = 102;
    BOOST_CHECK(p.layers_management(s) == result2);

    std::map<uint32_t, uint256_t> result3;
    for (int i = 1; i <= 100; i++)
    {
        if (i <= 11)
        {
            result3[i] = 101010101;
        }
        else if (i == 12)
        {
            result3[i] = 2202003;
        }
        else
        {
            result3[i] = 1010101;
        }
    }
    s = 1202202002;
    BOOST_CHECK(p.layers_management(s) == result3);
}

BOOST_AUTO_TEST_SUITE_END()


//==================================================================================================
/*!
  @file

  @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_FUNCTION_ISINCLUDED_C_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_ISINCLUDED_C_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
 /*!
    @ingroup group-reduction
    Function object implementing isincluded_c capabilities

    Returns @ref True is only if all bits set in x are not set in y

    @par Semantic:

    For every parameters of types respectively T0, T1 of same bit size:

    @code
    auto r = isincluded_c(x,y);
    @endcode

    is similar to:

    @code
    auto r = all(x&y == zero);
    @endcode
  **/
  template<typename T0, typename T1>
  bool isincluded_c(T0 const& x, T1 const& y);
} }
#endif

#include <boost/simd/function/scalar/isincluded_c.hpp>
#include <boost/simd/function/simd/isincluded_c.hpp>

#endif


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/functional/placeholder.hpp>

#include <boost/hana/assert.hpp>

#include <type_traits>
#include <utility>
using namespace boost::hana;

struct _extra { virtual ~_extra() { } };
_extra extra{};

constexpr struct { } invalid{};

template <typename ...> using bool_t = bool;
constexpr bool valid_call(...) { return false; }
template <typename F, typename ...Args>
constexpr auto valid_call(F&& f, Args&& ...args)
    -> bool_t<decltype(std::forward<F>(f)(std::forward<Args>(args)...))>
{ return true; }

#define BOOST_HANA_TEST_BINARY_OP(op, x, y)                                 \
    BOOST_HANA_CONSTEXPR_CHECK((_ op _)(x, y) == (x op y));                 \
    BOOST_HANA_RUNTIME_CHECK((_ op _)(x, y, extra) == (x op y));            \
    BOOST_HANA_RUNTIME_CHECK((_ op _)(x, y, extra, extra) == (x op y));     \
    static_assert(!valid_call(_ op _), "");                                 \
    static_assert(!valid_call(_ op _, invalid), "");                        \
    static_assert(!valid_call(_ op _, invalid, invalid), "");               \
                                                                            \
    BOOST_HANA_CONSTEXPR_CHECK((_ op y)(x) == (x op y));                    \
    BOOST_HANA_RUNTIME_CHECK((_ op y)(x, extra) == (x op y));               \
    BOOST_HANA_RUNTIME_CHECK((_ op y)(x, extra, extra) == (x op y));        \
    static_assert(!valid_call(_ op y), "");                                 \
    static_assert(!valid_call(_ op y, invalid), "");                        \
                                                                            \
    BOOST_HANA_CONSTEXPR_CHECK((x op _)(y) == (x op y));                    \
    BOOST_HANA_RUNTIME_CHECK((x op _)(y, extra) == (x op y));               \
    BOOST_HANA_RUNTIME_CHECK((x op _)(y, extra, extra) == (x op y));        \
    static_assert(!valid_call(x op _), "");                                 \
    static_assert(!valid_call(x op _, invalid), "");                        \
    static_assert(!valid_call(x op _, invalid), "");                        \
/**/

#define BOOST_HANA_TEST_UNARY_OP(op, x)                                     \
    BOOST_HANA_CONSTEXPR_CHECK((op _)(x) == (op x));                        \
    BOOST_HANA_RUNTIME_CHECK((op _)(x, extra) == (op x));                   \
    BOOST_HANA_RUNTIME_CHECK((op _)(x, extra, extra) == (op x));            \
    static_assert(!valid_call(op _), "");                                   \
    static_assert(!valid_call(op _, invalid), "");                          \
/**/

struct _incr {
    template <typename X>
    constexpr auto operator()(X x) const -> decltype(x+1)
    { return x+1; }
};
constexpr _incr incr{};

int main() {
    // Arithmetic
    BOOST_HANA_TEST_UNARY_OP(+, 1)
    BOOST_HANA_TEST_UNARY_OP(-, 1)
    BOOST_HANA_TEST_BINARY_OP(+, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(-, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(*, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(/, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(%, 6, 3)

    // Bitwise
    BOOST_HANA_TEST_UNARY_OP(~, 5)
    BOOST_HANA_TEST_BINARY_OP(&, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(|, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(^, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(<<, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(>>, 6, 3)

    // Comparison
    BOOST_HANA_TEST_BINARY_OP(==, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(!=, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(<, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(<=, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(>, 6, 3)
    BOOST_HANA_TEST_BINARY_OP(>=, 6, 3)

    // Logical
    BOOST_HANA_TEST_BINARY_OP(||, true, false)
    BOOST_HANA_TEST_BINARY_OP(&&, true, true)
    BOOST_HANA_TEST_UNARY_OP(!, true)

    // Member access
    constexpr int i = 4;
    constexpr int array[] = {0, 1, 2};
    BOOST_HANA_TEST_UNARY_OP(*, &i)

    BOOST_HANA_CONSTEXPR_CHECK(_[0](array) == array[0]);
    BOOST_HANA_RUNTIME_CHECK(_[0](array, extra) == array[0]);
    BOOST_HANA_RUNTIME_CHECK(_[0](array, extra, extra) == array[0]);
    BOOST_HANA_CONSTEXPR_CHECK(_[1](array) == array[1]);
    BOOST_HANA_CONSTEXPR_CHECK(_[1](array) == array[1]);
    BOOST_HANA_CONSTEXPR_CHECK(_[2](array) == array[2]);
    static_assert(!valid_call(_[invalid]), "");
    static_assert(!valid_call(_[invalid], array), "");
    static_assert(!valid_call(_[invalid], invalid), "");
    static_assert(!valid_call(_[0], invalid), "");

    // Call operator
    BOOST_HANA_CONSTEXPR_CHECK(_(1)(incr) == incr(1));
    BOOST_HANA_RUNTIME_CHECK(_(1)(incr, extra) == incr(1));
    BOOST_HANA_RUNTIME_CHECK(_(1)(incr, extra, extra) == incr(1));
    BOOST_HANA_CONSTEXPR_CHECK(_(2)(incr) == incr(2));
    BOOST_HANA_CONSTEXPR_CHECK(_(3)(incr) == incr(3));
    static_assert(!valid_call(_(invalid)), "");
    static_assert(!valid_call(_(invalid), incr), "");
    static_assert(!valid_call(_(invalid), invalid), "");
    static_assert(!valid_call(_(1), invalid), "");
}