#include <contracts.hpp>
#include <eosio/chain/abi_serializer.hpp>
#include <eosio/testing/tester.hpp>

#include <Runtime/Runtime.h>

#include <boost/multiprecision/cpp_int.hpp>
#include <boost/test/unit_test.hpp>
#include <fc/variant_object.hpp>

#include <cmath>

#include "mindswap_test_api.hpp"

using namespace eosio::testing;
using namespace eosio;
using namespace eosio::chain;
using namespace fc;
using namespace std;
using namespace boost::multiprecision;

using int128 = boost::multiprecision::int128_t;
using int256 = boost::multiprecision::int256_t;
using mvo = fc::mutable_variant_object;

class mindswapswap_tester : public tester {
protected:
	mindswap_test_api api;

public:
	mindswapswap_tester()
		: api(N(mindswapswap), this) {
		produce_blocks(2);

		create_accounts({N(alice), N(bob), N(carol), N(eosio.token), N(wevotethefee), N(badtoken), N(prediqtpedia)});
		produce_blocks(2);

		set_code(N(eosio.token), contracts::token_wasm());
		set_abi(N(eosio.token), contracts::token_abi().data());

		set_code(N(carol), contracts::token_wasm());
		set_abi(N(carol), contracts::token_abi().data());

		set_code(N(badtoken), contracts::badtoken_wasm());
		set_abi(N(badtoken), contracts::badtoken_abi().data());

		set_code(N(wevotethefee), contracts::wevotethefee_wasm());
		set_abi(N(wevotethefee), contracts::wevotethefee_abi().data());

		produce_blocks();

		const auto& accnt1 = control->db().get<account_object, by_name>(N(eosio.token));
		abi_def abi1;
		BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt1.abi, abi1), true);
		abi_ser.set_abi(abi1, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	}

	fc::variant get_balance(name smartctr, name user, name table, int64_t id, string struc) {
		vector<char> data = get_row_by_account(smartctr, user, table, account_name(id));
		return data.empty() ? fc::variant()
							: abi_ser.binary_to_variant(struc, data, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	}

	action_result
	push_action(const account_name& smartctr, const account_name& signer, const action_name& name, const variant_object& data) {
		string action_type_name = abi_ser.get_action_type(name);

		action act;
		act.account = smartctr;
		act.name = name;
		act.data = abi_ser.variant_to_binary(action_type_name, data, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
		return base_tester::push_action(std::move(act), signer.to_uint64_t());
	}
	action_result create(account_name issuer, asset maximum_supply) {
		return push_action(N(eosio.token), N(eosio.token), N(create), mvo()("issuer", issuer)("maximum_supply", maximum_supply));
	}
	action_result issue(account_name issuer, account_name to, asset quantity, string memo) {
		return push_action(N(eosio.token), issuer, N(issue), mvo()("to", to)("quantity", quantity)("memo", memo));
	}
	action_result transfer(name contract, account_name from, account_name to, asset quantity, string memo) {
		return push_action(contract, from, N(transfer), mvo()("from", from)("to", to)("quantity", quantity)("memo", memo));
	}

	action_result openfeetable(name user, symbol_code pair_token) {
		return push_action(N(wevotethefee), user, N(openfeetable), mvo()("user", user)("pair_token", pair_token));
	}
	action_result closefeetable(symbol_code pair_token) {
		return push_action(N(wevotethefee), N(wevotethefee), N(closefeetable), mvo()("pair_token", pair_token));
	}
	action_result votefee(name user, symbol_code pair_token, int fee_voted) {
		return push_action(N(wevotethefee), user, N(votefee), mvo()("user", user)("pair_token", pair_token)("fee_voted", fee_voted));
	}
	action_result closevote(name user, symbol_code pair_token) {
		return push_action(N(wevotethefee), user, N(closevote), mvo()("user", user)("pair_token", pair_token));
	}
	action_result updatefee(name user, symbol_code pair_token) {
		return push_action(N(wevotethefee), user, N(updatefee), mvo()("pair_token", pair_token));
	}

	int64_t balance(name user, int64_t id) {
		auto _balance = get_balance(N(mindswapswap), user, N(mindacnts), id, "mindaccount");
		return to_int(
			fc::json::to_string(_balance["balance"]["quantity"], fc::time_point(fc::time_point::now() + fc::microseconds{1000 * 1000})));
	}
	int64_t tok_balance(name user, int64_t id) {
		auto _balance = get_balance(N(mindswapswap), user, N(accounts), id, "account");
		return to_int(fc::json::to_string(_balance["balance"], fc::time_point(fc::time_point::now() + fc::microseconds{1000 * 1000})));
	}
	int64_t token_balance(name contract, name user, int64_t id) {
		auto _balance = get_balance(contract, user, N(accounts), id, "account");
		return to_int(fc::json::to_string(_balance["balance"], fc::time_point(fc::time_point::now() + fc::microseconds{1000 * 1000})));
	}
	int64_t to_int(string in) {
		auto sub = in.substr(1, in.length() - 2);
		return asset::from_string(sub).get_amount();
	}
	vector<int64_t> system_balance(int64_t id) {
		auto sys_balance_json = get_balance(N(mindswapswap), name(id), N(stat), id, "currency_stats");
		auto saldo1 = to_int(fc::json::to_string(sys_balance_json["pool1"]["quantity"],
												 fc::time_point(fc::time_point::now() + fc::microseconds{1000 * 1000})));
		auto saldo2 = to_int(fc::json::to_string(sys_balance_json["pool2"]["quantity"],
												 fc::time_point(fc::time_point::now() + fc::microseconds{1000 * 1000})));
		auto minted =
			to_int(fc::json::to_string(sys_balance_json["supply"], fc::time_point(fc::time_point::now() + fc::microseconds{1000 * 1000})));
		vector<int64_t> ans = {saldo1, saldo2, minted};
		/*
        std::cout << "saldo1 " << saldo1 << std::endl;
        std::cout << "saldo2 " << saldo2 << std::endl;
        std::cout << "minted " << minted << std::endl;
        */
		return ans;
	}
	bool is_increasing(vector<int64_t> v, vector<int64_t> w) {
		int256 x = int256(v.at(0)) * int256(v.at(1)) * int256(w.at(2)) * int256(w.at(2));
		int256 y = int256(w.at(0)) * int256(w.at(1)) * int256(v.at(2)) * int256(v.at(2));
		return x <= y;
	}
	vector<int64_t> total() {
		auto EVO_value = symbol::from_string("4,EVO").to_symbol_code().value;
		auto ETUSD_value = symbol::from_string("4,ETUSD").to_symbol_code().value;
		int64_t total_eos = balance(N(alice), 0) + balance(N(bob), 0) + balance(N(prediqtpedia), 0) + system_balance(EVO_value).at(0) + system_balance(ETUSD_value).at(0);
		int64_t total_voice = balance(N(alice), 1) + balance(N(bob), 1) + balance(N(prediqtpedia), 1) + system_balance(EVO_value).at(1);
		int64_t total_tusd = balance(N(alice), 2) + balance(N(bob), 2) + balance(N(prediqtpedia), 2) + system_balance(ETUSD_value).at(1);
		vector<int64_t> ans = {total_eos, total_voice, total_tusd};
		return ans;
	}
	void create_tokens_and_issue() {
		create(N(alice), asset::from_string("461168601842738.7903 EOS"));
		create(N(bob), asset::from_string("461168601842738.7903 VOICE"));
		create(N(alice), asset::from_string("461168601842738.7903 TUSD"));
		issue(N(alice), N(alice), asset::from_string("461168601842738.7903 EOS"), "");
		issue(N(bob), N(bob), asset::from_string("461168601842738.7903 VOICE"), "");
		issue(N(alice), N(alice), asset::from_string("461168601842738.7903 TUSD"), "");
	}
	void many_openext() {
		api.openext(N(alice), N(alice), extended_symbol{symbol::from_string("4,EOS"), N(eosio.token)});
		api.openext(N(alice), N(alice), extended_symbol{symbol::from_string("4,VOICE"), N(eosio.token)});
		api.openext(N(alice), N(alice), extended_symbol{symbol::from_string("4,TUSD"), N(eosio.token)});
		api.openext(N(bob), N(alice), extended_symbol{symbol::from_string("4,EOS"), N(eosio.token)});
		api.openext(N(bob), N(alice), extended_symbol{symbol::from_string("4,VOICE"), N(eosio.token)});
		api.openext(N(bob), N(alice), extended_symbol{symbol::from_string("4,TUSD"), N(eosio.token)});
	}
	void many_transfer() {
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("461000000000000.0000 EOS"), "");
		transfer(N(eosio.token), N(bob), N(mindswapswap), asset::from_string("461168601842738.7000 VOICE"), "deposit to: alice");
		transfer(N(eosio.token), N(bob), N(mindswapswap), asset::from_string("0.0903 VOICE"), "this goes to Bob");
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("450000000000000.0000 TUSD"), "");
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("3000000000000.0000 TUSD"), "deposit to: bob");
	}

	void prepare_carol_token() {
		push_action(N(carol), N(carol), N(create), mvo()("issuer", N(carol))("maximum_supply", asset::from_string("4.0000 EOS")));
		push_action(N(carol), N(carol), N(issue), mvo()("to", N(carol))("quantity", asset::from_string("4.0000 EOS"))("memo", ""));
		push_action(N(carol), N(carol), N(create), mvo()("issuer", N(carol))("maximum_supply", asset::from_string("1.0000 VOICE")));
		push_action(N(carol), N(carol), N(issue), mvo()("to", N(carol))("quantity", asset::from_string("1.0000 VOICE"))("memo", ""));
	}
	abi_serializer abi_ser;
};

static symbol EVO4 = symbol::from_string("4,EVO");
static symbol ETUSD4 = symbol::from_string("4,ETUSD");
static symbol EOS4 = symbol::from_string("4,EOS");
static symbol VOICE4 = symbol::from_string("4,VOICE");
static symbol TUSD4 = symbol::from_string("4,TUSD");

static symbol_code EVO = EVO4.to_symbol_code();
static symbol_code ETUSD = ETUSD4.to_symbol_code();
static symbol_code EOS = EOS4.to_symbol_code();
static symbol_code VOICE = VOICE4.to_symbol_code();
static symbol_code TUSD = TUSD4.to_symbol_code();

extended_asset extend(asset to_extend) {
	return extended_asset{to_extend, N(eosio.token)};
}

BOOST_AUTO_TEST_SUITE(mindswapswap_tests)

BOOST_FIXTURE_TEST_CASE(add_rem_exchange, mindswapswap_tester) try {
	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);

	create_tokens_and_issue();
	transfer(N(eosio.token), N(bob), N(alice), asset::from_string("500000000.0000 VOICE"), "");

	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));

	many_openext();

	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("10000000.0000 EOS"), "");
	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("200000000.0000 VOICE"), "");

	api.inittoken(N(alice),
				  EVO4,
				  extend(asset::from_string("1000000.0000 EOS")),
				  extend(asset::from_string("100000000.0000 VOICE")),
				  10,
				  N(wevotethefee));

	auto alice_evo_balance = get_balance(N(mindswapswap), N(alice), N(accounts), EVO.value, "account");
	BOOST_REQUIRE_EQUAL(alice_evo_balance["balance"].as_string(), "10000000.0000 EVO");

	// ADDLIQUIDITY
	BOOST_REQUIRE_EQUAL(error("missing authority of alice"),
						push_action(N(mindswapswap),
									N(bob),
									N(addliquidity),
									mvo()("user", N(alice))("to_buy", asset::from_string("1 EVO"))(
										"max_asset1", asset::from_string("1 EOS"))("max_asset2", asset::from_string("1 VOICE"))));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("to_buy amount must be positive"),
		api.addliquidity(
			N(alice), asset::from_string("-5.0000 EVO"), asset::from_string("0.5000 EOS"), asset::from_string("5000.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("assets must be nonnegative"),
		api.addliquidity(
			N(alice), asset::from_string("2.0000 EVO"), asset::from_string("-0.3000 EOS"), asset::from_string("30.0000 NOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("assets must be nonnegative"),
		api.addliquidity(
			N(alice), asset::from_string("2.0000 EVO"), asset::from_string("0.3000 EOS"), asset::from_string("-30.0000 NOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("available is less than expected"),
		api.addliquidity(
			N(alice), asset::from_string("5.0000 EVO"), asset::from_string("0.4999 EOS"), asset::from_string("5000.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("available is less than expected"),
		api.addliquidity(
			N(alice), asset::from_string("2.0000 EVO"), asset::from_string("0.0000 EOS"), asset::from_string("20.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("pair token does not exist"),
		api.addliquidity(
			N(alice), asset::from_string("2.0000 EMMO"), asset::from_string("0.0000 EOS"), asset::from_string("20.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("incorrect symbol"),
		api.addliquidity(
			N(alice), asset::from_string("3.0000 EVO"), asset::from_string("0.3000 ECOS"), asset::from_string("30.0001 VOICE")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("insufficient funds"),
						api.addliquidity(N(alice),
										 asset::from_string("1000000000000.0000 EVO"),
										 asset::from_string("1000000000000.0000 EOS"),
										 asset::from_string("20000000000000.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		success(),
		api.addliquidity(
			N(alice), asset::from_string("50.0000 EVO"), asset::from_string("5.0050 EOS"), asset::from_string("500.5000 VOICE")));
	produce_blocks();

	// REMLIQUIDITY
	BOOST_REQUIRE_EQUAL(error("missing authority of alice"),
						push_action(N(mindswapswap),
									N(bob),
									N(remliquidity),
									mvo()("user", N(alice))("to_sell", asset::from_string("1 EVO"))(
										"min_asset1", asset::from_string("1 EOS"))("min_asset2", asset::from_string("1 VOICE"))));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("to_sell amount must be positive"),
		api.remliquidity(
			N(alice), asset::from_string("-5.0000 EVO"), asset::from_string("0.5000 EOS"), asset::from_string("5000.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("assets must be nonnegative"),
		api.remliquidity(
			N(alice), asset::from_string("3.0000 EVO"), asset::from_string("-0.3000 EOS"), asset::from_string("30.0001 NOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("assets must be nonnegative"),
		api.remliquidity(
			N(alice), asset::from_string("3.0000 EVO"), asset::from_string("0.3000 EOS"), asset::from_string("-30.0001 NOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("available is less than expected"),
		api.remliquidity(
			N(alice), asset::from_string("1.0000 EVO"), asset::from_string("0.1001 EOS"), asset::from_string("10.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("available is less than expected"),
		api.remliquidity(
			N(alice), asset::from_string("3.0000 EVO"), asset::from_string("0.3000 EOS"), asset::from_string("30.0001 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("incorrect symbol"),
		api.remliquidity(
			N(alice), asset::from_string("3.0000 EVO"), asset::from_string("0.3000 EOS"), asset::from_string("30.0001 NOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("overdrawn balance"),
		api.remliquidity(
			N(alice), asset::from_string("1000000000000.0000 EVO"), asset::from_string("0.0000 EOS"), asset::from_string("0.0000 VOICE")));

	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("computation overflow"),
		api.addliquidity(
			N(alice), asset::from_string("46116860184273.8791 EVO"), asset::from_string("1.0000 EOS"), asset::from_string("1.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("computation underflow"),
		api.remliquidity(
			N(alice), asset::from_string("46116860184273.8791 EVO"), asset::from_string("1.0000 EOS"), asset::from_string("1.0000 VOICE")));

	BOOST_REQUIRE_EQUAL(
		success(),
		api.remliquidity(
			N(alice), asset::from_string("10000050.0000 EVO"), asset::from_string("0.0001 EOS"), asset::from_string("0.0001 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("pair token does not exist"),
		api.addliquidity(
			N(alice), asset::from_string("2.0000 EVO"), asset::from_string("0.0000 EOS"), asset::from_string("20.0000 VOICE")));

	// set pools
	api.inittoken(N(alice),
				  EVO4,
				  extend(asset::from_string("1000000.0000 EOS")),
				  extend(asset::from_string("100000000.0000 VOICE")),
				  10,
				  N(wevotethefee));
	api.addliquidity(N(alice), asset::from_string("50.0000 EVO"), asset::from_string("5.0050 EOS"), asset::from_string("500.5000 VOICE"));
	api.remliquidity(N(alice), asset::from_string("17.1872 EVO"), asset::from_string("0.0000 EOS"), asset::from_string("0.0000 VOICE"));

	// EXCHANGE
	BOOST_REQUIRE_EQUAL(error("missing authority of alice"),
						push_action(N(mindswapswap),
									N(bob),
									N(exchange),
									mvo()("user", N(alice))("pair_token", EVO)("ext_asset_in", extend(asset::from_string("1 EOS")))(
										"min_expected", asset::from_string("1 VOICE"))));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("ext_asset_in must be nonzero and min_expected must have same sign or be zero"),
						api.exchange(N(alice), EVO, extend(asset::from_string("2.0000 VOICE")), asset::from_string("-0.1000 EOS")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("ext_asset_in must be nonzero and min_expected must have same sign or be zero"),
						api.exchange(N(alice), EVO, extend(asset::from_string("-2.0000 RICE")), asset::from_string("0.1000 REOS")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("ext_asset_in must be nonzero and min_expected must have same sign or be zero"),
						api.exchange(N(alice), EVO, extend(asset::from_string("0.0000 EOS")), asset::from_string("-0.1000 VOICE")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("invalid parameters"),
						api.exchange(N(alice), EVO, extend(asset::from_string("-1000004.0000 EOS")), asset::from_string("-0.0001 VOICE")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("invalid parameters"),
						api.exchange(N(alice), EVO, extend(asset::from_string("-100000328.6280 VOICE")), asset::from_string("0.0000 EOS")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("pair token does not exist"),
						api.exchange(N(alice), TUSD, extend(asset::from_string("-8.0000 VOICE")), asset::from_string("0.0000 EOS")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("extended_symbol mismatch"),
						api.exchange(N(alice), EVO, extend(asset::from_string("4.000 EOS")), asset::from_string("10.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("extended_symbol mismatch"),
		api.exchange(N(alice), EVO, extended_asset{asset::from_string("4.0000 EOS"), N(another)}, asset::from_string("10.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("extended_symbol mismatch"),
		api.exchange(N(alice), EVO, extended_asset{asset::from_string("1.0000 VOICE"), N(another)}, asset::from_string("1.0000 EOS")));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("available is less than expected"),
						api.exchange(N(alice), EVO, extend(asset::from_string("4.0000 EOS")), asset::from_string("400.0000 VOICE")));

	api.exchange(N(alice), EVO, extend(asset::from_string("4.0000 EOS")), asset::from_string("10.0000 VOICE"));
	api.exchange(N(alice), EVO, extend(asset::from_string("0.1000 EOS")), asset::from_string("4.8500 VOICE"));
	api.exchange(N(alice), EVO, extend(asset::from_string("0.0001 EOS")), asset::from_string("0.0009 VOICE"));

	vector<int64_t> expected_system_balance = {10000073814, 999999184889, 100000328128};
	// vector<int64_t> expected_system_balance = {10000073814, 999999185299, 100000328128}; before adding dev fee (-410 for VOICE)
	// vector<int64_t> expected_system_balance = {10000073864, 999999190299, 100000328128}; before remove fees
	BOOST_REQUIRE_EQUAL(expected_system_balance == system_balance(EVO.value), true);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 0), 89999926186);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 1), 1000000814701);
	BOOST_REQUIRE_EQUAL(balance(N(prediqtpedia), 1), 410);

	BOOST_REQUIRE_EQUAL(success(), api.changefee(EVO, 50));

	api.addliquidity(
		N(alice), asset::from_string("50.0000 EVO"), asset::from_string("10000000.0000 EOS"), asset::from_string("10000000.0000 VOICE"));

	expected_system_balance = {10000123815, 1000004184869, 100000828128};
	// expected_system_balance = {10000123815, 1000004185279, 100000828128}; before adding dev fee (-410 for VOICE)
	// expected_system_balance = {10000124116, 1000004215279, 100000828128}; before remove fees
	BOOST_REQUIRE_EQUAL(expected_system_balance == system_balance(EVO.value), true);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 0), 89999876185);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 1), 999995814721);
	BOOST_REQUIRE_EQUAL(balance(N(prediqtpedia), 1), 410);
}
FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(increasing_parameter, mindswapswap_tester) try {
	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);

	create_tokens_and_issue();
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	many_openext();
	many_transfer();
	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("168601842738.7903 EOS"), "");

	api.inittoken(N(alice),
				  EVO4,
				  extend(asset::from_string("23058430092.1369 EOS")),
				  extend(asset::from_string("96116860184.2738 VOICE")),
				  10,
				  N(wevotethefee));

	api.inittoken(N(alice),
				  ETUSD4,
				  extend(asset::from_string("10000000000.0000 EOS")),
				  extend(asset::from_string("99116860184.2738 TUSD")),
				  10,
				  N(wevotethefee));

	auto old_total = total();
	auto old_vec = system_balance(EVO.value);
	auto old_alice_bal_0 = balance(N(alice), 0);
	auto old_alice_bal_1 = balance(N(alice), 1);

	BOOST_REQUIRE_EQUAL(success(),
						api.exchange(N(alice), EVO, extend(asset::from_string("4.0000 EOS")), asset::from_string("1.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 0) - old_alice_bal_0, -40000);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 1) - old_alice_bal_1, 166569);

	old_total = total();
	old_vec = system_balance(EVO.value);
	old_alice_bal_0 = balance(N(alice), 0);
	old_alice_bal_1 = balance(N(alice), 1);
	api.addliquidity(
		N(alice), asset::from_string("0.0001 EVO"), asset::from_string("10000000.0000 EOS"), asset::from_string("10000000.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 0) - old_alice_bal_0, -1); // TODO: check, it was -2
	BOOST_REQUIRE_EQUAL(balance(N(alice), 1) - old_alice_bal_1, -3); // TODO: check, it was -4

	produce_blocks();

	old_total = total();
	old_vec = system_balance(EVO.value);
	old_alice_bal_0 = balance(N(alice), 0);
	old_alice_bal_1 = balance(N(alice), 1);
	api.remliquidity(N(alice), asset::from_string("0.0001 EVO"), asset::from_string("0.0000 EOS"), asset::from_string("0.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 0) - old_alice_bal_0, 0);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 1) - old_alice_bal_1, 2);

	old_total = total();
	old_vec = system_balance(ETUSD.value);
	BOOST_REQUIRE_EQUAL(
		success(),
		api.exchange(N(bob), ETUSD, extend(asset::from_string("30000000000.0000 TUSD")), asset::from_string("40000000.0000 EOS")));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(ETUSD.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	BOOST_REQUIRE_EQUAL(success(),
						api.exchange(N(bob), EVO, extend(asset::from_string("4000.0000 EOS")), asset::from_string("1.0000 VOICE")));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	BOOST_REQUIRE_EQUAL(success(), api.withdraw(N(bob), N(bob), extend(asset::from_string("0.0001 EOS"))));
	BOOST_REQUIRE_EQUAL(old_total == total(), false);

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.addliquidity(N(alice),
					 asset::from_string("150000000000000.0000 EVO"),
					 asset::from_string("400000000000000.0000 EOS"),
					 asset::from_string("400000000000000.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.exchange(N(alice), EVO, extend(asset::from_string("387592687324317.3478 EOS")), asset::from_string("0.0001 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("available is less than expected"),
						api.exchange(N(alice), EVO, extend(asset::from_string("-1.0000 EOS")), asset::from_string("-0.1066 VOICE")));
	BOOST_REQUIRE_EQUAL(success(),
						api.exchange(N(alice), EVO, extend(asset::from_string("-1.0000 EOS")), asset::from_string("-0.1068 VOICE")));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("available is less than expected"),
						api.exchange(N(bob), EVO, extend(asset::from_string("-13.0001 VOICE")), asset::from_string("-122.3014 EOS")));
	BOOST_REQUIRE_EQUAL(success(),
						api.exchange(N(bob), EVO, extend(asset::from_string("-13.0001 VOICE")), asset::from_string("-122.3015 EOS"))); // 122.2374 EOS before dev fee
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);
}
FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(test_dev_fee, mindswapswap_tester) try {
	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);

	create_tokens_and_issue();
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	many_openext();
	many_transfer();
	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("205.0000 EOS"), "");

	api.inittoken(
		N(alice), EVO4, extend(asset::from_string("100.0000 EOS")), extend(asset::from_string("100.0000 VOICE")), 70, N(wevotethefee));

	api.inittoken(
		N(alice), ETUSD4, extend(asset::from_string("1.0000 EOS")), extend(asset::from_string("1.0000 TUSD")), 0, N(wevotethefee));

	auto old_total = total();

	auto old_vec = system_balance(EVO.value);
	auto old_alice_bal_0 = balance(N(alice), 0);
	auto old_alice_bal_1 = balance(N(alice), 1);
	BOOST_REQUIRE_EQUAL(balance(N(prediqtpedia), 1), 0);
	BOOST_REQUIRE_EQUAL(success(),
						api.exchange(N(alice), EVO, extend(asset::from_string("4.0000 EOS")), asset::from_string("3.8000 VOICE")));
	BOOST_REQUIRE_EQUAL(balance(N(prediqtpedia), 1), 27);
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	auto new_vec = system_balance(EVO.value);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, new_vec), true);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 0) - old_alice_bal_0, -40000);
	BOOST_REQUIRE_EQUAL(balance(N(alice), 1) - old_alice_bal_1, 38191);

	vector<int64_t> expected_system_balance = {1040000, 961782, 1000000}; // TODO: 961809 - 27 fee that goes to prediqtpedia
	BOOST_REQUIRE_EQUAL(expected_system_balance == new_vec, true);
	BOOST_REQUIRE_EQUAL(success(), api.resetext(N(prediqtpedia), N(prediqtpedia), extended_symbol{VOICE4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(balance(N(prediqtpedia), 1), 0);
}
FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(increasing_parameter_zero_fee, mindswapswap_tester) try {
	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);

	create_tokens_and_issue();

	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));

	many_openext();
	many_transfer();
	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("168601842738.7903 EOS"), "");

	api.inittoken(N(alice),
				  EVO4,
				  extend(asset::from_string("23058430092.1369 EOS")),
				  extend(asset::from_string("96116860184.2738 VOICE")),
				  0,
				  N(wevotethefee));

	api.inittoken(N(alice),
				  ETUSD4,
				  extend(asset::from_string("10000000000.0000 EOS")),
				  extend(asset::from_string("99116860184.2738 TUSD")),
				  0,
				  N(wevotethefee));

	auto old_total = total();
	auto old_vec = system_balance(EVO.value);
	api.exchange(N(alice), EVO, extend(asset::from_string("4.0000 EOS")), asset::from_string("1.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.addliquidity(
		N(alice), asset::from_string("0.0001 EVO"), asset::from_string("10000000.0000 EOS"), asset::from_string("10000000.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	produce_blocks();

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.remliquidity(N(alice), asset::from_string("0.0001 EVO"), asset::from_string("0.0000 EOS"), asset::from_string("0.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	old_vec = system_balance(ETUSD.value);
	api.exchange(N(bob), ETUSD, extend(asset::from_string("30000000000.0000 TUSD")), asset::from_string("40000000.0000 EOS"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(ETUSD.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.exchange(N(bob), EVO, extend(asset::from_string("4000.0000 EOS")), asset::from_string("1.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	api.withdraw(N(bob), N(bob), extend(asset::from_string("0.0001 EOS")));
	BOOST_REQUIRE_EQUAL(old_total == total(), false);

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.addliquidity(N(alice),
					 asset::from_string("150000000000000.0000 EVO"),
					 asset::from_string("400000000000000.0000 EOS"),
					 asset::from_string("400000000000000.0000 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);

	old_total = total();
	old_vec = system_balance(EVO.value);
	api.exchange(N(alice), EVO, extend(asset::from_string("387592687324317.3478 EOS")), asset::from_string("0.0001 VOICE"));
	BOOST_REQUIRE_EQUAL(old_total == total(), true);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, system_balance(EVO.value)), true);
}
FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(memoexchange_test, mindswapswap_tester) try {
	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);

	create_tokens_and_issue();
	prepare_carol_token();
	transfer(N(eosio.token), N(bob), N(alice), asset::from_string("0.0001 VOICE"), "");

	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));

	many_openext();
	many_transfer();

	BOOST_REQUIRE_EQUAL(success(),
						api.inittoken(N(alice),
									  EVO4,
									  extend(asset::from_string("23058430092.1369 EOS")),
									  extend(asset::from_string("96116860184.2738 VOICE")),
									  12,
									  N(wevotethefee)));

	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("extended_symbol mismatch"),
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("4.0000 EOS"), "exchange: EVO, 166536 VOICE"));

	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("available is less than expected"),
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("4.0000 EOS"), "exchange: EVO, 16.6536 VOICE"));

	BOOST_REQUIRE_EQUAL(wasm_assert_msg("extended_symbol mismatch"),
						transfer(N(carol), N(carol), N(mindswapswap), asset::from_string("4.0000 EOS"), "exchange: EVO, 16.6535 VOICE"));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("extended_symbol mismatch"),
						transfer(N(carol), N(carol), N(mindswapswap), asset::from_string("1.0000 VOICE"), "exchange: EVO, 0.0001 EOS"));

	int64_t pre_eos_balance = token_balance(N(eosio.token), N(alice), EOS.value);
	int64_t pre_voice_balance = token_balance(N(eosio.token), N(alice), VOICE.value);
	BOOST_REQUIRE_EQUAL(
		success(), transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("4.0000 EOS"), "exchange: EVO, 16.6535 VOICE"));
	BOOST_REQUIRE_EQUAL(pre_eos_balance - 40000, token_balance(N(eosio.token), N(alice), EOS.value));
	BOOST_REQUIRE_EQUAL(pre_voice_balance + 166535, token_balance(N(eosio.token), N(alice), VOICE.value));

	api.inittoken(N(alice),
				  ETUSD4,
				  extend(asset::from_string("10000000000.0000 EOS")),
				  extend(asset::from_string("99116860184.2738 TUSD")),
				  0,
				  N(wevotethefee));

	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("available is less than expected"),
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("4000.0000 TUSD"), "exchange: ETUSD, 403.5641 EOS"));

	pre_eos_balance = token_balance(N(eosio.token), N(alice), EOS.value);
	int64_t pre_tusd_balance = token_balance(N(eosio.token), N(alice), TUSD.value);
	BOOST_REQUIRE_EQUAL(
		success(),
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("4000.0000 TUSD"), "exchange: ETUSD, 403.5639 EOS"));

	BOOST_REQUIRE_EQUAL(pre_tusd_balance - 40000000, token_balance(N(eosio.token), N(alice), TUSD.value));
	BOOST_REQUIRE_EQUAL(pre_eos_balance + 4035640, token_balance(N(eosio.token), N(alice), EOS.value));

	auto old_vec = system_balance(EVO.value);
	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("4.0000 EOS"), "exchange: EVO, 10000 VOICE, nothing to say");
	auto new_vec = system_balance(EVO.value);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, new_vec), true);

	old_vec = system_balance(ETUSD.value);
	transfer(
		N(eosio.token), N(bob), N(mindswapswap), asset::from_string("300000000000000.0000 TUSD"), "exchange: ETUSD, 40000000000000 EOS,");
	new_vec = system_balance(ETUSD.value);
	BOOST_REQUIRE_EQUAL(is_increasing(old_vec, new_vec), true);
}
FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(the_other_actions, mindswapswap_tester) try {

	create_tokens_and_issue();
	transfer(N(eosio.token), N(bob), N(alice), asset::from_string("500000000.0000 VOICE"), "");

	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));

	// add_signed_ext_balance
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("extended_symbol not registered for this user,\
 please run openext action or write exchange details in the memo of your transfer"),
						transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("0.1000 EOS"), ""));

	// OPENEXT
	BOOST_REQUIRE_EQUAL(error("missing authority of natalia"),
						push_action(N(mindswapswap),
									N(bob),
									N(openext),
									mvo()("user", N(bob))("payer", N(natalia))("ext_symbol", extended_symbol{VOICE4, N(eosio.token)})));
	BOOST_REQUIRE_EQUAL(success(), api.openext(N(alice), N(alice), extended_symbol{EOS4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(success(), api.openext(N(alice), N(alice), extended_symbol{VOICE4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(success(), api.openext(N(alice), N(alice), extended_symbol{VOICE4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("user account does not exist"),
						api.openext(N(cat), N(alice), extended_symbol{VOICE4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(success(), api.openext(N(alice), N(alice), extended_symbol{VOICE4, N(eosio.token)}));

	// ONTRANSFER
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("This transfer is not for mindswapswap"),
						transfer(N(badtoken), N(alice), N(bob), asset::from_string("1000.0000 EOS"), ""));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("quantity must be positive"),
						transfer(N(badtoken), N(alice), N(mindswapswap), asset::from_string("-1000.0000 EOS"), ""));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("Donation not accepted"),
		transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("1000.0000 EOS"), "deposit to: mindswapswap"));
	BOOST_REQUIRE_EQUAL(success(), transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("1000.0000 EOS"), ""));
	BOOST_REQUIRE_EQUAL(success(), transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("20000.0000 VOICE"), ""));

	// WITHDRAW
	BOOST_REQUIRE_EQUAL(
		error("missing authority of alice"),
		push_action(N(mindswapswap),
					N(bob),
					N(withdraw),
					mvo()("user", N(alice))("to", N(natalia))("to_withdraw", extend(asset::from_string("1.0000 EOS")))("memo", "")));
	BOOST_REQUIRE_EQUAL(success(), api.withdraw(N(alice), N(bob), extend(asset::from_string("999.9998 EOS"))));
	BOOST_REQUIRE_EQUAL(9999998, token_balance(N(eosio.token), N(bob), EOS.value));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("quantity must be positive"),
						api.withdraw(N(alice), N(bob), extend(asset::from_string("-0.0001 EOS"))));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("insufficient funds"), api.withdraw(N(alice), N(bob), extend(asset::from_string("0.0003 EOS"))));

	// INITTOKEN
	BOOST_REQUIRE_EQUAL(error("missing authority of alice"),
						push_action(N(mindswapswap),
									N(bob),
									N(inittoken),
									mvo()("user", N(alice))("new_symbol", EVO4)("initial_pool1", extend(asset::from_string("1.0000 EOS")))(
										"initial_pool2",
										extend(asset::from_string("1.0000 ECO")))("initial_fee", 1)("fee_contract", N(carol))("curve", 1)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("Both assets must be positive"),
		api.inittoken(
			N(alice), EVO4, extend(asset::from_string("-0.0001 EOS")), extend(asset::from_string("0.1000 VOICE")), 10, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("Both assets must be positive"),
		api.inittoken(
			N(alice), EVO4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("-0.1000 VOICE")), 10, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("Initial amounts must be less than 10^15"),
						api.inittoken(N(alice),
									  EVO4,
									  extend(asset::from_string("0.0001 EOS")),
									  extend(asset::from_string("100000000000.0001 VOICE")),
									  10,
									  N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("Initial amounts must be less than 10^15"),
						api.inittoken(N(alice),
									  EVO4,
									  extend(asset::from_string("100000000000.0001 EOS")),
									  extend(asset::from_string("1.0001 VOICE")),
									  10,
									  N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("extended symbols must be different"),
		api.inittoken(
			N(alice), EVO4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.1000 EOS")), 10, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("insufficient funds"),
		api.inittoken(
			N(alice), ETUSD4, extend(asset::from_string("0.0003 EOS")), extend(asset::from_string("0.1000 VOICE")), 10, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("insufficient funds"),
						api.inittoken(N(alice),
									  ETUSD4,
									  extend(asset::from_string("0.0002 EOS")),
									  extend(asset::from_string("100000000.0000 VOICE")),
									  10,
									  N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		success(),
		api.inittoken(
			N(alice), EVO4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.1000 VOICE")), 10, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("token symbol already exists"),
		api.inittoken(
			N(alice), EVO4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.1000 VOICE")), 10, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("initial fee out of reasonable range"),
		api.inittoken(
			N(alice), ETUSD4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.1000 VOICE")), 501, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("initial fee out of reasonable range"),
		api.inittoken(
			N(alice), ETUSD4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.1000 VOICE")), -1, N(wevotethefee)));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("fee_contract account must exist or be empty"),
		api.inittoken(
			N(alice), ETUSD4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.1000 VOICE")), 1, N(nonexistent)));
	BOOST_REQUIRE_EQUAL(
		success(),
		api.inittoken(N(alice), ETUSD4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.0001 VOICE")), 1, N()));

	// behaviour when fee_contract is empty
	BOOST_REQUIRE_EQUAL(success(), transfer(N(mindswapswap), N(alice), N(bob), asset::from_string("0.0001 ETUSD"), ""));
	BOOST_REQUIRE_EQUAL(success(), transfer(N(mindswapswap), N(bob), N(alice), asset::from_string("0.0001 ETUSD"), ""));
	BOOST_REQUIRE_EQUAL(error("missing authority of mindswapswap"), api.changefee(ETUSD, 20));
	BOOST_REQUIRE_EQUAL(
		success(),
		api.remliquidity(
			N(alice), asset::from_string("0.0001 ETUSD"), asset::from_string("0.0001 EOS"), asset::from_string("0.0001 VOICE")));

	// TRANSFER
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("extended_symbol not registered for this user,\
 please run openext action or write exchange details in the memo of your transfer"),
						transfer(N(mindswapswap), N(alice), N(mindswapswap), asset::from_string("0.0010 EVO"), ""));

	// CLOSEEXT
	BOOST_REQUIRE_EQUAL(
		error("missing authority of natalia"),
		push_action(N(mindswapswap),
					N(bob),
					N(closeext),
					mvo()("user", N(natalia))("ext_symbol", extended_symbol{EVO4, N(eosio.token)})("to", N(alice))("memo", "")));
	BOOST_REQUIRE_EQUAL(success(), api.closeext(N(alice), N(alice), extended_symbol{VOICE4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(success(), api.closeext(N(alice), N(bob), extended_symbol{EOS4, N(eosio.token)}));
	BOOST_REQUIRE_EQUAL(9999999, token_balance(N(eosio.token), N(bob), EOS.value));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("User does not have such token"),
						api.closeext(N(alice), N(bob), extended_symbol{EOS4, N(eosio.token)}));

	// CHANGEFEE
	BOOST_REQUIRE_EQUAL(error("missing authority of wevotethefee"),
						push_action(N(mindswapswap), N(bob), N(changefee), mvo()("pair_token", EVO)("newfee", 50)));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("pair token does not exist"), api.changefee(EOS, 500));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("new fee out of reasonable range"), api.changefee(EVO, 501));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("new fee out of reasonable range"), api.changefee(EVO, -5));
	BOOST_REQUIRE_EQUAL(success(), api.changefee(EVO, 50));

	// Notifications to fee_contract
	many_openext();
	transfer(N(eosio.token), N(bob), N(mindswapswap), asset::from_string("0.0002 EOS"), "");
	transfer(N(eosio.token), N(bob), N(mindswapswap), asset::from_string("0.0002 VOICE"), "");
	BOOST_REQUIRE_EQUAL(
		success(),
		api.inittoken(
			N(bob), ETUSD4, extend(asset::from_string("0.0001 EOS")), extend(asset::from_string("0.0001 VOICE")), 0, N(badtoken)));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("notification received"),
						transfer(N(mindswapswap), N(bob), N(alice), asset::from_string("0.0001 ETUSD"), ""));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("notification received"),
		api.addliquidity(N(bob), asset::from_string("0.0001 ETUSD"), asset::from_string("0.0001 EOS"), asset::from_string("0.0001 VOICE")));
	BOOST_REQUIRE_EQUAL(
		wasm_assert_msg("notification received"),
		api.remliquidity(N(bob), asset::from_string("0.0001 ETUSD"), asset::from_string("0.0001 EOS"), asset::from_string("0.0001 VOICE")));
}
FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(we_vote_the_fee, mindswapswap_tester) try {

	create_tokens_and_issue();
	transfer(N(eosio.token), N(bob), N(alice), asset::from_string("500000000.0000 VOICE"), "");

	const auto& accnt2 = control->db().get<account_object, by_name>(N(mindswapswap));
	abi_def abi_evo;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt2.abi, abi_evo), true);
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));

	many_openext();

	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("10000000.0000 EOS"), "");
	transfer(N(eosio.token), N(alice), N(mindswapswap), asset::from_string("200000000.0000 VOICE"), "");

	api.inittoken(N(alice),
				  EVO4,
				  extend(asset::from_string("1000000.0000 EOS")),
				  extend(asset::from_string("100000000.0000 VOICE")),
				  10,
				  N(wevotethefee));

	const auto& accnt3 = control->db().get<account_object, by_name>(N(wevotethefee));
	abi_def abi_wevote;
	BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt3.abi, abi_wevote), true);

	// Start wevotethefee actions. Testing checks and basic functions.
	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	auto ISNT = symbol::from_string("4,ISNT").to_symbol_code();
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("pair_token balance does not exist"), votefee(N(alice), ISNT, 30));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("pair_token balance does not exist"), votefee(N(bob), EVO, 30));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("fee table nonexistent, run openfeetable"), votefee(N(alice), EVO, 30));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("fee table nonexistent, run openfeetable"), updatefee(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("table does not exist"), closefeetable(EVO));
	BOOST_REQUIRE_EQUAL(success(), openfeetable(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(success(), closefeetable(EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("fee table nonexistent, run openfeetable"), votefee(N(alice), EVO, 30));
	BOOST_REQUIRE_EQUAL(success(), openfeetable(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("already opened"), openfeetable(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("there are no votes"), updatefee(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("user is not voting"), closevote(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("only values between 1 and 300 allowed"), votefee(N(alice), EVO, 301));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("only values between 1 and 300 allowed"), votefee(N(alice), EVO, -10));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("only values between 1 and 300 allowed"), votefee(N(alice), EVO, 0));

	BOOST_REQUIRE_EQUAL(success(), votefee(N(alice), EVO, 30));
	BOOST_REQUIRE_EQUAL(success(), updatefee(N(alice), EVO));
	BOOST_REQUIRE_EQUAL(wasm_assert_msg("table of votes is not empty"), closefeetable(EVO));

	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	auto evo_stats = get_balance(N(mindswapswap), name(EVO.value), N(stat), EVO.value, "currency_stats");
	BOOST_REQUIRE_EQUAL(30, evo_stats["fee"]);

	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	auto evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	BOOST_REQUIRE_EQUAL(100000000000, evo_votes[8]);

	// Test authorizations
	BOOST_REQUIRE_EQUAL(error("missing authority of bob"),
						push_action(N(wevotethefee), N(alice), N(votefee), mvo()("user", N(bob))("pair_token", EVO)("fee_voted", "10")));
	BOOST_REQUIRE_EQUAL(error("missing authority of bob"),
						push_action(N(wevotethefee), N(alice), N(closevote), mvo()("user", N(bob))("pair_token", EVO)));

	// Vote balance change after transfer
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	BOOST_REQUIRE_EQUAL(success(), transfer(N(mindswapswap), N(alice), N(bob), asset::from_string("100.0000 EVO"), ""));

	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	BOOST_REQUIRE_EQUAL(success(), votefee(N(bob), EVO, 300));
	evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	BOOST_REQUIRE_EQUAL(99999000000, evo_votes[8]);
	BOOST_REQUIRE_EQUAL(1000000, evo_votes[14]);

	// Scenarios with many votes
	create_accounts({N(dan), N(eva), N(fran)});
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	BOOST_REQUIRE_EQUAL(success(), transfer(N(mindswapswap), N(alice), N(carol), asset::from_string("2000.0000 EVO"), ""));
	transfer(N(mindswapswap), N(alice), N(dan), asset::from_string("400000.0000 EVO"), "");
	transfer(N(mindswapswap), N(alice), N(eva), asset::from_string("4240000.0000 EVO"), "");
	transfer(N(mindswapswap), N(alice), N(fran), asset::from_string("2500000.0000 EVO"), "");

	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	BOOST_REQUIRE_EQUAL(success(), votefee(N(carol), EVO, 1));
	votefee(N(dan), EVO, 2);
	votefee(N(eva), EVO, 99);
	votefee(N(fran), EVO, 108);

	updatefee(N(alice), EVO);
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_stats = get_balance(N(mindswapswap), name(EVO.value), N(stat), EVO.value, "currency_stats");
	BOOST_REQUIRE_EQUAL(100, evo_stats["fee"]);

	api.openext(N(eva), N(eva), extended_symbol{symbol::from_string("4,EOS"), N(eosio.token)});
	api.openext(N(eva), N(eva), extended_symbol{symbol::from_string("4,VOICE"), N(eosio.token)});
	push_action(N(mindswapswap),
				N(eva),
				N(remliquidity),
				mvo()("user", N(eva))("to_sell", asset::from_string("4000000.0000 EVO"))("min_asset1", asset::from_string("1.0000 EOS"))(
					"min_asset2", asset::from_string("1.0000 VOICE")));

	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	updatefee(N(alice), EVO);
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_stats = get_balance(N(mindswapswap), name(EVO.value), N(stat), EVO.value, "currency_stats");
	BOOST_REQUIRE_EQUAL(30, evo_stats["fee"]);

	BOOST_REQUIRE_EQUAL(success(),
						push_action(N(mindswapswap),
									N(eva),
									N(addliquidity),
									mvo()("user", N(eva))("to_buy", asset::from_string("3900000.0000 EVO"))(
										"max_asset1", asset::from_string("100000000000.0000 EOS"))(
										"max_asset2", asset::from_string("100000000000.0000 VOICE"))));
	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	updatefee(N(alice), EVO);
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_stats = get_balance(N(mindswapswap), name(EVO.value), N(stat), EVO.value, "currency_stats");
	BOOST_REQUIRE_EQUAL(100, evo_stats["fee"]);

	// median 1
	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	votefee(N(alice), EVO, 1);
	votefee(N(dan), EVO, 1);
	votefee(N(eva), EVO, 1);
	votefee(N(fran), EVO, 1);
	updatefee(N(alice), EVO);
	evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	BOOST_REQUIRE_EQUAL(98999000000, evo_votes[0]);
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_stats = get_balance(N(mindswapswap), name(EVO.value), N(stat), EVO.value, "currency_stats");
	BOOST_REQUIRE_EQUAL(1, evo_stats["fee"]);

	// check votes after transfer, remliquidity and addliquidity
	BOOST_REQUIRE_EQUAL(success(), transfer(N(mindswapswap), N(alice), N(bob), asset::from_string("100.0000 EVO"), ""));
	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	BOOST_REQUIRE_EQUAL(98998000000, evo_votes[0]);
	BOOST_REQUIRE_EQUAL(2000000, evo_votes[14]);

	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	push_action(N(mindswapswap),
				N(eva),
				N(remliquidity),
				mvo()("user", N(eva))("to_sell", asset::from_string("10000.0000 EVO"))("min_asset1", asset::from_string("1.0000 EOS"))(
					"min_asset2", asset::from_string("1.0000 VOICE")));
	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	BOOST_REQUIRE_EQUAL(98998000000 - 100000000, evo_votes[0]);

	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	push_action(N(mindswapswap),
				N(eva),
				N(addliquidity),
				mvo()("user", N(eva))("to_buy", asset::from_string("100.0000 EVO"))("max_asset1", asset::from_string("10000000.0000 EOS"))(
					"max_asset2", asset::from_string("10000000.0000 VOICE")));
	abi_ser.set_abi(abi_wevote, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	BOOST_REQUIRE_EQUAL(98998000000 - 100000000 + 1000000, evo_votes[0]);

	// median 300
	votefee(N(dan), EVO, 300);
	votefee(N(eva), EVO, 300);
	votefee(N(fran), EVO, 300);
	updatefee(N(alice), EVO);
	evo_votes = get_balance(N(wevotethefee), name(EVO.value), N(feetable), EVO.value, "feetable")["votes"].get_array();
	abi_ser.set_abi(abi_evo, abi_serializer::create_yield_function(fc::microseconds{1000 * 1000}));
	evo_stats = get_balance(N(mindswapswap), name(EVO.value), N(stat), EVO.value, "currency_stats");
	BOOST_REQUIRE_EQUAL(300, evo_stats["fee"]);
}
FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_SUITE_END()


#include <Core/Animation/PoseOperation.hpp>
#include <Core/Math/Interpolation.hpp>

#include <Eigen/Geometry>

namespace Ra {
namespace Core {
namespace Animation {

bool compatible( const Pose& p0, const Pose& p1 ) {
    return ( p0.size() == p1.size() );
}

Pose relativePose( const Pose& modelPose, const RestPose& restPose ) {
    CORE_ASSERT( compatible( modelPose, restPose ), " Poses with different size " );
    Pose T( restPose.size() );
#pragma omp parallel for
    for ( int i = 0; i < int( T.size() ); ++i )
    {
        T[i] = modelPose[i] * restPose[i].inverse( Eigen::Affine );
    }
    return T;
}

Pose applyTransformation( const Pose& pose, const AlignedStdVector<Transform>& transform ) {
    Pose T( std::min( pose.size(), transform.size() ) );
#pragma omp parallel for
    for ( int i = 0; i < int( T.size() ); ++i )
    {
        T[i] = transform[i] * pose[i];
    }
    return T;
}

Pose applyTransformation( const Pose& pose, const Transform& transform ) {
    Pose T( pose.size() );
#pragma omp parallel for
    for ( int i = 0; i < int( T.size() ); ++i )
    {
        T[i] = transform * pose[i];
    }
    return T;
}

bool areEqual( const Pose& p0, const Pose& p1 ) {
    CORE_ASSERT( compatible( p0, p1 ), " Poses with different size " );
    const uint n = p0.size();
    for ( uint i = 0; i < n; ++i )
    {
        if ( !p0[i].isApprox( p1[i] ) ) { return false; }
    }
    return true;
}

Pose interpolatePoses( const Pose& a, const Pose& b, const Scalar t ) {
    CORE_ASSERT( ( a.size() == b.size() ), "Poses are wrong" );
    CORE_ASSERT( ( ( t >= Scalar( 0. ) ) && ( t <= Scalar( 1. ) ) ), "T is wrong" );

    const uint size = a.size();
    Pose interpolatedPose( size );

#pragma omp parallel for
    for ( int i = 0; i < int( size ); ++i )
    {
        interpolatedPose[i] = Math::linearInterpolate( a[i], b[i], t );
    }

    return interpolatedPose;
}

} // namespace Animation
} // namespace Core
} // namespace Ra


/*
  Copyright 2010 Larry Gritz and the other authors and contributors.
  All Rights Reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:
  * Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
  * Neither the name of the software's owners nor the names of its
    contributors may be used to endorse or promote products derived from
    this software without specific prior written permission.
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

  (This is the Modified BSD License)
*/

#include <cmath>
#include <vector>
#include <string>

#include <boost/algorithm/string.hpp>
using boost::algorithm::iequals;

#include "colortransfer.h"

OIIO_NAMESPACE_ENTER
{

bool
ColorTransfer::set (std::string name_, float param)
{
    return false;
}

bool
ColorTransfer::get (std::string name_, float &param)
{
    return false;
}

namespace {  // anonymous

/// Null (pass-thru) transfer function
///
class ColorTransfer_null : public ColorTransfer {
public:
    ColorTransfer_null () : ColorTransfer("null") { };
    ~ColorTransfer_null (void) { };
    float operator() (float x) { return x; }
};

/// Gamma transfer function
///
class ColorTransfer_gamma : public ColorTransfer {
public:
    ColorTransfer_gamma (float gamma=2.2f, float gain=1.0f)
        : ColorTransfer("gamma"), m_gamma(gamma), m_gain(gain)
    {
        add_paramater ("gamma");
        add_paramater ("gain");
    };

    ~ColorTransfer_gamma (void) { };

    bool set (std::string name, float param) {
        if (iequals (name, "gamma"))
            m_gamma = param;
        else if (iequals (name, "gain"))
            m_gain = param;
        else return false;
        return true;
    };

    bool get (std::string name, float &param) {
        if (iequals (name, "gamma"))
            param = m_gamma;
        else if (iequals (name, "gain"))
            param = m_gain;
        else return false;
        return true;
    };

    float operator() (float x) {
        if (x < 0.0f)
            return m_gain * x;
        return std::pow (m_gain * x, m_gamma);
    };

private:
    float m_gamma;
    float m_gain;
};

/// sRGB transfer function which is a widely used computer monitor standard
///    http://en.wikipedia.org/wiki/SRGB
class ColorTransfer_linear_to_sRGB : public ColorTransfer {
public:
    ColorTransfer_linear_to_sRGB () : ColorTransfer("linear_to_sRGB") { };
    ~ColorTransfer_linear_to_sRGB (void) { };

    float operator() (float x) {
        if (x < 0.0f)
            return 0.0f;
        return (x <= 0.0031308f) ? (12.92f * x)
                                 : (1.055f * std::pow (x, 1.f/2.4f) - 0.055f);
    }
};

/// sRGB transfer function which is a widely used computer monitor standard
///    http://en.wikipedia.org/wiki/SRGB
class ColorTransfer_sRGB_to_linear : public ColorTransfer {
public:
    ColorTransfer_sRGB_to_linear () : ColorTransfer("sRGB_to_linear") { };
    ~ColorTransfer_sRGB_to_linear (void) { };

    float operator() (float x) {
        return (x <= 0.04045f) ? (x / 12.92f)
                               : std::pow ((x + 0.055f) / 1.055f, 2.4f);
    }
};

/// AdobeRGB transfer function
///    http://en.wikipedia.org/wiki/Adobe_RGB
///    http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
class ColorTransfer_AdobeRGB_to_linear : public ColorTransfer {

public:
    ColorTransfer_AdobeRGB_to_linear () : ColorTransfer("AdobeRGB_to_linear") {}
    ~ColorTransfer_AdobeRGB_to_linear (void) { };

    float operator() (float x) {
        if (x < 0.f)
            return 0.f;
        return std::pow (x, 2.f + (51.f / 256.f));
    }
};

class ColorTransfer_linear_to_AdobeRGB : public ColorTransfer {

public:
    ColorTransfer_linear_to_AdobeRGB () : ColorTransfer("linear_to_AdobeRGB") {}
    ~ColorTransfer_linear_to_AdobeRGB (void) { }

    float operator() (float x) {
        if (x < 0.f)
            return 0.f;
        return std::pow (x, 1.f / (2.f + (51.f / 256.f)));
    }
};

/// Rec709 transfer function which is a HDTV standard
///    http://en.wikipedia.org/wiki/Rec._709
class ColorTransfer_Rec709_to_linear : public ColorTransfer {
public:
    ColorTransfer_Rec709_to_linear () : ColorTransfer ("Rec709_to_linear") { };
    ~ColorTransfer_Rec709_to_linear (void) { };

    float operator() (float x) {
        if (x < 0.f)
            return 0.f;
        return (x <= 0.081f ? x / 4.5f : std::pow ((x + 0.099f) / 1.099f, 1.0f / 0.45f));
    };
};

class ColorTransfer_linear_to_Rec709 : public ColorTransfer {
public:
    ColorTransfer_linear_to_Rec709 () : ColorTransfer ("linear_to_Rec709") { };
    ~ColorTransfer_linear_to_Rec709 (void) { };

    float operator() (float x) {
        if (x < 0.f)
            return 0.f;
        return (x <= 0.018f) ? x * 4.5f : (std::pow (x, 0.45f) * 1.099f) - 0.099f;
    }
};

static void
compute_kodak_coeff (float refBlack, float refWhite,
                     float dispGamma, float negGamma,
                     float &black, float &white, float &gamma, float &gain,
                     float &offset)
{
    // reference black code values below 0 are not allowed
    black = refBlack;
    if (black < 0.f) black = 0.f;

    // reference white code values above 1023 are not allowed, and must be
    // equal or above reference black
    white = refWhite;
    if (white > 1023.f)
        white = 1023.f;
    else if (white < black)
        white = black;

    // compute coefficients
    gamma = 0.002f / negGamma * dispGamma / 1.7f;
    gain = 1.f / (1.f - std::pow(10.f, (black - white) * gamma));
    offset = gain - 1.f;

    // convert 10bit code values to float
    black /= 1023.f;
    white /= 1023.f;
    gamma *= 1023.f;
}

/// Kodak Log transfer function which is used on Kodak 10bit log data
///
/// @todo cache results into a look up table with enough detail for
/// 10bits as this function will be slightly slow at the moment.
///
/// @note the soft clip is not implemented as it makes the transfer
/// function non-reversible. This parameter was designed to help display
/// high code values on a CRT monitor. Having a non-reversible transfer
/// function isn't so good for an image processing pipeline.
class ColorTransfer_KodakLog_to_linear : public ColorTransfer {
public:
    ColorTransfer_KodakLog_to_linear ()
        : ColorTransfer("_KodakLog_to_linear"), m_refBlack(95.f),
          m_refWhite(685.f), m_dispGamma(1.7f), m_negGamma(0.6f)
    {
        add_paramater ("refBlack");
        add_paramater ("refWhite");
        add_paramater ("dispGamma");
        add_paramater ("negGamma");
        compute_kodak_coeff (m_refBlack, m_refWhite, m_dispGamma, m_negGamma,
                                     m_black, m_white, m_gamma, m_gain, m_offset);
    };

    ~ColorTransfer_KodakLog_to_linear (void) { };

    bool set (std::string name, float param) {
        if (iequals (name, "refBlack"))  m_refBlack = param;
        else if (iequals (name, "refWhite"))  m_refWhite = param;
        else if (iequals (name, "dispGamma")) m_dispGamma = param;
        else if (iequals (name, "negGamma"))  m_negGamma = param;
        else return false;
        // recompute coefficients
        compute_kodak_coeff (m_refBlack, m_refWhite, m_dispGamma, m_negGamma,
                             m_black, m_white, m_gamma, m_gain, m_offset);
        return true;
    }

    bool get (std::string name, float &param) {
        if (iequals (name, "refBlack"))  param = m_refBlack;
        else if (iequals (name, "refWhite"))  param = m_refWhite;
        else if (iequals (name, "dispGamma")) param = m_dispGamma;
        else if (iequals (name, "negGamma"))  param = m_negGamma;
        else return false;
        return true;
    }

    float operator() (float x) {
        return (x < (m_black + 1e-06)) ? 0.f :
            std::pow (10.f, ((x - m_white) * m_gamma)) * m_gain - m_offset;
    }

private:
    // transfer paramaters
    float m_refBlack;
    float m_refWhite;
    float m_dispGamma;
    float m_negGamma;

    // coefficents
    float m_black;
    float m_white;
    float m_gamma;
    float m_gain;
    float m_offset;
};

class ColorTransfer_linear_to_KodakLog : public ColorTransfer {
public:
    ColorTransfer_linear_to_KodakLog ()
        : ColorTransfer("linear_to_KodakLog"), m_refBlack(95.f),
          m_refWhite(685.f), m_dispGamma(1.7f), m_negGamma(0.6f)
    {
        add_paramater ("refBlack");
        add_paramater ("refWhite");
        add_paramater ("dispGamma");
        add_paramater ("negGamma");
        compute_kodak_coeff (m_refBlack, m_refWhite, m_dispGamma, m_negGamma,
                                     m_black, m_white, m_gamma, m_gain, m_offset);
    }

    ~ColorTransfer_linear_to_KodakLog (void) { };

    bool set (std::string name, float param) {
        if (iequals (name, "refBlack"))  m_refBlack = param;
        else if (iequals (name, "refWhite"))  m_refWhite = param;
        else if (iequals (name, "dispGamma")) m_dispGamma = param;
        else if (iequals (name, "negGamma"))  m_negGamma = param;
        else return false;
        // recompute coefficients
        compute_kodak_coeff (m_refBlack, m_refWhite, m_dispGamma, m_negGamma,
                             m_black, m_white, m_gamma, m_gain, m_offset);
        return true;
    }

    bool get (std::string name, float &param) {
        if (iequals (name, "refBlack"))  param = m_refBlack;
        else if (iequals (name, "refWhite"))  param = m_refWhite;
        else if (iequals (name, "dispGamma")) param = m_dispGamma;
        else if (iequals (name, "negGamma"))  param = m_negGamma;
        else return false;
        return true;
    }

    float operator() (float x) {
        if (x < 1e-10)
            x = 1e-10;
        x = std::log10 ((x + m_offset) / m_gain) / m_gamma + m_white;
        return (x < m_black) ? 0.f : x;
    }

private:
    // transfer paramaters
    float m_refBlack;
    float m_refWhite;
    float m_dispGamma;
    float m_negGamma;

    // coefficents
    float m_black;
    float m_white;
    float m_gamma;
    float m_gain;
    float m_offset;
};

};  // anonymous namespace

// ColorTransfer::create is a static method that, given a transfer function
// name, returns an allocated and instantiated transfer function of the correct
// implementation.
// If the name is not recongnoized, return NULL.
ColorTransfer *
ColorTransfer::create (const std::string &name)
{
    if (iequals (name, "linear_to_linear") || iequals (name, "null"))
        return new ColorTransfer_null ();
    if (iequals (name, "Gamma"))
        return new ColorTransfer_gamma ();
    if (iequals (name, "linear_to_sRGB"))
        return new ColorTransfer_linear_to_sRGB ();
    if (iequals (name, "sRGB_to_linear"))
        return new ColorTransfer_sRGB_to_linear ();
    if (iequals (name, "linear_to_AdobeRGB"))
        return new ColorTransfer_linear_to_AdobeRGB ();
    if (iequals (name, "AdobeRGB_to_linear"))
        return new ColorTransfer_AdobeRGB_to_linear ();
    if (iequals (name, "linear_to_Rec709"))
        return new ColorTransfer_linear_to_Rec709 ();
    if (iequals (name, "Rec709_to_linear"))
        return new ColorTransfer_Rec709_to_linear ();
    if (iequals (name, "linear_to_KodakLog"))
        return new ColorTransfer_linear_to_KodakLog ();
    if (iequals (name, "KodakLog_to_linear"))
        return new ColorTransfer_KodakLog_to_linear ();
    return NULL;
}

}
OIIO_NAMESPACE_EXIT


/*
 * Copyright 2009-2018 The VOTCA Development Team (http://www.votca.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *     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.
 *
 */
#define BOOST_TEST_MAIN

#define BOOST_TEST_MODULE basisset_test
#include <boost/test/unit_test.hpp>
#include <votca/xtp/basisset.h>
#include <fstream>
#include <iostream>
#include <votca/xtp/orbitals.h>
#include <votca/xtp/aobasis.h>
#include <votca/xtp/aoshell.h>
using namespace votca::xtp;
using namespace std;
BOOST_AUTO_TEST_SUITE(basisset_test)

BOOST_AUTO_TEST_CASE(Contraction_test) {
std::ofstream basisfile("contracted.xml");
basisfile<<"<basis name=\"cc-pVTZ\">"<<std::endl;
basisfile<<"<element name=\"C\">"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"8236.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0005424302\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"1235.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0041964279\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"280.8\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0215409141\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"79.27\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0836149496\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"25.59\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.2398716189\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"8.997\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.4437518201\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"3.319\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.3535796965\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"0.3643\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0091763661\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"8236.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0001963922\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"1235.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0015259503\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"280.8\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.007890449\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"79.27\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0315148705\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"25.59\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0969100083\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"8.997\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.2205415263\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"3.319\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.2960691129\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"0.3643\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0405034329\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"0.9059\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"0.1285\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"P\">"<<std::endl;
basisfile<<"			<constant decay=\"18.71\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0394263872\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"4.133\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.2440889849\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"1.2\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.8154920089\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"P\">"<<std::endl;
basisfile<<"			<constant decay=\"0.3827\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"P\">"<<std::endl;
basisfile<<"			<constant decay=\"0.1209\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"D\">"<<std::endl;
basisfile<<"			<constant decay=\"1.097\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"D\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"D\">"<<std::endl;
basisfile<<"			<constant decay=\"0.318\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"D\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"F\">"<<std::endl;
basisfile<<"			<constant decay=\"0.761\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"F\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"	</element>"<<std::endl;
basisfile<<"<element name=\"O\">"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"15330.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0005201983\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"2299.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0040233448\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"522.4\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0207290833\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"147.3\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0810823271\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"47.55\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.2362263521\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"16.76\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.4435182094\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"6.207\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.3586705887\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"0.6882\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0083497972\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"15330.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.000197236\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"2299.0\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0015350107\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"522.4\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0079511839\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"147.3\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.0321134529\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"47.55\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.100269643\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"16.76\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.2340471118\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"6.207\">"<<std::endl;
basisfile<<"				<contractions factor=\"-0.3014109278\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"0.6882\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0349196495\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"1.752\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"S\">"<<std::endl;
basisfile<<"			<constant decay=\"0.2384\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"S\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"P\">"<<std::endl;
basisfile<<"			<constant decay=\"34.46\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.0411634896\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"7.749\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.2577628359\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"			<constant decay=\"2.28\">"<<std::endl;
basisfile<<"				<contractions factor=\"0.8024192744\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"P\">"<<std::endl;
basisfile<<"			<constant decay=\"0.7156\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"P\">"<<std::endl;
basisfile<<"			<constant decay=\"0.214\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"P\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"D\">"<<std::endl;
basisfile<<"			<constant decay=\"2.314\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"D\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"D\">"<<std::endl;
basisfile<<"			<constant decay=\"0.645\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"D\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"		<shell scale=\"1.0\" type=\"F\">"<<std::endl;
basisfile<<"			<constant decay=\"1.428\">"<<std::endl;
basisfile<<"				<contractions factor=\"1.0\" type=\"F\"/>"<<std::endl;
basisfile<<"			</constant>"<<std::endl;
basisfile<<"		</shell>"<<std::endl;
basisfile<<"	</element>"<<std::endl;
basisfile<<"</basis>"<<std::endl;
basisfile.close();

std::ofstream xyzfile("CO.xyz");
xyzfile << " 2" << std::endl;
xyzfile << " CO" << std::endl;
xyzfile << " C            .000000     .000000     .000000" << std::endl;
xyzfile << " O            1.000000     .000000     .000000" << std::endl;
xyzfile.close();

Orbitals orbitals;
orbitals.LoadFromXYZ("CO.xyz");
BasisSet basis;
basis.LoadBasisSet("contracted.xml");

}

BOOST_AUTO_TEST_SUITE_END()


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

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

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

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

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISJOINT_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISJOINT_HPP

// Note: contrary to most files, the geometry::detail::disjoint namespace
// is partly implemented in a separate file, to avoid circular references
// disjoint -> get_turns -> disjoint

#include <cstddef>

#include <boost/range.hpp>

#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/coordinate_dimension.hpp>
#include <boost/geometry/core/reverse_dispatch.hpp>

#include <boost/geometry/algorithms/covered_by.hpp>

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

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace disjoint
{

struct disjoint_interrupt_policy
{
    static bool const enabled = true;
    bool has_intersections;

    inline disjoint_interrupt_policy()
        : has_intersections(false)
    {}

    template <typename Range>
    inline bool apply(Range const& range)
    {
        // If there is any IP in the range, it is NOT disjoint
        if (boost::size(range) > 0)
        {
            has_intersections = true;
            return true;
        }
        return false;
    }
};

template
<
    typename Point1, typename Point2,
    std::size_t Dimension, std::size_t DimensionCount
>
struct point_point
{
    static inline bool apply(Point1 const& p1, Point2 const& p2)
    {
        if (! geometry::math::equals(get<Dimension>(p1), get<Dimension>(p2)))
        {
            return true;
        }
        return point_point
            <
                Point1, Point2,
                Dimension + 1, DimensionCount
            >::apply(p1, p2);
    }
};

template <typename Point1, typename Point2, std::size_t DimensionCount>
struct point_point<Point1, Point2, DimensionCount, DimensionCount>
{
    static inline bool apply(Point1 const& , Point2 const& )
    {
        return false;
    }
};

template
<
    typename Point, typename Box,
    std::size_t Dimension, std::size_t DimensionCount
>
struct point_box
{
    static inline bool apply(Point const& point, Box const& box)
    {
        if (get<Dimension>(point) < get<min_corner, Dimension>(box)
            || get<Dimension>(point) > get<max_corner, Dimension>(box))
        {
            return true;
        }
        return point_box
            <
                Point, Box,
                Dimension + 1, DimensionCount
            >::apply(point, box);
    }
};

template <typename Point, typename Box, std::size_t DimensionCount>
struct point_box<Point, Box, DimensionCount, DimensionCount>
{
    static inline bool apply(Point const& , Box const& )
    {
        return false;
    }
};

template
<
    typename Box1, typename Box2,
    std::size_t Dimension, std::size_t DimensionCount
>
struct box_box
{
    static inline bool apply(Box1 const& box1, Box2 const& box2)
    {
        if (get<max_corner, Dimension>(box1) < get<min_corner, Dimension>(box2))
        {
            return true;
        }
        if (get<min_corner, Dimension>(box1) > get<max_corner, Dimension>(box2))
        {
            return true;
        }
        return box_box
            <
                Box1, Box2,
                Dimension + 1, DimensionCount
            >::apply(box1, box2);
    }
};

template <typename Box1, typename Box2, std::size_t DimensionCount>
struct box_box<Box1, Box2, DimensionCount, DimensionCount>
{
    static inline bool apply(Box1 const& , Box2 const& )
    {
        return false;
    }
};

template
<
    typename Geometry1, typename Geometry2
>
struct reverse_covered_by
{
    static inline bool apply(Geometry1 const& geometry1, Geometry2 const& geometry2)
    {
        return ! geometry::covered_by(geometry1, geometry2);
    }
};

/*!
    \brief Internal utility function to detect of boxes are disjoint
    \note Is used from other algorithms, declared separately
        to avoid circular references
 */
template <typename Box1, typename Box2>
inline bool disjoint_box_box(Box1 const& box1, Box2 const& box2)
{
    return box_box
        <
            Box1, Box2,
            0, dimension<Box1>::type::value
        >::apply(box1, box2);
}

/*!
    \brief Internal utility function to detect of points are disjoint
    \note To avoid circular references
 */
template <typename Point1, typename Point2>
inline bool disjoint_point_point(Point1 const& point1, Point2 const& point2)
{
    return point_point
        <
            Point1, Point2,
            0, dimension<Point1>::type::value
        >::apply(point1, point2);
}

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

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace equals
{

/*!
    \brief Internal utility function to detect of points are disjoint
    \note To avoid circular references
 */
template <typename Point1, typename Point2>
inline bool equals_point_point(Point1 const& point1, Point2 const& point2)
{
    return ! detail::disjoint::disjoint_point_point(point1, point2);
}

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

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISJOINT_HPP


// Copyright (C) 2017  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.

#include <dlib/python.h>
#include <dlib/matrix.h>
#include <dlib/dnn.h>
#include <dlib/image_transforms.h>
#include "indexing.h"
#include <pybind11/stl_bind.h>

using namespace dlib;
using namespace std;

namespace py = pybind11;

PYBIND11_MAKE_OPAQUE(std::vector<mmod_rect>);
PYBIND11_MAKE_OPAQUE(std::vector<std::vector<mmod_rect> >);

class cnn_face_detection_model_v1
{

public:

    cnn_face_detection_model_v1(const std::string& model_filename)
    {
        deserialize(model_filename) >> net;
    }

    std::vector<mmod_rect> detect (
        py::object pyimage,
        const int upsample_num_times
    )
    {
        pyramid_down<2> pyr;
        std::vector<mmod_rect> rects;

        // Copy the data into dlib based objects
        matrix<rgb_pixel> image;
        if (is_gray_python_image(pyimage))
            assign_image(image, numpy_gray_image(pyimage));
        else if (is_rgb_python_image(pyimage))
            assign_image(image, numpy_rgb_image(pyimage));
        else
            throw dlib::error("Unsupported image type, must be 8bit gray or RGB image.");

        // Upsampling the image will allow us to detect smaller faces but will cause the
        // program to use more RAM and run longer.
        unsigned int levels = upsample_num_times;
        while (levels > 0)
        {
            levels--;
            pyramid_up(image, pyr);
        }

        auto dets = net(image);

        // Scale the detection locations back to the original image size
        // if the image was upscaled.
        for (auto&& d : dets) {
            d.rect = pyr.rect_down(d.rect, upsample_num_times);
            rects.push_back(d);
        }

        return rects;
    }

    std::vector<std::vector<mmod_rect> > detect_mult (
        py::list& imgs,
        const int upsample_num_times,
        const int batch_size = 128
    )
    {
        pyramid_down<2> pyr;
        std::vector<matrix<rgb_pixel> > dimgs;
        dimgs.reserve(len(imgs));

        for(int i = 0; i < len(imgs); i++)
        {
            // Copy the data into dlib based objects
            matrix<rgb_pixel> image;
            py::object tmp = imgs[i].cast<py::object>();
            if (is_gray_python_image(tmp))
                assign_image(image, numpy_gray_image(tmp));
            else if (is_rgb_python_image(tmp))
                assign_image(image, numpy_rgb_image(tmp));
            else
                throw dlib::error("Unsupported image type, must be 8bit gray or RGB image.");

            for(int i = 0; i < upsample_num_times; i++)
            {
                pyramid_up(image);
            }
            dimgs.push_back(image);
        }

        for(int i = 1; i < dimgs.size(); i++)
        {
            if
            (
                dimgs[i - 1].nc() != dimgs[i].nc() ||
                dimgs[i - 1].nr() != dimgs[i].nr()
            )
                throw dlib::error("Images in list must all have the same dimensions.");

        }

        auto dets = net(dimgs, batch_size);
        std::vector<std::vector<mmod_rect> > all_rects;

        for(auto&& im_dets : dets)
        {
            std::vector<mmod_rect> rects;
            rects.reserve(im_dets.size());
            for (auto&& d : im_dets) {
                d.rect = pyr.rect_down(d.rect, upsample_num_times);
                rects.push_back(d);
            }
            all_rects.push_back(rects);
        }

        return all_rects;
    }

private:

    template <long num_filters, typename SUBNET> using con5d = con<num_filters,5,5,2,2,SUBNET>;
    template <long num_filters, typename SUBNET> using con5  = con<num_filters,5,5,1,1,SUBNET>;

    template <typename SUBNET> using downsampler  = relu<affine<con5d<32, relu<affine<con5d<32, relu<affine<con5d<16,SUBNET>>>>>>>>>;
    template <typename SUBNET> using rcon5  = relu<affine<con5<45,SUBNET>>>;

    using net_type = loss_mmod<con<1,9,9,1,1,rcon5<rcon5<rcon5<downsampler<input_rgb_image_pyramid<pyramid_down<6>>>>>>>>;

    net_type net;
};

// ----------------------------------------------------------------------------------------

void bind_cnn_face_detection(py::module& m)
{
    {
    py::class_<cnn_face_detection_model_v1>(m, "cnn_face_detection_model_v1", "This object detects human faces in an image.  The constructor loads the face detection model from a file. You can download a pre-trained model from http://dlib.net/files/mmod_human_face_detector.dat.bz2.")
        .def(py::init<std::string>())
        .def(
            "__call__",
            &cnn_face_detection_model_v1::detect,
            py::arg("img"), py::arg("upsample_num_times")=0,
            "Find faces in an image using a deep learning model.\n\
          - Upsamples the image upsample_num_times before running the face \n\
            detector."
            )
        .def(
            "__call__",
            &cnn_face_detection_model_v1::detect_mult,
            py::arg("imgs"), py::arg("upsample_num_times")=0, py::arg("batch_size")=128,
            "takes a list of images as input returning a 2d list of mmod rectangles"
            );
    }
    {
    typedef mmod_rect type;
    py::class_<type>(m, "mmod_rectangle", "Wrapper around a rectangle object and a detection confidence score.")
        .def_readwrite("rect",   &type::rect)
        .def_readwrite("confidence", &type::detection_confidence);
    }
    {
    typedef std::vector<mmod_rect> type;
    py::bind_vector<type>(m, "mmod_rectangles", "An array of mmod rectangle objects.")
        .def("extend", extend_vector_with_python_list<mmod_rect>);
    }
    {
    typedef std::vector<std::vector<mmod_rect> > type;
    py::bind_vector<type>(m, "mmod_rectangless", "A 2D array of mmod rectangle objects.")
        .def("extend", extend_vector_with_python_list<std::vector<mmod_rect>>);
    }
}


//////////////////////////////////////////////////////////////////////////////////
// survival::modelss::exponential::scalar::log_rate::include.hpp                 //
//                                                                              //
//  Copyright 2009 Erwann Rogard. 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 BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_EXPONENTIAL_SCALAR_LOG_RATE_INCLUDE_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_MODELS_EXPONENTIAL_SCALAR_LOG_RATE_INCLUDE_HPP_ER_2009

#include <boost/statistics/detail/distribution/survival/models/exponential/scalar/log_rate/identity/include.hpp>

#endif


//
// Created by h3ct0r on 28/09/20.
//

#include <iostream>
#include <fstream>
#include <chrono>
#include <algorithm>
#include <iterator>

#include <octomap/octomap.h>
#include <ros/ros.h>
#include <pcl_ros/point_cloud.h>
#include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h>
#include <geometry_msgs/Point.h>
#include <geometry_msgs/Pose.h>
#include <octomap_msgs/Octomap.h>
#include <octomap_msgs/conversions.h>
#include <octomap_msgs/GetOctomap.h>
#include <tf/transform_broadcaster.h>
#include <ros/console.h>
#include <octomap/math/Utils.h>
#include <octomap/math/Vector3.h>
#include <map>
#include <limits>
#include "boost/tuple/tuple_comparison.hpp"
#include <sensor_msgs/PointCloud2.h>
#include <pcl_conversions/pcl_conversions.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/point_types.h>
#include <pcl/filters/passthrough.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/common/distances.h>

#include <pcl/ModelCoefficients.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/features/normal_3d.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/filters/statistical_outlier_removal.h>

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
#include <boost/range/algorithm.hpp>

#include <tf/transform_listener.h>
#include <pcl_ros/transforms.h>
#include <pcl_conversions/pcl_conversions.h>

#include <math.h>
#include <limits>

#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)
#define DEBUG  1

using namespace std;
using namespace std::chrono;

ros::Publisher cloud_transformed_world;
sensor_msgs::PointCloud2 cloud_msg;
auto start = chrono::high_resolution_clock::now();
auto stop = chrono::high_resolution_clock::now();

tf::StampedTransform lookup_transform(const std::string& target_frame, const std::string& source_frame) {
    tf::StampedTransform transform;

    tf::TransformListener tf_listener;
    try {
        tf_listener.waitForTransform(target_frame, source_frame, ros::Time(0), ros::Duration(10.0) );
        tf_listener.lookupTransform (target_frame, source_frame, ros::Time(0), transform);
    } catch (tf::TransformException &ex) {
        ROS_ERROR("%s",ex.what());
    }

    return transform;
}

void transformPointCloud(const std::string &target_frame, const sensor_msgs::PointCloud2 &in,
                         pcl::PointCloud<pcl::PointXYZ> &out) {
    sensor_msgs::PointCloud2::Ptr transformed_cloud(new sensor_msgs::PointCloud2);
    pcl_ros::transformPointCloud(target_frame, lookup_transform(target_frame, in.header.frame_id), in, *transformed_cloud);

    pcl::fromROSMsg(*transformed_cloud, out);
}

void cloud_cb(const sensor_msgs::PointCloud2ConstPtr& input){
    if(cloud_msg.height <= 0){
        ROS_INFO_STREAM("cloud received...");
    }

    start = chrono::high_resolution_clock::now();

    string target_frame = "/world";
    sensor_msgs::PointCloud2 cloud = *input;

    sensor_msgs::PointCloud2::Ptr transformed_cloud(new sensor_msgs::PointCloud2);
    pcl_ros::transformPointCloud(target_frame, lookup_transform(target_frame, cloud.header.frame_id), cloud, *transformed_cloud);

    stop = chrono::high_resolution_clock::now();
    ROS_INFO_STREAM("Time to process cloud: " << float(chrono::duration_cast<chrono::microseconds>(stop - start).count() / 1000000.0) << " seconds");

    cloud_transformed_world.publish(transformed_cloud);
}

//void process_cloud(){
//    if(cloud_msg.height <= 0){
//        ROS_WARN_ONCE("Cloud not received yet, waiting for new pointcloud msg...");
//        return;
//    }
//
//    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_original(new pcl::PointCloud<pcl::PointXYZ>);
//
//    pcl::PCLPointCloud2 cloud_tmp;
//    pcl_conversions::toPCL(cloud_msg, cloud_tmp); // sensor_msgs::PointCloud2 to pcl::PCLPointCloud2
//    pcl::fromPCLPointCloud2(cloud_tmp, *cloud_original); // pcl::PCLPointCloud2 to PointCloud
//
//    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
//    *cloud = *cloud_original;
//
//    try{
//            cam_bl.waitForTransform("base_link","pico_flexx_optical_frame",ros::Time(0),ros::Duration(5));
//            cam_bl.lookupTransform("base_link","pico_flexx_optical_frame",ros::Time(0),cam_bl_tf);
//    }
//    catch(tf::TransformException &ex){
//            ROS_WARN("%s",ex.what());
//    };
//
//    sensor_msgs::PointCloud2 input_in_bl;
//    pcl_ros::transformPointCloud("base_link",*input_from_camera_frame,input_in_bl,cam_bl);
//}

int main(int argc, char **argv) {
    ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Debug);
    ros::console::notifyLoggerLevelsChanged();

    ros::init(argc, argv, "republish_cloud_to_frame");
    ros::NodeHandle nh;
    ros::Rate r(10);

    cloud_transformed_world = nh.advertise<sensor_msgs::PointCloud2>("/cloud_transformed_world", 1);
    ros::Subscriber sub = nh.subscribe("/laser_cloud_surround2", 1, cloud_cb);

    ros::Duration(2.0).sleep();
    ros::spin();

    return 0;
}


//---------------------------------------------------------------------------//
//!
//! \file   Utility_MomentumUnits.hpp
//! \author Alex Robinson
//! \brief  The momentum units
//!
//---------------------------------------------------------------------------//

#ifndef UTILITY_MOMENTUM_UNITS_HPP
#define UTILITY_MOMENTUM_UNITS_HPP

// Boost Includes
#include <boost/units/systems/si/momentum.hpp>
#include <boost/units/conversion.hpp>

// FRENSIE Includes
#include "Utility_AtomicMomentumUnit.hpp"
#include "Utility_MeCMomentumUnit.hpp"
#include "Utility_RawPhysicalConstants.hpp"

BOOST_UNITS_DEFINE_CONVERSION_FACTOR( Utility::Units::AtomicMomentumBaseUnit, Utility::Units::MeCMomentumBaseUnit, double, Utility::RawPhysicalConstants::fine_structure_constant );

BOOST_UNITS_DEFINE_CONVERSION_FACTOR( Utility::Units::MeCMomentumBaseUnit, Utility::Units::AtomicMomentumBaseUnit, double, 1.0/Utility::RawPhysicalConstants::fine_structure_constant );

#endif // end UTILITY_MOMENTUM_UNITS_HPP

//---------------------------------------------------------------------------//
// end Utility_MomentumUnitS.hpp
//---------------------------------------------------------------------------//