/** @file
 *****************************************************************************

 A test for Zerocash.

 *****************************************************************************
 * @author     This file is part of libzerocash, developed by the Zerocash
 *             project and contributors (see AUTHORS).
 * @copyright  MIT license (see LICENSE file)
 *****************************************************************************/

#include <stdlib.h>
#include <iostream>

#define BOOST_TEST_MODULE zerocashTest
#include <boost/test/included/unit_test.hpp>

#include "timer.h"

#include "zerocash/Zerocash.h"
#include "zerocash/ZerocashParams.h"
#include "zerocash/Address.h"
#include "zerocash/CoinCommitment.h"
#include "zerocash/Coin.h"
#include "zerocash/IncrementalMerkleTree.h"
#include "zerocash/MintTransaction.h"
#include "zerocash/PourTransaction.h"
#include "zerocash/PourInput.h"
#include "zerocash/PourOutput.h"
#include "zerocash/utils/util.h"

using namespace std;
using namespace libsnark;

#define TEST_TREE_DEPTH 4

BOOST_AUTO_TEST_CASE( SaveAndLoadKeysFromFiles ) {
    cout << "\nSaveAndLoadKeysFromFiles TEST\n" << endl;

    cout << "Creating Params...\n" << endl;

    libzerocash::timer_start("Param Generation");
    auto keypair = libzerocash::ZerocashParams::GenerateNewKeyPair(TEST_TREE_DEPTH);
    libzerocash::ZerocashParams p(
        TEST_TREE_DEPTH,
        &keypair
    );
    libzerocash::timer_stop("Param Generation");
    print_mem("after param generation");

    cout << "Successfully created Params.\n" << endl;

    std::string vk_path = "./zerocashTest-verification-key";
    std::string pk_path = "./zerocashTest-proving-key";

    libzerocash::timer_start("Saving Proving Key");

    libzerocash::ZerocashParams::SaveProvingKeyToFile(
        &p.getProvingKey(),
        pk_path
    );

    libzerocash::timer_stop("Saving Proving Key");

    libzerocash::timer_start("Saving Verification Key");

    libzerocash::ZerocashParams::SaveVerificationKeyToFile(
        &p.getVerificationKey(),
        vk_path
    );

    libzerocash::timer_stop("Saving Verification Key");

    libzerocash::timer_start("Loading Proving Key");
    auto pk_loaded = libzerocash::ZerocashParams::LoadProvingKeyFromFile(pk_path, TEST_TREE_DEPTH);
    libzerocash::timer_stop("Loading Proving Key");

    libzerocash::timer_start("Loading Verification Key");
    auto vk_loaded = libzerocash::ZerocashParams::LoadVerificationKeyFromFile(vk_path, TEST_TREE_DEPTH);
    libzerocash::timer_stop("Loading Verification Key");

    cout << "Comparing Proving and Verification key.\n" << endl;

    if ( !( p.getProvingKey() == pk_loaded && p.getVerificationKey() == vk_loaded) ) {
        BOOST_ERROR("Proving and verification key are not equal.");
    }

    vector<libzerocash::Coin> coins;
    vector<libzerocash::Address> addrs;

    cout << "Creating Addresses and Coins...\n" << endl;
    for(size_t i = 0; i < 5; i++) {
        addrs.push_back(libzerocash::Address::CreateNewRandomAddress());
        coins.push_back(libzerocash::Coin(addrs.at(i).getPublicAddress(), i));
    }
    cout << "Successfully created address and coins.\n" << endl;

    cout << "Creating a Mint Transaction...\n" << endl;
    libzerocash::MintTransaction minttx(coins.at(0));
    cout << "Successfully created a Mint Transaction.\n" << endl;

    vector<std::vector<bool>> coinValues(5);
    vector<bool> temp_comVal(ZC_CM_SIZE * 8);
    for(size_t i = 0; i < coinValues.size(); i++) {
        libzerocash::convertBytesVectorToVector(coins.at(i).getCoinCommitment().getCommitmentValue(), temp_comVal);
        coinValues.at(i) = temp_comVal;
    }

    cout << "Creating Merkle Tree...\n" << endl;
    libzerocash::IncrementalMerkleTree merkleTree(coinValues, TEST_TREE_DEPTH);
    cout << "Successfully created Merkle Tree.\n" << endl;

    std::vector<bool> index;

    cout << "Creating Witness 1...\n" << endl;
    merkle_authentication_path witness_1(TEST_TREE_DEPTH);
    libzerocash::convertIntToVector(1, index);
    merkleTree.getWitness(index, witness_1);
    cout << "Successfully created Witness 1.\n" << endl;

    cout << "Creating Witness 2...\n" << endl;
    merkle_authentication_path witness_2(TEST_TREE_DEPTH);
    libzerocash::convertIntToVector(3, index);
    merkleTree.getWitness(index, witness_2);
    cout << "Successfully created Witness 2.\n" << endl;

    cout << "Creating coins to spend...\n" << endl;
    libzerocash::Address newAddress3 = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress3 = newAddress3.getPublicAddress();

    libzerocash::Address newAddress4 = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress4 = newAddress4.getPublicAddress();

    libzerocash::Coin c_1_new(pubAddress3, 2);
    libzerocash::Coin c_2_new(pubAddress4, 2);
    cout << "Successfully created coins to spend.\n" << endl;

    vector<unsigned char> rt(ZC_ROOT_SIZE);
    merkleTree.getRootValue(rt);

    // XXX: debugging
    std::cout << "Root: " << rt.size() << endl;
    std::cout << "wit1: " << witness_1.size() << endl;
    std::cout << "wit2: " << witness_1.size() << endl;

    vector<unsigned char> as(ZC_SIG_PK_SIZE, 'a');

    cout << "Creating a pour transaction...\n" << endl;
    libzerocash::PourTransaction pourtx(1, p,
    		rt,
    		coins.at(1), coins.at(3),
    		addrs.at(1), addrs.at(3),
                1, 3,
    		witness_1, witness_2,
    		pubAddress3, pubAddress4,
    		0,
            0,
    		as,
    		c_1_new, c_2_new);
    cout << "Successfully created a pour transaction.\n" << endl;

	std::vector<unsigned char> pubkeyHash(ZC_SIG_PK_SIZE, 'a');

    cout << "Verifying a pour transaction...\n" << endl;
    bool pourtx_res = pourtx.verify(p, pubkeyHash, rt);

    BOOST_CHECK(pourtx_res);
}

BOOST_AUTO_TEST_CASE( PourInputOutputTest ) {
    // dummy input
    {
        libzerocash::PourInput input(TEST_TREE_DEPTH);

        BOOST_CHECK(input.old_coin.getValue() == 0);
        BOOST_CHECK(input.old_address.getPublicAddress() == input.old_coin.getPublicAddress());
    }

    // dummy output
    {
        libzerocash::PourOutput output(0);

        BOOST_CHECK(output.new_coin.getValue() == 0);
        BOOST_CHECK(output.to_address == output.new_coin.getPublicAddress());
    }
}

// testing with general situational setup
void test_pour(libzerocash::ZerocashParams& p,
          uint64_t vpub_in,
          uint64_t vpub_out,
          std::vector<uint64_t> inputs, // values of the inputs (max 2)
          std::vector<uint64_t> outputs) // values of the outputs (max 2)
{
    using pour_input_state = std::tuple<libzerocash::Address, libzerocash::Coin, std::vector<bool>>;

    // Construct incremental merkle tree
    libzerocash::IncrementalMerkleTree merkleTree(TEST_TREE_DEPTH);

    // Dummy sig_pk
    vector<unsigned char> as(ZC_SIG_PK_SIZE, 'a');

    vector<libzerocash::PourInput> pour_inputs;
    vector<libzerocash::PourOutput> pour_outputs;

    vector<pour_input_state> input_state;

    for(std::vector<uint64_t>::iterator it = inputs.begin(); it != inputs.end(); ++it) {
        libzerocash::Address addr = libzerocash::Address::CreateNewRandomAddress();
        libzerocash::Coin coin(addr.getPublicAddress(), *it);

        // commitment from coin
        std::vector<bool> commitment(ZC_CM_SIZE * 8);
        libzerocash::convertBytesVectorToVector(coin.getCoinCommitment().getCommitmentValue(), commitment);

        // insert commitment into the merkle tree
        std::vector<bool> index;
        merkleTree.insertElement(commitment, index);

        // store the state temporarily
        input_state.push_back(std::make_tuple(addr, coin, index));
    }

    // compute the merkle root we will be working with
    vector<unsigned char> rt(ZC_ROOT_SIZE);
    {
        vector<bool> root_bv(ZC_ROOT_SIZE * 8);
        merkleTree.getRootValue(root_bv);
        libzerocash::convertVectorToBytesVector(root_bv, rt);
    }

    // get witnesses for all the input coins and construct the pours
    for(vector<pour_input_state>::iterator it = input_state.begin(); it != input_state.end(); ++it) {
        merkle_authentication_path path(TEST_TREE_DEPTH);

        auto index = std::get<2>(*it);
        merkleTree.getWitness(index, path);

        pour_inputs.push_back(libzerocash::PourInput(std::get<1>(*it), std::get<0>(*it), libzerocash::convertVectorToInt(index), path));
    }

    // construct dummy outputs with the given values
    for(vector<uint64_t>::iterator it = outputs.begin(); it != outputs.end(); ++it) {
        pour_outputs.push_back(libzerocash::PourOutput(*it));
    }

    libzerocash::PourTransaction pourtx(p, as, rt, pour_inputs, pour_outputs, vpub_in, vpub_out);

    BOOST_CHECK(pourtx.verify(p, as, rt));
}

BOOST_AUTO_TEST_CASE( PourVpubInTest ) {
    auto keypair = libzerocash::ZerocashParams::GenerateNewKeyPair(TEST_TREE_DEPTH);
    libzerocash::ZerocashParams p(
        TEST_TREE_DEPTH,
        &keypair
    );

    // Things that should work..
    test_pour(p, 0, 0, {1}, {1});
    test_pour(p, 0, 0, {2}, {1, 1});
    test_pour(p, 0, 0, {2, 2}, {3, 1});
    test_pour(p, 0, 1, {1}, {});
    test_pour(p, 0, 1, {2}, {1});
    test_pour(p, 0, 1, {2, 2}, {2, 1});
    test_pour(p, 1, 0, {}, {1});
    test_pour(p, 1, 0, {1}, {1, 1});
    test_pour(p, 1, 0, {2, 2}, {2, 3});

    // Things that should not work...
    BOOST_CHECK_THROW(test_pour(p, 0, 1, {1}, {1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 0, 1, {2}, {1, 1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 0, 1, {2, 2}, {3, 1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 0, 2, {1}, {}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 0, 2, {2}, {1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 0, 2, {2, 2}, {2, 1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 1, 1, {}, {1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 1, 1, {1}, {1, 1}), std::invalid_argument);
    BOOST_CHECK_THROW(test_pour(p, 1, 1, {2, 2}, {2, 3}), std::invalid_argument);

    BOOST_CHECK_THROW(test_pour(p, 0, 0, {2, 2}, {2, 3}), std::invalid_argument);
}

BOOST_AUTO_TEST_CASE( CoinTest ) {
    cout << "\nCOIN TEST\n" << endl;

    libzerocash::Address newAddress = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress = newAddress.getPublicAddress();

    libzerocash::Coin coin(pubAddress, 0);

    cout << "Successfully created a coin.\n" << endl;

    ///////////////////////////////////////////////////////////////////////////

    libzerocash::timer_start("Coin");
    libzerocash::Coin coin2(pubAddress, 0);
    libzerocash::timer_stop("Coin");

    cout << "Successfully created a coin.\n" << endl;
}

BOOST_AUTO_TEST_CASE( MintTxTest ) {
    cout << "\nMINT TRANSACTION TEST\n" << endl;

    libzerocash::Address newAddress = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress = newAddress.getPublicAddress();

    vector<unsigned char> value(ZC_V_SIZE, 0);

    libzerocash::timer_start("Coin");
    const libzerocash::Coin coin(pubAddress, 0);
    libzerocash::timer_stop("Coin");

    libzerocash::timer_start("Mint Transaction");
    libzerocash::MintTransaction minttx(coin);
    libzerocash::timer_stop("Mint Transaction");

    cout << "Successfully created a mint transaction.\n" << endl;

    libzerocash::timer_start("Mint Transaction Verify");
    bool minttx_res = minttx.verify();
    libzerocash::timer_stop("Mint Transaction Verify");

    BOOST_CHECK(minttx_res);
}

BOOST_AUTO_TEST_CASE( PourTxTest ) {
    cout << "\nPOUR TRANSACTION TEST\n" << endl;

    cout << "Creating Params...\n" << endl;

    libzerocash::timer_start("Param Generation");
    auto keypair = libzerocash::ZerocashParams::GenerateNewKeyPair(TEST_TREE_DEPTH);
    libzerocash::ZerocashParams p(
        TEST_TREE_DEPTH,
        &keypair
    );
    libzerocash::timer_stop("Param Generation");
    print_mem("after param generation");

    cout << "Successfully created Params.\n" << endl;

    vector<libzerocash::Coin> coins;
    vector<libzerocash::Address> addrs;

    for(size_t i = 0; i < 5; i++) {
        addrs.push_back(libzerocash::Address::CreateNewRandomAddress());
        coins.push_back(libzerocash::Coin(addrs.at(i).getPublicAddress(), i));
    }

    cout << "Successfully created coins.\n" << endl;

    vector<std::vector<bool>> coinValues(5);

    vector<bool> temp_comVal(ZC_CM_SIZE * 8);
    for(size_t i = 0; i < coinValues.size(); i++) {
        libzerocash::convertBytesVectorToVector(coins.at(i).getCoinCommitment().getCommitmentValue(), temp_comVal);
        coinValues.at(i) = temp_comVal;
        libzerocash::printVectorAsHex("Coin => ", coinValues.at(i));
    }

    cout << "Creating Merkle Tree...\n" << endl;

    libzerocash::timer_start("Merkle Tree");
    libzerocash::IncrementalMerkleTree merkleTree(coinValues, TEST_TREE_DEPTH);
    libzerocash::timer_stop("Merkle Tree");

    cout << "Successfully created Merkle Tree.\n" << endl;

    merkle_authentication_path witness_1(TEST_TREE_DEPTH);

    libzerocash::timer_start("Witness");
    std::vector<bool> index;
    libzerocash::convertIntToVector(1, index);
    if (merkleTree.getWitness(index, witness_1) == false) {
        BOOST_ERROR("Could not get witness");
	}
    libzerocash::timer_stop("Witness");

    cout << "Witness 1: " << endl;
    for(size_t i = 0; i < witness_1.size(); i++) {
        libzerocash::printVectorAsHex(witness_1.at(i));
    }
    cout << "\n" << endl;

    merkle_authentication_path witness_2(TEST_TREE_DEPTH);
    libzerocash::convertIntToVector(3, index);
    if (merkleTree.getWitness(index, witness_2) == false) {
		cout << "Could not get witness" << endl;
	}

    cout << "Witness 2: " << endl;
    for(size_t i = 0; i < witness_2.size(); i++) {
        libzerocash::printVectorAsHex(witness_2.at(i));
    }
    cout << "\n" << endl;

    libzerocash::Address newAddress3 = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress3 = newAddress3.getPublicAddress();

    libzerocash::Address newAddress4 = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress4 = newAddress4.getPublicAddress();

    libzerocash::Coin c_1_new(pubAddress3, 2);
    libzerocash::Coin c_2_new(pubAddress4, 2);

    vector<bool> root_bv(ZC_ROOT_SIZE * 8);
    merkleTree.getRootValue(root_bv);
    vector<unsigned char> rt(ZC_ROOT_SIZE);
    libzerocash::convertVectorToBytesVector(root_bv, rt);

    vector<unsigned char> ones(ZC_V_SIZE, 1);
    vector<unsigned char> twos(ZC_V_SIZE, 2);
    vector<unsigned char> as(ZC_SIG_PK_SIZE, 'a');

    cout << "Creating a pour transaction...\n" << endl;

    libzerocash::timer_start("Pour Transaction");
    libzerocash::PourTransaction pourtx(1, p, rt, coins.at(1), coins.at(3), addrs.at(1), addrs.at(3), 1, 3, witness_1, witness_2, pubAddress3, pubAddress4, 0, 0, as, c_1_new, c_2_new);
    libzerocash::timer_stop("Pour Transaction");
    print_mem("after pour transaction");

    cout << "Successfully created a pour transaction.\n" << endl;

	std::vector<unsigned char> pubkeyHash(ZC_SIG_PK_SIZE, 'a');

    libzerocash::timer_start("Pour Transaction Verify");
    bool pourtx_res = pourtx.verify(p, pubkeyHash, rt);
    libzerocash::timer_stop("Pour Transaction Verify");

    BOOST_CHECK(pourtx_res);
}

BOOST_AUTO_TEST_CASE( MerkleTreeSimpleTest ) {
    cout << "\nMERKLE TREE SIMPLE TEST\n" << endl;

    vector<libzerocash::Coin> coins;
    vector<libzerocash::Address> addrs;

    cout << "Creating coins...\n" << endl;

    for(size_t i = 0; i < 5; i++) {
        addrs.push_back(libzerocash::Address::CreateNewRandomAddress());
        coins.push_back(libzerocash::Coin(addrs.at(i).getPublicAddress(), i));
    }

    cout << "Successfully created coins.\n" << endl;

    vector<std::vector<bool>> coinValues(coins.size());

    vector<bool> temp_comVal(ZC_CM_SIZE * 8);
    for(size_t i = 0; i < coinValues.size(); i++) {
        libzerocash::convertBytesVectorToVector(coins.at(i).getCoinCommitment().getCommitmentValue(), temp_comVal);
        coinValues.at(i) = temp_comVal;
        libzerocash::printVectorAsHex(coinValues.at(i));
    }

    cout << "Creating Merkle Tree...\n" << endl;

    libzerocash::IncrementalMerkleTree merkleTree(64);
	vector<bool> root;
	merkleTree.getRootValue(root);
	cout << "Root: ";
	libzerocash::printVectorAsHex(root);
	cout << endl;

    cout << "Successfully created Merkle Tree.\n" << endl;

	cout << "Copying and pruning Merkle Tree...\n" << endl;
	libzerocash::IncrementalMerkleTree copyTree = merkleTree;
	copyTree.prune();

	cout << "Obtaining compact representation and reconstituting tree...\n" << endl;
	libzerocash::IncrementalMerkleTreeCompact compactTree = merkleTree.getCompactRepresentation();

	cout << "Compact representation vector: ";
	libzerocash::printVector(compactTree.getHashList());

	libzerocash::IncrementalMerkleTree reconstitutedTree(compactTree);
	reconstitutedTree.getRootValue(root);
	cout << "New root: ";
	libzerocash::printVectorAsHex(root);
	cout << endl;

	reconstitutedTree.insertVector(coinValues);
	merkleTree.insertVector(coinValues);

	reconstitutedTree.getRootValue(root);
	cout << "New root (added a bunch more): ";
	libzerocash::printVectorAsHex(root);
	cout << endl;

	merkleTree.getRootValue(root);
	cout << "Old root (added a bunch more): ";
	libzerocash::printVectorAsHex(root);
	cout << endl;

    merkle_authentication_path witness(16);
    std::vector<bool> index;
    libzerocash::convertIntToVector(3, index);
	if (merkleTree.getWitness(index, witness) == false) {
		BOOST_ERROR("Witness generation failed.");
	}

    cout << "Successfully created witness.\n" << endl;

    cout << "Witness: " << endl;
    for(size_t i = 0; i < witness.size(); i++) {
        libzerocash::printVectorAsHex(witness.at(i));
    }
    cout << "\n" << endl;

    vector<bool> wit1(CSHA256::OUTPUT_SIZE * 8);
    vector<bool> wit2(CSHA256::OUTPUT_SIZE * 8);
    vector<bool> wit3(CSHA256::OUTPUT_SIZE * 8);
    vector<bool> inter_1(CSHA256::OUTPUT_SIZE * 8);
    vector<bool> inter_2(CSHA256::OUTPUT_SIZE * 8);
    std::vector<bool> zeros(CSHA256::OUTPUT_SIZE * 8, 0);

    wit1 = coinValues.at(2);
    libzerocash::hashVectors(coinValues.at(0), coinValues.at(1), wit2);
    libzerocash::hashVectors(coinValues.at(4), zeros, inter_1);
    inter_2 = zeros;
    libzerocash::hashVectors(inter_1, inter_2, wit3);

    BOOST_CHECK(witness.size() == 64);
    for (size_t i = 0; i < 61; i++) {
        BOOST_CHECK(witness.at(i) == zeros);
    }
    BOOST_CHECK(
        (witness.at(61) == wit3) &&
        (witness.at(62) == wit2) &&
        (witness.at(63) == wit1)
    );
}

BOOST_AUTO_TEST_CASE( SimpleTxTest ) {
    cout << "\nSIMPLE TRANSACTION TEST\n" << endl;

    libzerocash::timer_start("Param Generation");
    auto keypair = libzerocash::ZerocashParams::GenerateNewKeyPair(TEST_TREE_DEPTH);
    libzerocash::ZerocashParams p(
        TEST_TREE_DEPTH,
        &keypair
    );
    libzerocash::timer_stop("Param Generation");

    vector<libzerocash::Coin> coins;
    vector<libzerocash::Address> addrs;

    cout << "Creating Addresses and Coins...\n" << endl;
    for(size_t i = 0; i < 5; i++) {
        addrs.push_back(libzerocash::Address::CreateNewRandomAddress());
        coins.push_back(libzerocash::Coin(addrs.at(i).getPublicAddress(), i));
    }
    cout << "Successfully created address and coins.\n" << endl;

    cout << "Creating a Mint Transaction...\n" << endl;
    libzerocash::MintTransaction minttx(coins.at(0));
    cout << "Successfully created a Mint Transaction.\n" << endl;

    cout << "Verifying a Mint Transaction...\n" << endl;
    bool minttx_res = minttx.verify();

    vector<std::vector<bool>> coinValues(5);
    vector<bool> temp_comVal(ZC_CM_SIZE * 8);
    for(size_t i = 0; i < coinValues.size(); i++) {
        libzerocash::convertBytesVectorToVector(coins.at(i).getCoinCommitment().getCommitmentValue(), temp_comVal);
        coinValues.at(i) = temp_comVal;
    }

    cout << "Creating Merkle Tree...\n" << endl;
    libzerocash::IncrementalMerkleTree merkleTree(coinValues, TEST_TREE_DEPTH);
    cout << "Successfully created Merkle Tree.\n" << endl;

    std::vector<bool> index;

    cout << "Creating Witness 1...\n" << endl;
    merkle_authentication_path witness_1(TEST_TREE_DEPTH);
    libzerocash::convertIntToVector(1, index);
    if (merkleTree.getWitness(index, witness_1) == false) {
		BOOST_ERROR("Could not get witness");
	}
    cout << "Successfully created Witness 1.\n" << endl;

    cout << "Creating Witness 2...\n" << endl;
    merkle_authentication_path witness_2(TEST_TREE_DEPTH);
    libzerocash::convertIntToVector(3, index);
    if (merkleTree.getWitness(index, witness_2) == false) {
		cout << "Could not get witness" << endl;
	}
    cout << "Successfully created Witness 2.\n" << endl;

    cout << "Creating coins to spend...\n" << endl;
    libzerocash::Address newAddress3 = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress3 = newAddress3.getPublicAddress();

    libzerocash::Address newAddress4 = libzerocash::Address::CreateNewRandomAddress();
    libzerocash::PublicAddress pubAddress4 = newAddress4.getPublicAddress();

    libzerocash::Coin c_1_new(pubAddress3, 2);
    libzerocash::Coin c_2_new(pubAddress4, 2);
    cout << "Successfully created coins to spend.\n" << endl;

    vector<bool> root_bv(ZC_ROOT_SIZE * 8);
    merkleTree.getRootValue(root_bv);
    vector<unsigned char> rt(ZC_ROOT_SIZE);
    libzerocash::convertVectorToBytesVector(root_bv, rt);

    vector<unsigned char> as(ZC_SIG_PK_SIZE, 'a');

    cout << "Creating a pour transaction...\n" << endl;
    libzerocash::PourTransaction pourtx(1, p,
    		rt,
    		coins.at(1), coins.at(3),
    		addrs.at(1), addrs.at(3),
                1, 3,
                witness_1, witness_2,
    		pubAddress3, pubAddress4,
    		0,
            0,
    		as,
    		c_1_new, c_2_new);
    cout << "Successfully created a pour transaction.\n" << endl;

	std::vector<unsigned char> pubkeyHash(ZC_SIG_PK_SIZE, 'a');

    cout << "Verifying a pour transaction...\n" << endl;
    bool pourtx_res = pourtx.verify(p, pubkeyHash, rt);

    BOOST_CHECK(minttx_res && pourtx_res);
}


#include "utility/line.hpp"
#include "utility/octtree.hpp"
#include "utility/tagged_int.hpp"
#include <boost/optional/optional_io.hpp>
#include <fmt/ostream.h>
#include <range/v3/all.hpp>
#include <deque>

#define CATCH_CONFIG_MAIN
#include <catch.hpp>

using namespace jhmi;

struct test_tag {};
using test_id = jhmi::tagged_int<test_tag>;
struct ln {
  test_id id;
  line<m3> line;
};
std::ostream& operator<<(std::ostream& out, ln const& v) {
  out << v.id;
  return out;
}
cube<m3> extents(ln const& l) {
  return {l.line.p1, l.line.p2};
}
bool operator<(ln const& lhs, ln const& rhs) {
  return lhs.id < rhs.id;
}
auto distance_squared(ln const& l, m3 const& pt) {
  return distance_squared(l.line, pt);
}

TEST_CASE( "Can I add things to the tree?", "[octtree]" ) {
  octtree<ln> tree{cube<m3>{m3{},dbl3{5,5,5}*mm}, 2};
  std::deque<ln> l{
    ln{test_id{1}, line<m3>{dbl3{0,0,0}*mm, dbl3{1,1,1}*mm}},
    ln{test_id{2}, line<m3>{dbl3{3,3,3}*mm, dbl3{4,4,4}*mm}},
    ln{test_id{3}, line<m3>{dbl3{0,4,0}*mm, dbl3{4,0,4}*mm}}};
  auto find_nearest_id = [&](m3 const& pt) {
    auto dist = distance_squared(l[0].line, pt);
    auto id = l[0].id;
    for (auto&& lni : l) {
      auto nd = distance_squared(lni.line, pt);
      if (nd < dist) {
        nd = dist;
        id = lni.id;
      }
    }
    return id;
  };
  REQUIRE(tree.root().objs.empty());
  REQUIRE(!tree.root().sub_nodes);
  tree.add_item(l[0]);
  REQUIRE(tree.root().objs.size() == 1);
  REQUIRE(!tree.root().sub_nodes);
  tree.add_item(l[1]);
  REQUIRE(tree.root().objs.size() == 2);
  REQUIRE(!tree.root().sub_nodes);
  tree.add_item(l[2]);
  REQUIRE(tree.root().objs.size() == 1);
  REQUIRE(tree.root().sub_nodes);
  auto npt = dbl3{1,1,1.05}*mm;
  auto n1 = tree.find_nearest_item(npt);
  REQUIRE(n1);
  REQUIRE(n1->id == find_nearest_id(npt));
  tree.remove_item(l[0]);
  REQUIRE(tree.root().objs.begin()->id == l[2].id);
  REQUIRE(tree.root().sub_nodes[6].objs.begin()->id == l[1].id);
  l.pop_front();
  auto n2 = tree.find_nearest_item(npt);
  REQUIRE(n2);
  REQUIRE(n2->id == find_nearest_id(npt));
}

namespace jhmi {
  auto extents(m3 const& pt) { return cube<m3>{pt, pt}; }
}
TEST_CASE( "Do multiple items work?", "[octtree]" ) {
  namespace rv = ranges::view;
  namespace ra = ranges::action;
  auto tree = octtree<m3>{cube<m3>{m3{}, dbl3{5,5,5} * mm}};
  std::random_device rd;
  auto gen = std::mt19937{rd()};
  auto dist = std::uniform_real_distribution<>{};
  auto rand = [&] { return 5_mm * dist(gen); };
  auto pts = rv::generate_n([&] {
     return m3{rand(), rand(), rand()}; }, 100) | ranges::to_vector;

  for (auto&& p : pts)
    tree.add_item(p);

  auto cpt = dbl3{4,4,4} * mm;
  auto in_tree = tree.find_nearest_items(cpt, 1_mm) | ra::sort(std::less<>{});
  auto in_list = pts | rv::filter([&](m3 const& p) {
     return distance(p - cpt) < 1_mm; }) | ranges::to_vector | ra::sort(std::less<>{});
  REQUIRE(ranges::equal(in_tree, in_list));
}


/******************************************************************************
 *                                                                            *
 * Copyright (C) 2020 Fondazione Istituto Italiano di Tecnologia (IIT)        *
 * All Rights Reserved.                                                       *
 *                                                                            *
 ******************************************************************************/

#include <cmath>

#include <gazebo/common/Plugin.hh>
#include <gazebo/physics/Model.hh>
#include <gazebo/common/Events.hh>
#include <ignition/math/Pose3.hh>

#include <boost/bind.hpp>

#include <yarp/os/Bottle.h>
#include <yarp/os/BufferedPort.h>

namespace gazebo {

/******************************************************************************/
class ModelMover : public gazebo::ModelPlugin
{
    gazebo::physics::ModelPtr model;
    gazebo::event::ConnectionPtr renderer_connection;
    yarp::os::BufferedPort<yarp::os::Bottle> port;

    /**************************************************************************/
    void onWorldFrame() {
        if (auto* b = port.read(false)) {
            if (b->size() >= 3) {
                const auto x = b->get(0).asDouble();
                const auto y = b->get(1).asDouble();
                const auto ang = b->get(2).asDouble() / 2.;
                const auto& cur_pose = model->WorldPose();
                const auto q = cur_pose.Rot() * ignition::math::Quaterniond(std::cos(ang), 0., 0., std::sin(ang));
                ignition::math::Pose3d new_pose(x, y, cur_pose.Pos()[2], q.W(), q.X(), q.Y(), q.Z());
                model->SetWorldPose(new_pose);
            }
        }
    }

public:
    /**************************************************************************/
    void Load(gazebo::physics::ModelPtr model, sdf::ElementPtr) {
        this->model = model;
        port.open("/" + model->GetName() + "/model-mover/delta-pose:i");
        auto bind = boost::bind(&ModelMover::onWorldFrame, this);
        renderer_connection = gazebo::event::Events::ConnectWorldUpdateBegin(bind);
    }

    /**************************************************************************/
    virtual ~ModelMover() {
        if (!port.isClosed()) {
            port.close();
        }
    }
};

}

GZ_REGISTER_MODEL_PLUGIN(gazebo::ModelMover)


/*
 *            Copyright 2009-2017 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.
 * You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
// Overload of uBLAS prod function with MKL/GSL implementations
#include <votca/tools/linalg.h>
//for libxc
#include <votca/xtp/votca_config.h>

#include <votca/xtp/numerical_integrations.h>
#include <boost/math/constants/constants.hpp>
#include <votca/xtp/radial_euler_maclaurin_rule.h>
#include <votca/xtp/sphere_lebedev_rule.h>
#include <votca/xtp/aoshell.h>
#include <votca/tools/constants.h>
#include <numeric>

#include <votca/xtp/aomatrix.h>
#include <fstream>
#include <boost/timer/timer.hpp>
#include <boost/algorithm/string.hpp>

#include <iterator>
#include <string>

namespace votca {
    namespace xtp {
        namespace ub = boost::numeric::ublas;

        double NumericalIntegration::getExactExchange(const string _functional){
#ifdef LIBXC

            double exactexchange=0.0;
            Vxc_Functionals map;
            std::vector<string> strs;

            boost::split(strs, _functional, boost::is_any_of(" "));
            if (strs.size()>2 ) {
                throw std::runtime_error("Too many functional names");
            }
            else if (strs.size()<1 ) {
                throw std::runtime_error("Specify at least one functional");
            }

            for (unsigned i=0;i<strs.size();i++){

                int func_id = map.getID(strs[i]);
                if (func_id<0){
                    exactexchange=0.0;
                    break;
                }
                xc_func_type func;
                if (xc_func_init(&func, func_id, XC_UNPOLARIZED) != 0) {
                    fprintf(stderr, "Functional '%d' not found\n", func_id);
                    exit(1);
                }
                if (exactexchange>0 && func.cam_alpha>0){
                    throw std::runtime_error("You have specified two functionals with exact exchange");
                }
                exactexchange+=func.cam_alpha;

            }
            return exactexchange;

#else
            return 0.0;
#endif

        }

        ub::matrix<double> NumericalIntegration::IntegrateExternalPotential(const std::vector<double>& Potentialvalues){

            ub::matrix<double> ExternalMat = ub::zero_matrix<double>(_basis->AOBasisSize(), _basis->AOBasisSize());
            unsigned nthreads = 1;
            #ifdef _OPENMP
               nthreads = omp_get_max_threads();
            #endif
               std::vector<ub::matrix<double> >vex_thread;
               std::vector<double> Exc_thread=std::vector<double>(nthreads,0.0);
               for(unsigned i=0;i<nthreads;++i){
                   ub::matrix<double> Vex_thread=ub::zero_matrix<double>(ExternalMat.size1());
                   vex_thread.push_back(Vex_thread);
               }

            #pragma omp parallel for
            for (unsigned thread=0;thread<nthreads;++thread){
            for (unsigned i = thread_start[thread]; i < thread_stop[thread]; ++i) {

                GridBox& box = _grid_boxes[i];

                ub::matrix<double> Vex_here=ub::zero_matrix<double>(box.Matrixsize());
                const std::vector<tools::vec>& points=box.getGridPoints();
                const std::vector<double>& weights=box.getGridWeights();

                ub::range one=ub::range(0,1);

                ub::matrix<double> _temp     = ub::zero_matrix<double>(1,box.Matrixsize());

                ub::matrix<double> ao=ub::matrix<double>(1,box.Matrixsize());

                //iterate over gridpoints
                for(unsigned p=0;p<box.size();p++){
                    ao=ub::zero_matrix<double>(1,box.Matrixsize());
                    const std::vector<ub::range>& aoranges=box.getAOranges();
                    const std::vector<const AOShell* > shells=box.getShells();
                    for(unsigned j=0;j<box.Shellsize();++j){
                        const AOShell* shell=shells[j];
                        ub::matrix_range< ub::matrix<double> > aoshell=ub::project(ao,one,aoranges[j]);
                        shell->EvalAOspace(aoshell,points[p]);
                    }

                    double weight=weights[p];
                    ub::matrix<double> _addEX = weight*Potentialvalues[box.getIndexoffirstgridpoint()+p]*ao ;

                    Vex_here+=ub::prod( ub::trans(_addEX), ao);
                }

                box.AddtoBigMatrix(vex_thread[thread],Vex_here);

            }
            }
            for(unsigned i=0;i<nthreads;++i){
                ExternalMat+=vex_thread[i];

               }

            ExternalMat += ub::trans(ExternalMat);
            return ExternalMat;

        }

        void NumericalIntegration::setXCfunctional(const string _functional){

            Vxc_Functionals map;
            std::vector<string> strs;
            boost::split(strs, _functional, boost::is_any_of(" "));
            xfunc_id = 0;

#ifdef LIBXC
            _use_votca = false;
            _use_separate = false;
            cfunc_id = 0;

            if (strs.size() == 1) {
                xfunc_id = map.getID(strs[0]);
                if (xfunc_id < 0) _use_votca = true;
            }

            else if (strs.size() == 2) {
                cfunc_id = map.getID(strs[0]);
                xfunc_id = map.getID(strs[1]);
                _use_separate = true;
            }
            else {
                cout<<"LIBXC "<<strs.size()<<endl;
                throw std::runtime_error("With LIBXC. Please specify one combined or an exchange and a correlation functionals");

            }

            if (!_use_votca){
            if (xc_func_init(&xfunc, xfunc_id, XC_UNPOLARIZED) != 0) {
                fprintf(stderr, "Functional '%d' not found\n", xfunc_id);
                exit(1);
            }

            xc_func_init(&xfunc, xfunc_id, XC_UNPOLARIZED);
            if (xfunc.info->kind!=2 && !_use_separate){
                throw std::runtime_error("Your functional misses either correlation or exchange, please specify another functional, separated by whitespace");
            }

            if (_use_separate) {
                if (xc_func_init(&cfunc, cfunc_id, XC_UNPOLARIZED) != 0) {
                    fprintf(stderr, "Functional '%d' not found\n", cfunc_id);
                    exit(1);
                }
                xc_func_init(&cfunc, cfunc_id, XC_UNPOLARIZED);
                xc_func_init(&xfunc, xfunc_id, XC_UNPOLARIZED);
                if ((xfunc.info->kind+cfunc.info->kind)!=1){
                    throw std::runtime_error("Your functionals are not one exchange and one correlation");
                }
            }
            }
#else
         if (strs.size() == 1) {
                xfunc_id = map.getID(strs[0]);
            }
         else {
                throw std::runtime_error("Running without LIBXC, Please specify one combined or an exchange and a correlation functionals");
         }
#endif
            if(_use_votca){
                cout<<"Warning: VOTCA_PBE does give correct Vxc but incorrect E_xc"<<endl;
            }
            setXC=true;
            return;
        }

        void NumericalIntegration::EvaluateXC(const double rho,const ub::matrix<double>& grad_rho,double& f_xc, double& df_drho, double& df_dsigma){

 #ifdef LIBXC
                    if (_use_votca) {
#endif
                        _xc.getXC(xfunc_id, rho, grad_rho(0,0), grad_rho(0,1), grad_rho(0,2), f_xc, df_drho, df_dsigma);
#ifdef LIBXC
                    }                        // evaluate via LIBXC, if compiled, otherwise, go via own implementation

                    else {

                        double sigma = ub::prod(grad_rho,ub::trans(grad_rho))(0,0);

                        double exc[1];
                        double vsigma[1]; // libxc
                        double vrho[1]; // libxc df/drho
                        switch (xfunc.info->family) {
                            case XC_FAMILY_LDA:
                                xc_lda_exc_vxc(&xfunc, 1, &rho, exc, vrho);
                                break;
                            case XC_FAMILY_GGA:
                            case XC_FAMILY_HYB_GGA:
                                xc_gga_exc_vxc(&xfunc, 1, &rho, &sigma, exc, vrho, vsigma);
                                break;
                        }
                        f_xc = exc[0];
                        df_drho = vrho[0];
                        df_dsigma = vsigma[0];
                        if (_use_separate) {
                            // via libxc correlation part only
                            switch (cfunc.info->family) {
                                case XC_FAMILY_LDA:
                                    xc_lda_exc_vxc(&cfunc, 1, &rho, exc, vrho);
                                    break;
                                case XC_FAMILY_GGA:
                                case XC_FAMILY_HYB_GGA:
                                    xc_gga_exc_vxc(&cfunc, 1, &rho, &sigma, exc, vrho, vsigma);
                                    break;
                            }

                            f_xc += exc[0];
                            df_drho += vrho[0];
                            df_dsigma += vsigma[0];
                        }
                    }
#endif

            return;
        }

        double NumericalIntegration::IntegratePotential(const vec& rvector){

            double result = 0.0;
            assert(density_set && "Density not calculated");

            for (unsigned i = 0; i < _grid_boxes.size(); i++) {

                const std::vector<tools::vec>& points = _grid_boxes[i].getGridPoints();
                const std::vector<double>& weights = _grid_boxes[i].getGridWeights();
                const std::vector<double>& densities = _grid_boxes[i].getGridDensities();
                for (unsigned j = 0; j < points.size(); j++) {
                    double dist = abs(points[j] - rvector);
                    result -= weights[j] * densities[j] / dist;
                }
            }

            return result;
        }

        void NumericalIntegration::SortGridpointsintoBlocks(std::vector< std::vector< GridContainers::integration_grid > >& grid){
            const double boxsize=1;

            std::vector< std::vector< std::vector< std::vector< GridContainers::integration_grid* > > > >  boxes;

            tools::vec min=vec(std::numeric_limits<double>::max());
            tools::vec max=vec(std::numeric_limits<double>::min());

            for ( unsigned i = 0 ; i < grid.size(); i++){
                for ( unsigned j = 0 ; j < grid[i].size(); j++){
                    const tools::vec& pos= grid[i][j].grid_pos;
                    if(pos.getX()>max.getX()){
                        max.x()=pos.getX();
                    }
                    else if(pos.getX()<min.getX()){
                        min.x()=pos.getX();
                    }
                    if(pos.getY()>max.getY()){
                        max.y()=pos.getY();
                    }
                    else if(pos.getY()<min.getY()){
                        min.y()=pos.getY();
                    }
                    if(pos.getZ()>max.getZ()){
                        max.z()=pos.getZ();
                    }
                    else if(pos.getZ()<min.getZ()){
                        min.z()=pos.getZ();
                        }
                    }
                }

            vec molextension=(max-min);
            vec numberofboxes=molextension/boxsize;
            vec roundednumofbox=vec(std::ceil(numberofboxes.getX()),std::ceil(numberofboxes.getY()),std::ceil(numberofboxes.getZ()));

            //creating temparray
            for (unsigned i=0;i<unsigned(roundednumofbox.getX());i++){
                std::vector< std::vector< std::vector< GridContainers::integration_grid* > > > boxes_yz;
                for (unsigned j=0;j<unsigned(roundednumofbox.getY());j++){
                    std::vector< std::vector< GridContainers::integration_grid* > >  boxes_z;
                    for (unsigned k=0;k<unsigned(roundednumofbox.getZ());k++){
                        std::vector< GridContainers::integration_grid* >  box;
                        box.reserve(100);
                        boxes_z.push_back(box);
                    }
                    boxes_yz.push_back(boxes_z);
            }
                boxes.push_back(boxes_yz);
            }

             for ( auto & atomgrid : grid){
                for ( auto & gridpoint : atomgrid){
                    tools::vec pos= gridpoint.grid_pos-min;
                    tools::vec index=pos/boxsize;
                    int i_x=int(index.getX());
                    int i_y=int(index.getY());
                    int i_z=int(index.getZ());
                    boxes[i_x][i_y][i_z].push_back(&gridpoint);
                }
             }

            for ( auto& boxes_xy : boxes){
                for( auto& boxes_z : boxes_xy){
                    for ( auto& box : boxes_z){
                        if( box.size()<1){
                            continue;
                        }
                        GridBox gridbox;

                        for(const auto&point:box){
                            gridbox.addGridPoint(*point);
                        }
                        _grid_boxes.push_back(gridbox);
                    }
                }
            }

            return;
        }

        void NumericalIntegration::FindSignificantShells(){

            for (unsigned i=0;i<_grid_boxes.size();++i){
                GridBox & box=_grid_boxes[i];
                for (AOBasis::AOShellIterator _row = _basis->firstShell(); _row != _basis->lastShell(); _row++) {
                      AOShell* _store=(*_row);
                      const double decay=(*_row)->getMinDecay();
                      const tools::vec& shellpos=(*_row)->getPos();

                      for(const auto& point : box.getGridPoints()){
                          tools::vec dist=shellpos-point;
                          double distsq=dist*dist;
                          // if contribution is smaller than -ln(1e-10), add atom to list
                        if ( (decay * distsq) < 20.7 ){
                            box.addShell(_store);
                            break;
                        }
                      }
                }
                //cout<<box.significant_shells.size()<<" "<<box.grid_pos.size()<<endl;
            }

             std::vector< GridBox > _grid_boxes_copy;

            int combined=0;
            std::vector<bool> Compared=std::vector<bool>(_grid_boxes.size(),false);
            for (unsigned i=0;i<_grid_boxes.size();i++){
                if(Compared[i]){continue;}
                GridBox box=_grid_boxes[i];
                if(box.Shellsize()<1){continue;}
                Compared[i]=true;
                for (unsigned j=i+1;j<_grid_boxes.size();j++){
                    if(GridBox::compareGridboxes(_grid_boxes[i],_grid_boxes[j])){
                        Compared[j]=true;
                        box.addGridBox(_grid_boxes[j]);
                        combined++;
                    }

                }
                _grid_boxes_copy.push_back(box);
            }

            std::vector<unsigned> sizes;
            sizes.reserve(_grid_boxes_copy.size());
            for(auto& box: _grid_boxes_copy){
                sizes.push_back(box.size()*box.Matrixsize());
            }

            std::vector<unsigned> indexes=std::vector<unsigned>(sizes.size());
            std::iota(indexes.begin(), indexes.end(), 0);
            std::sort(indexes.begin(), indexes.end(),[&sizes](unsigned i1, unsigned i2) {return sizes[i1] > sizes[i2];});

             unsigned nthreads = 1;
            #ifdef _OPENMP
               nthreads = omp_get_max_threads();
            #endif

            std::vector<unsigned> scores=std::vector<unsigned>(nthreads,0);
            std::vector< std::vector<unsigned> > indices;
            for (unsigned i=0;i<nthreads;++i){
                std::vector<unsigned> thread_box_indices;
                indices.push_back(thread_box_indices);
            }

            for(const auto index:indexes){
                unsigned thread=0;
                unsigned minimum= std::numeric_limits<unsigned>::max();
                for(unsigned i=0;i<scores.size();++i){
                    if(scores[i]<minimum){
                        minimum=scores[i];
                        thread=i;
                    }
                }
                indices[thread].push_back(index);
                scores[thread]+=sizes[index];
            }

            thread_start=std::vector<unsigned>(0);
            thread_stop=std::vector<unsigned>(0);
            unsigned start=0;
            unsigned stop=0;
            unsigned indexoffirstgridpoint=0;
             _grid_boxes.resize(0);
            for (const std::vector<unsigned>& thread_index:indices){
                thread_start.push_back(start);
                stop=start+thread_index.size();
                thread_stop.push_back(stop);
                start=stop;
                for(const unsigned index:thread_index){
                        GridBox newbox=_grid_boxes_copy[index];
                        newbox.setIndexoffirstgridpoint(indexoffirstgridpoint);
                        indexoffirstgridpoint+=newbox.size();
                        newbox.PrepareForIntegration();
                        _grid_boxes.push_back(newbox);
                }
            }
            return;
        }

        ub::matrix<double> NumericalIntegration::IntegrateVXC(const ub::matrix<double>& _density_matrix){
            ub::matrix<double> Vxc=ub::zero_matrix<double>(_density_matrix.size1());
            EXC = 0;

            unsigned nthreads = 1;
            #ifdef _OPENMP
               nthreads = omp_get_max_threads();
            #endif
               std::vector<ub::matrix<double> >vxc_thread;
               std::vector<double> Exc_thread=std::vector<double>(nthreads,0.0);
               for(unsigned i=0;i<nthreads;++i){
                   ub::matrix<double> Vxc_thread=ub::zero_matrix<double>(_density_matrix.size1());
                   vxc_thread.push_back(Vxc_thread);
               }

            #pragma omp parallel for
             for (unsigned thread=0;thread<nthreads;++thread){
                for (unsigned i = thread_start[thread]; i < thread_stop[thread]; ++i) {

                double EXC_box=0.0;
                GridBox& box = _grid_boxes[i];

                const ub::matrix<double>  DMAT_here=box.ReadFromBigMatrix(_density_matrix);

                ub::matrix<double> Vxc_here=ub::zero_matrix<double>(DMAT_here.size1());
                const std::vector<tools::vec>& points=box.getGridPoints();
                const std::vector<double>& weights=box.getGridWeights();

                ub::range one=ub::range(0,1);
                ub::range three=ub::range(0,3);
                ub::matrix<double> _temp     = ub::matrix<double>(1,box.Matrixsize());
                ub::matrix<double> _tempgrad = ub::matrix<double>(3,box.Matrixsize());
                ub::matrix<double> ao=ub::matrix<double>(1,box.Matrixsize());
                ub::matrix<double> ao_grad=ub::matrix<double>(3,box.Matrixsize());

                //iterate over gridpoints
                for(unsigned p=0;p<box.size();p++){
                    ao=ub::zero_matrix<double>(1,box.Matrixsize());
                    ao_grad=ub::zero_matrix<double>(3,box.Matrixsize());
                    const std::vector<ub::range>& aoranges=box.getAOranges();
                    const std::vector<const AOShell* >& shells=box.getShells();

                    for(unsigned j=0;j<box.Shellsize();++j){

                        ub::matrix_range< ub::matrix<double> > aoshell=ub::project(ao,one,aoranges[j]);
                        ub::matrix_range< ub::matrix<double> > ao_grad_shell=ub::project(ao_grad,three,aoranges[j]);
                        shells[j]->EvalAOspace(aoshell,ao_grad_shell,points[p]);

                    }

                    _temp=ub::prod( ao, DMAT_here);

                    _tempgrad=ub::prod(ao_grad,DMAT_here);

                    double rho=ub::prod(_temp, ub::trans( ao) )(0,0);

                    ub::matrix<double> rho_grad=ub::prod(_temp, ub::trans(ao_grad))+ub::prod(ao,ub::trans(_tempgrad));

		    if ( rho < 1.e-15 ) continue; // skip the rest, if density is very small

                    double f_xc;      // E_xc[n] = int{n(r)*eps_xc[n(r)] d3r} = int{ f_xc(r) d3r }
                    double df_drho;   // v_xc_rho(r) = df/drho
                    double df_dsigma; // df/dsigma ( df/dgrad(rho) = df/dsigma * dsigma/dgrad(rho) = df/dsigma * 2*grad(rho))
                    EvaluateXC( rho,rho_grad,f_xc, df_drho, df_dsigma);

                    double weight=weights[p];
                    ub::matrix<double> _addXC = weight * df_drho * ao *0.5;

                    _addXC+=  2.0*df_dsigma * weight * ub::prod(rho_grad,ao_grad);

                    // Exchange correlation energy
                    EXC_box += weight  * rho * f_xc;

                    Vxc_here+=ub::prod( ub::trans(_addXC), ao);

                }

                box.AddtoBigMatrix(vxc_thread[thread],Vxc_here);

                Exc_thread[thread]+=EXC_box;

            }

            }
            for(unsigned i=0;i<nthreads;++i){
                Vxc+=vxc_thread[i];
                EXC+=Exc_thread[i];
               }
            Vxc+=ub::trans(Vxc);

            return Vxc;
        }

        double NumericalIntegration::IntegrateDensity(const ub::matrix<double>& _density_matrix){

            double N = 0;

            unsigned nthreads = 1;
            #ifdef _OPENMP
               nthreads = omp_get_max_threads();
            #endif

               std::vector<double> N_thread=std::vector<double>(nthreads,0.0);

            #pragma omp parallel for
            for (unsigned thread=0;thread<nthreads;++thread){
             for (unsigned i = thread_start[thread]; i < thread_stop[thread]; ++i) {

                double N_box=0.0;
                GridBox& box = _grid_boxes[i];

                const ub::matrix<double>  DMAT_here=box.ReadFromBigMatrix(_density_matrix);

                ub::matrix<double> Vxc_here=ub::zero_matrix<double>(DMAT_here.size1());
                const std::vector<tools::vec>& points=box.getGridPoints();
                const std::vector<double>& weights=box.getGridWeights();

                ub::range one=ub::range(0,1);

                ub::matrix<double> _temp     = ub::zero_matrix<double>(1,box.Matrixsize());

                ub::matrix<double> ao=ub::matrix<double>(1,box.Matrixsize());

                box.prepareDensity();

                //iterate over gridpoints
                for(unsigned p=0;p<box.size();p++){
                    ao=ub::zero_matrix<double>(1,box.Matrixsize());

                    const std::vector<ub::range>& aoranges=box.getAOranges();
                    const std::vector<const AOShell* > shells=box.getShells();
                    for(unsigned j=0;j<box.Shellsize();++j){
                        const AOShell* shell=shells[j];
                        ub::matrix_range< ub::matrix<double> > aoshell=ub::project(ao,one,aoranges[j]);

                        shell->EvalAOspace(aoshell,points[p]);
                    }

                    _temp=ub::prod( ao, DMAT_here);

                    double rho=ub::prod(_temp, ub::trans( ao) )(0,0);
                    box.addDensity(rho);
                    N_box+=rho*weights[p];

                }

                N_thread[thread]+=N_box;

            }
            }
            for(unsigned i=0;i<nthreads;++i){
                N+=N_thread[i];
               }
            density_set=true;
            return N;
        }

 ub::vector<double> NumericalIntegration::IntegrateGyrationTensor(const ub::matrix<double>& _density_matrix){

            double N = 0;
            double centroid_x = 0.0;
            double centroid_y = 0.0;
            double centroid_z = 0.0;
            double gyration_xx = 0.0;
            double gyration_xy = 0.0;
            double gyration_xz = 0.0;
            double gyration_yy = 0.0;
            double gyration_yz = 0.0;
            double gyration_zz = 0.0;
            ub::vector<double> result=ub::zero_vector<double>(10);

            unsigned nthreads = 1;
            #ifdef _OPENMP
               nthreads = omp_get_max_threads();
            #endif

               std::vector<double> N_thread=std::vector<double>(nthreads,0.0);

               // centroid
	       std::vector<double> centroid_x_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> centroid_y_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> centroid_z_thread=std::vector<double>(nthreads,0.0);

	       // gyration tensor
	       std::vector<double> gyration_xx_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> gyration_xy_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> gyration_xz_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> gyration_yy_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> gyration_yz_thread=std::vector<double>(nthreads,0.0);
	       std::vector<double> gyration_zz_thread=std::vector<double>(nthreads,0.0);

            #pragma omp parallel for
            for (unsigned thread=0;thread<nthreads;++thread){
             for (unsigned i = thread_start[thread]; i < thread_stop[thread]; ++i) {

                double N_box=0.0;
                double centroid_x_box=0.0;
                double centroid_y_box=0.0;
                double centroid_z_box=0.0;
                double gyration_xx_box=0.0;
                double gyration_xy_box=0.0;
                double gyration_xz_box=0.0;
                double gyration_yy_box=0.0;
                double gyration_yz_box=0.0;
                double gyration_zz_box=0.0;

                GridBox& box = _grid_boxes[i];

                const ub::matrix<double>  DMAT_here=box.ReadFromBigMatrix(_density_matrix);

                const std::vector<tools::vec>& points=box.getGridPoints();
                const std::vector<double>& weights=box.getGridWeights();

                ub::range one=ub::range(0,1);

                ub::matrix<double> _temp     = ub::zero_matrix<double>(1,box.Matrixsize());

                ub::matrix<double> ao=ub::matrix<double>(1,box.Matrixsize());

                box.prepareDensity();

                //iterate over gridpoints
                for(unsigned p=0;p<box.size();p++){
                    ao=ub::zero_matrix<double>(1,box.Matrixsize());

                    const std::vector<ub::range>& aoranges=box.getAOranges();
                    const std::vector<const AOShell* > shells=box.getShells();
                    for(unsigned j=0;j<box.Shellsize();++j){
                        const AOShell* shell=shells[j];
                        ub::matrix_range< ub::matrix<double> > aoshell=ub::project(ao,one,aoranges[j]);

                        shell->EvalAOspace(aoshell,points[p]);
                    }

                    _temp=ub::prod( ao, DMAT_here);

                    double rho=ub::prod(_temp, ub::trans( ao) )(0,0);
                    box.addDensity(rho);
                    N_box+=rho*weights[p];
                    centroid_x_box+=rho*weights[p]* points[p].getX();
                    centroid_y_box+=rho*weights[p]* points[p].getY();
                    centroid_z_box+=rho*weights[p]* points[p].getZ();
                    gyration_xx_box+=rho*weights[p]* points[p].getX()*points[p].getX();
                    gyration_xy_box+=rho*weights[p]* points[p].getX()*points[p].getY();
                    gyration_xz_box+=rho*weights[p]* points[p].getX()*points[p].getZ();
                    gyration_yy_box+=rho*weights[p]* points[p].getY()*points[p].getY();
                    gyration_yz_box+=rho*weights[p]* points[p].getY()*points[p].getZ();
                    gyration_zz_box+=rho*weights[p]* points[p].getZ()*points[p].getZ();

                }

                N_thread[thread]+=N_box;
                centroid_x_thread[thread] += centroid_x_box;
                centroid_y_thread[thread] += centroid_y_box;
                centroid_z_thread[thread] += centroid_z_box;
                gyration_xx_thread[thread] += gyration_xx_box;
                gyration_xy_thread[thread] += gyration_xy_box;
                gyration_xz_thread[thread] += gyration_xz_box;
                gyration_yy_thread[thread] += gyration_yy_box;
                gyration_yz_thread[thread] += gyration_yz_box;
                gyration_zz_thread[thread] += gyration_zz_box;

            }
            }
            for(unsigned i=0;i<nthreads;++i){
                N+=N_thread[i];
                centroid_x += centroid_x_thread[i];
                centroid_y += centroid_y_thread[i];
                centroid_z += centroid_z_thread[i];
                gyration_xx += gyration_xx_thread[i];
                gyration_xy += gyration_xy_thread[i];
                gyration_xz += gyration_xz_thread[i];
                gyration_yy += gyration_yy_thread[i];
                gyration_yz += gyration_yz_thread[i];
                gyration_zz += gyration_zz_thread[i];
               }
            density_set=true;

            // Normalize
	    centroid_x = centroid_x/N;
	    centroid_y = centroid_y/N;
	    centroid_z = centroid_z/N;

            gyration_xx = gyration_xx/N;
	    gyration_xy = gyration_xy/N;
	    gyration_xz = gyration_xz/N;
	    gyration_yy = gyration_yy/N;
	    gyration_yz = gyration_yz/N;
	    gyration_zz = gyration_zz/N;

	      // Copy all elements to result vector
	      result(0) = N;
	      result(1) = centroid_x;
	      result(2) = centroid_y;
	      result(3) = centroid_z;
	      result(4) = gyration_xx - centroid_x*centroid_x;
	      result(5) = gyration_xy - centroid_x*centroid_y;
	      result(6) = gyration_xz - centroid_x*centroid_z;
	      result(7) = gyration_yy - centroid_y*centroid_y;
	      result(8) = gyration_yz - centroid_y*centroid_z;
	      result(9) = gyration_zz - centroid_z*centroid_z;

            return result;
        }

        std::vector<const vec *> NumericalIntegration::getGridpoints(){

            std::vector<const vec *> gridpoints;

            for ( unsigned i = 0 ; i < _grid_boxes.size(); i++){
                const std::vector<tools::vec>& points=_grid_boxes[i].getGridPoints();
                for ( unsigned j = 0 ; j < points.size(); j++){
                    gridpoints.push_back(&points[j]);

               }
                }
            return gridpoints;
        }

        void NumericalIntegration::GridSetup(string type, BasisSet* bs, vector<ctp::QMAtom*> _atoms,AOBasis* basis) {
            _basis=basis;
            std::vector< std::vector< GridContainers::integration_grid > > grid;
            const double pi = boost::math::constants::pi<double>();
            // get GridContainer
            GridContainers initialgrids;

            // get radial grid per element
            EulerMaclaurinGrid _radialgrid;
            _radialgrid.getRadialGrid(bs, _atoms, type, initialgrids); // this checks out 1:1 with NWChem results! AWESOME

           map<string, GridContainers::radial_grid>::iterator it;

            LebedevGrid _sphericalgrid;

            for (it = initialgrids._radial_grids.begin(); it != initialgrids._radial_grids.end(); ++it) {
               _sphericalgrid.getSphericalGrid(_atoms, type, initialgrids);

            }

            // for the partitioning, we need all inter-center distances later, stored in one-directional list
            int ij = 0;
            Rij.push_back(0.0); // 1st center "self-distance"

            vector< ctp::QMAtom* > ::iterator ait;
            vector< ctp::QMAtom* > ::iterator bit;
            int i = 1;
            for (ait = _atoms.begin() + 1; ait != _atoms.end(); ++ait) {
                // get center coordinates in Bohr
                vec pos_a = (*ait)->getPos() * tools::conv::ang2bohr;

                int j = 0;
                for (bit = _atoms.begin(); bit != ait; ++bit) {
                    ij++;
                    // get center coordinates in Bohr
                    vec pos_b = (*bit)->getPos() * tools::conv::ang2bohr;

                    Rij.push_back(1.0 / abs(pos_a-pos_b));

                    j++;
                } // atoms
                Rij.push_back(0.0); // self-distance again
                i++;
            } // atoms

            int i_atom = 0;
            _totalgridsize = 0;
            for (ait = _atoms.begin(); ait < _atoms.end(); ++ait) {
                // get center coordinates in Bohr
                std::vector< GridContainers::integration_grid > _atomgrid;
                const vec atomA_pos =(*ait)->getPos() * tools::conv::ang2bohr;

                string name = (*ait)->type;

                // get radial grid information for this atom type
                GridContainers::radial_grid _radial_grid = initialgrids._radial_grids.at(name);

                // get spherical grid information for this atom type
                GridContainers::spherical_grid _spherical_grid = initialgrids._spherical_grids.at(name);

                // maximum order (= number of points) in spherical integration grid
                int maxorder = _sphericalgrid.Type2MaxOrder(name,type);
                int maxindex = _sphericalgrid.getIndexFromOrder(maxorder);

                // for pruning of integration grid, get interval boundaries for this element
                std::vector<double> PruningIntervals = _radialgrid.getPruningIntervals( name );
              //  cout << " Pruning Intervals: " << PruningIntervals[0] << " " << PruningIntervals[1] << " " << PruningIntervals[2] << " " << PruningIntervals[3] << endl;

                int current_order = 0;
                // get spherical grid
                std::vector<double> _theta;
                std::vector<double> _phi;
                std::vector<double> _weight;

                // for each radial value
                for (unsigned _i_rad = 0; _i_rad < _radial_grid.radius.size(); _i_rad++) {
                    double r = _radial_grid.radius[_i_rad];
                    int order;
                    // which Lebedev order for this point?
                    if ( maxindex == 1 ) {
                        // smallest possible grid anyway, nothing to do
                        order = maxorder;
                    } else if ( maxindex == 2 ) {
                        // only three intervals
                        if ( r < PruningIntervals[0] ) {
                            order = _sphericalgrid.getOrderFromIndex(1);//1;
                        } else if ( ( r >= PruningIntervals[0] ) && ( r < PruningIntervals[3] )   ){
                            order = _sphericalgrid.getOrderFromIndex(2);
                        } else {
                            order = _sphericalgrid.getOrderFromIndex(1);
                        } // maxorder == 2
                    } else {
                        // five intervals
                        if ( r < PruningIntervals[0] ) {
                            order = _sphericalgrid.getOrderFromIndex(int(2));
                        } else if ( ( r >= PruningIntervals[0]) && ( r < PruningIntervals[1] ) ) {
                            order = _sphericalgrid.getOrderFromIndex(4);
                        } else if ( ( r >= PruningIntervals[1]) && ( r < PruningIntervals[2] ) ) {
                            order = _sphericalgrid.getOrderFromIndex(max(maxindex-1, 4));
                        } else if ( (r >= PruningIntervals[2]) && ( r < PruningIntervals[3] ) ) {
                            order = maxorder;
                        } else {
                            order = _sphericalgrid.getOrderFromIndex(max(maxindex-1,1));
                        }
                    }

                    // get new spherical grid, if order changed
                    if ( order != current_order ){
                        _theta.clear();
                        _phi.clear();
                        _weight.clear();

                        _sphericalgrid.getUnitSphereGrid(order,_theta,_phi,_weight);
                        current_order = order;
                    }

                    for (unsigned _i_sph = 0; _i_sph < _phi.size(); _i_sph++) {

                        double p   = _phi[_i_sph] * pi / 180.0; // back to rad
                        double t   = _theta[_i_sph] * pi / 180.0; // back to rad
                        double ws  = _weight[_i_sph];

                        const vec s = vec(sin(p) * cos(t), sin(p) * sin(t),cos(p));

                        GridContainers::integration_grid _gridpoint;
                        _gridpoint.grid_pos = atomA_pos+r*s;

                        _gridpoint.grid_weight = _radial_grid.weight[_i_rad] * ws;

                        _atomgrid.push_back(_gridpoint);

                    } // spherical gridpoints
                } // radial gridpoint

                // get all distances from grid points to centers
                std::vector< std::vector<double> > rq;
                // for each center
                for (bit = _atoms.begin(); bit < _atoms.end(); ++bit) {
                    // get center coordinates
                   const vec atom_pos = (*bit)->getPos() * tools::conv::ang2bohr;

                    std::vector<double> temp;
                    // for each gridpoint
                    for (std::vector<GridContainers::integration_grid >::iterator git = _atomgrid.begin(); git != _atomgrid.end(); ++git) {

                        temp.push_back(abs(git->grid_pos-atom_pos));

                    } // gridpoint of _atomgrid
                    rq.push_back(temp);

                } // centers
                // cout << " Calculated all gridpoint distances to centers for " << i_atom << endl;

                // find nearest-neighbor of this atom
                double distNN = 1e10;

                vector< ctp::QMAtom* > ::iterator NNit;
                //int i_NN;

                // now check all other centers
                int i_b =0;
                for (bit = _atoms.begin(); bit != _atoms.end(); ++bit) {

                    if (bit != ait) {
                        // get center coordinates

                        const vec atomB_pos=(*bit)->getPos() * tools::conv::ang2bohr;
                        double distSQ = (atomA_pos-atomB_pos)*(atomA_pos-atomB_pos);

                        // update NN distance and iterator
                        if ( distSQ < distNN ) {
                            distNN = distSQ;
                            NNit = bit;

                        }

                    } // if ( ait != bit)
                    i_b++;
                }// bit centers

                for ( unsigned i_grid = 0; i_grid < _atomgrid.size() ; i_grid++){
                    // call some shit called grid_ssw0 in NWChem
                    std::vector<double> _p = SSWpartition( i_grid, _atoms.size(),rq);

                    // check weight sum
                    double wsum = 0.0;
                    for (unsigned i =0 ; i < _p.size(); i++ ){
                        wsum += _p[i];
                    }
                    //cout << " sum of partition weights " << wsum << endl;
                    if ( wsum != 0.0 ){

                        // update the weight of this grid point
                        _atomgrid[i_grid].grid_weight = _atomgrid[i_grid].grid_weight * _p[i_atom]/wsum;
                        //cout << " adjusting gridpoint weight "  << endl;
                    } else {

                       cerr << "\nSum of partition weights of grid point " << i_grid << " of atom " << i_atom << " is zero! ";
                       throw std::runtime_error("\nThis should never happen!");

                    }

                } // partition weight for each gridpoint

                // now remove points from the grid with negligible weights

                for (std::vector<GridContainers::integration_grid >::iterator git = _atomgrid.begin(); git != _atomgrid.end();) {
                    if (git->grid_weight < 1e-13 ) {
                        git = _atomgrid.erase(git);
                    } else {
                        ++git;
                    }
                }

                _totalgridsize += _atomgrid.size() ;

                grid.push_back(_atomgrid);

                i_atom++;

            } // atoms

            SortGridpointsintoBlocks(grid);
            FindSignificantShells();
            return;
        }

        std::vector<double> NumericalIntegration::SSWpartition(int igrid, int ncenters, std::vector< std::vector<double> >& rq){
            const double ass = 0.725;
            // initialize partition vector to 1.0
            std::vector<double> p(ncenters,1.0);

            const double tol_scr = 1e-10;
            const double leps    = 1e-6;
            // go through centers
            for ( int i = 1; i < ncenters; i++ ){

                int ij = i*(i+1)/2 -1; // indexing magic
                double rag = rq[i][igrid] ;

                // through all other centers (one-directional)
                for (int j = 0; j < i; j++ ){

                    ij++;
                    if ( ( std::abs(p[i]) > tol_scr  ) || ( std::abs(p[j]) > tol_scr  ) ){

                        double mu = ( rag - rq[j][igrid] )*Rij[ij];
                        if ( mu > ass ) {
                            p[i] = 0.0;
                        } else if ( mu < -ass ) {
                            p[j] = 0.0;
                        } else {

                            double sk;
                            if (std::abs(mu) < leps ) {
                                sk = -1.88603178008*mu + 0.5;
                            } else {
                                sk = erf1c(mu);
                            }
                            if ( mu > 0.0 ) sk = 1.0 - sk;
                            p[j] = p[j] * sk;
                            p[i] = p[i] * (1.0-sk);

                        }
                    }
                }

            }

            return p;
        }

        double NumericalIntegration::erf1c(double x){

            const static double alpha_erf1=1.0/0.30;
            return 0.5*erfcc((x/(1.0-x*x))*alpha_erf1);
        }

        double NumericalIntegration::erfcc(double x){

            double tau = 1.0/(1.0+0.5*std::abs(x));

            return tau*exp(-x*x-1.26551223 + 1.00002368*tau + 0.37409196*tau*tau
            + 0.09678418*pow(tau,3) - 0.18628806*pow(tau,4) + 0.27886807*pow(tau,5)
            -1.13520398*pow(tau,6) + 1.48851587*pow(tau,7)  -0.82215223*pow(tau,8)
            + 0.17087277*pow(tau,9));
        }

    }
}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Framework/TestingFramework.hpp"

#include <boost/functional/hash.hpp>
#include <cstddef>
#include <deque>
#include <memory>
#include <unordered_map>
#include <utility>

#include "DataStructures/DataBox/DataBox.hpp"
#include "DataStructures/DataBox/PrefixHelpers.hpp"
#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/DataBox/Tag.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "Domain/CoordinateMaps/Affine.hpp"
#include "Domain/CoordinateMaps/CoordinateMap.hpp"
#include "Domain/CoordinateMaps/CoordinateMap.tpp"
#include "Domain/CoordinateMaps/Identity.hpp"
#include "Domain/CoordinateMaps/ProductMaps.hpp"
#include "Domain/CoordinateMaps/ProductMaps.tpp"
#include "Domain/CoordinateMaps/Tags.hpp"
#include "Domain/CoordinateMaps/Wedge.hpp"
#include "Domain/ElementMap.hpp"
#include "Domain/LogicalCoordinates.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Tags.hpp"
#include "Evolution/DgSubcell/Actions/TakeTimeStep.hpp"
#include "Evolution/DgSubcell/ActiveGrid.hpp"
#include "Evolution/DgSubcell/Mesh.hpp"
#include "Evolution/DgSubcell/Tags/Coordinates.hpp"
#include "Evolution/DgSubcell/Tags/Mesh.hpp"
#include "Evolution/DiscontinuousGalerkin/MortarData.hpp"
#include "Evolution/DiscontinuousGalerkin/MortarTags.hpp"
#include "Framework/ActionTesting.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "Parallel/RegisterDerivedClassesWithCharm.hpp"
#include "Utilities/ErrorHandling/Error.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TaggedTuple.hpp"

namespace {
template <size_t Dim, typename Metavariables>
struct component {
  using metavariables = Metavariables;
  using chare_type = ActionTesting::MockArrayChare;
  using array_index = size_t;

  using initial_tags = tmpl::list<
      evolution::dg::subcell::Tags::Mesh<Dim>,
      domain::Tags::ElementMap<Dim, Frame::Grid>,
      domain::CoordinateMaps::Tags::CoordinateMap<Dim, Frame::Grid,
                                                  Frame::Inertial>,
      evolution::dg::subcell::fd::Tags::InverseJacobianLogicalToGrid<Dim>,
      evolution::dg::subcell::fd::Tags::DetInverseJacobianLogicalToGrid,
      evolution::dg::Tags::MortarData<Dim>>;

  using initial_compute_tags =
      tmpl::list<evolution::dg::subcell::Tags::LogicalCoordinatesCompute<Dim>>;

  using phase_dependent_action_list = tmpl::list<Parallel::PhaseActions<
      typename Metavariables::Phase, Metavariables::Phase::Initialization,
      tmpl::list<
          ActionTesting::InitializeDataBox<initial_tags, initial_compute_tags>,
          evolution::dg::subcell::fd::Actions::TakeTimeStep<
              typename Metavariables::TimeDerivative>>>>;
};

template <size_t Dim>
struct Metavariables {
  static constexpr size_t volume_dim = Dim;
  using component_list = tmpl::list<component<Dim, Metavariables>>;
  using const_global_cache_tags = tmpl::list<>;
  enum class Phase { Initialization, Exit };

  // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
  static bool time_derivative_invoked;

  struct TimeDerivative {
    template <typename DbTagsList>
    static void apply(
        const gsl::not_null<db::DataBox<DbTagsList>*> box,
        const InverseJacobian<DataVector, Dim, Frame::Logical, Frame::Grid>&
            cell_centered_logical_to_grid_inv_jacobian,
        const Scalar<DataVector>& cell_centered_det_inv_jacobian) noexcept {
      time_derivative_invoked = true;
      CHECK(db::get<evolution::dg::subcell::Tags::Mesh<Dim>>(*box) ==
            Mesh<Dim>(5, Spectral::Basis::FiniteDifference,
                      Spectral::Quadrature::CellCentered));
      const auto inv_jacobian =
          db::get<domain::Tags::ElementMap<Dim, Frame::Grid>>(*box)
              .inv_jacobian(db::get<evolution::dg::subcell::Tags::Coordinates<
                                Dim, Frame::Logical>>(*box));
      CHECK(cell_centered_logical_to_grid_inv_jacobian == inv_jacobian);
      const auto det_inv_jacobian = determinant(inv_jacobian);
      CHECK_ITERABLE_APPROX(cell_centered_det_inv_jacobian, det_inv_jacobian);
    }
  };
};

template <size_t Dim>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
bool Metavariables<Dim>::time_derivative_invoked = false;

template <size_t Dim>
auto make_grid_map() {
  using domain::make_coordinate_map_base;
  using domain::CoordinateMaps::Affine;
  if constexpr (Dim == 1) {
    return make_coordinate_map_base<Frame::BlockLogical, Frame::Grid>(
        Affine(-1.0, 1.0, 2.0, 5.0));
  } else if constexpr (Dim == 2) {
    using domain::CoordinateMaps::ProductOf2Maps;
    return make_coordinate_map_base<Frame::BlockLogical, Frame::Grid>(
        ProductOf2Maps<Affine, Affine>(Affine(-1.0, 1.0, -1.0, -0.8),
                                       Affine(-1.0, 1.0, -1.0, -0.8)),
        domain::CoordinateMaps::Wedge<2>(0.5, 0.75, 1.0, 1.0, {}, false));
  } else {
    using domain::CoordinateMaps::ProductOf3Maps;
    return make_coordinate_map_base<Frame::BlockLogical, Frame::Grid>(
        ProductOf3Maps<Affine, Affine, Affine>(Affine(-1.0, 1.0, -1.0, -0.8),
                                               Affine(-1.0, 1.0, -1.0, -0.8),
                                               Affine(-1.0, 1.0, 0.8, 1.0)),
        domain::CoordinateMaps::Wedge<3>(0.5, 0.75, 1.0, 1.0, {}, false));
  }
}

template <size_t Dim>
auto make_inertial_map() {
  return domain::make_coordinate_map_base<Frame::Grid, Frame::Inertial>(
      domain::CoordinateMaps::Identity<Dim>{});
}

template <size_t Dim>
void test() {
  CAPTURE(Dim);
  using metavars = Metavariables<Dim>;
  metavars::time_derivative_invoked = false;

  using comp = component<Dim, metavars>;
  using MockRuntimeSystem = ActionTesting::MockRuntimeSystem<metavars>;
  MockRuntimeSystem runner{{}};

  const Mesh<Dim> subcell_mesh{5, Spectral::Basis::FiniteDifference,
                               Spectral::Quadrature::CellCentered};
  // Set up nonsense mortar data since we only need to check that it got
  // cleared.
  using Key = std::pair<Direction<Dim>, ElementId<Dim>>;
  std::unordered_map<Key, evolution::dg::MortarData<Dim>, boost::hash<Key>>
      mortar_data{};
  evolution::dg::MortarData<Dim> lower_xi_data{};
  lower_xi_data.insert_local_mortar_data(
      TimeStepId{true, 1, Time{Slab{1.2, 7.8}, {1, 10}}},
      subcell_mesh.slice_away(0), std::vector<double>{1.1, 2.43, 7.8});
  const std::pair lower_id{Direction<Dim>::lower_xi(), ElementId<Dim>{1}};
  mortar_data[lower_id] = lower_xi_data;

  ActionTesting::emplace_array_component_and_initialize<comp>(
      &runner, ActionTesting::NodeId{0}, ActionTesting::LocalCoreId{0}, 0,
      {subcell_mesh,
       ElementMap<Dim, Frame::Grid>{ElementId<Dim>{0}, make_grid_map<Dim>()},
       make_inertial_map<Dim>(), std::nullopt, std::nullopt, mortar_data});

  CHECK(ActionTesting::get_databox_tag<comp,
                                       evolution::dg::Tags::MortarData<Dim>>(
            runner, 0)
            .at(lower_id)
            .local_mortar_data()
            .has_value());

  // Invoke the TakeTimeStep action on the runner
  ActionTesting::next_action<comp>(make_not_null(&runner), 0);

  CHECK_FALSE(ActionTesting::get_databox_tag<
                  comp, evolution::dg::Tags::MortarData<Dim>>(runner, 0)
                  .at(lower_id)
                  .local_mortar_data()
                  .has_value());
  CHECK(metavars::time_derivative_invoked);
}

SPECTRE_TEST_CASE("Unit.Evolution.Subcell.Fd.Actions.TakeTimeStep",
                  "[Evolution][Unit]") {
  using Affine = domain::CoordinateMaps::Affine;
  PUPable_reg(SINGLE_ARG(
      domain::CoordinateMap<Frame::BlockLogical, Frame::Grid, Affine>));
  PUPable_reg(SINGLE_ARG(domain::CoordinateMap<
                         Frame::BlockLogical, Frame::Grid,
                         domain::CoordinateMaps::ProductOf2Maps<Affine, Affine>,
                         domain::CoordinateMaps::Wedge<2>>));
  PUPable_reg(
      SINGLE_ARG(domain::CoordinateMap<
                 Frame::BlockLogical, Frame::Grid,
                 domain::CoordinateMaps::ProductOf3Maps<Affine, Affine, Affine>,
                 domain::CoordinateMaps::Wedge<3>>));
  PUPable_reg(
      SINGLE_ARG(domain::CoordinateMap<Frame::Grid, Frame::Inertial,
                                       domain::CoordinateMaps::Identity<1>>));
  PUPable_reg(
      SINGLE_ARG(domain::CoordinateMap<Frame::Grid, Frame::Inertial,
                                       domain::CoordinateMaps::Identity<2>>));
  PUPable_reg(
      SINGLE_ARG(domain::CoordinateMap<Frame::Grid, Frame::Inertial,
                                       domain::CoordinateMaps::Identity<3>>));

  test<1>();
  test<2>();
  test<3>();
}
}  // namespace


/*
* Created by Mark Johnson on 1/6/2018.
*/

#ifndef VEHO_CONDENSE_CLOSEST_EXACT_FILTERS_TO_RANGE_PASS_HPP
#define VEHO_CONDENSE_CLOSEST_EXACT_FILTERS_TO_RANGE_PASS_HPP

#include <limits>
#include <type_traits>

#include <boost/mp11/algorithm.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/mp11/utility.hpp>
#include <boost/mp11/list.hpp>
#include <boost/mp11/map.hpp>

#include <veho/frame.hpp>
#include <veho/frame_matchers.hpp>

#include <veho/facet/receiver/optimization/pass_traits.hpp>

#include <veho/detail/tuple_utils.hpp>
#include <veho/detail/matcher_utils.hpp>

namespace veho {
    namespace facet {
        namespace receiver {
            namespace optimization {
                namespace reduction {
                    namespace detail {
                        template <typename TypeList,
                                std::size_t Idx = 0,
                                typename First = void, typename Second = void,
                                veho::common_frame::id_diff_type MinDiff = std::numeric_limits<veho::common_frame::id_diff_type>::max()>
                        struct get_least_diff_pair {
                            using type_at_idx = boost::mp11::mp_at_c<TypeList, Idx>;

                            using type_at_idx_plus_one = boost::mp11::mp_at_c<TypeList, Idx + 1>;

                            constexpr static const veho::common_frame::id_diff_type current_diff
                                    = type_at_idx_plus_one::id - type_at_idx::id;

                            using next_iter = boost::mp11::mp_if_c<
                                    (current_diff < MinDiff),
                                    get_least_diff_pair<TypeList,
                                            Idx + 1, type_at_idx, type_at_idx_plus_one, current_diff>,
                                    get_least_diff_pair<TypeList, Idx + 1, First, Second, MinDiff>
                            >;

                            using first = typename next_iter::first;

                            using second = typename next_iter::second;

                            constexpr static const veho::common_frame::id_diff_type difference = next_iter::difference;
                        };

                        template <typename TypeList, typename First, typename Second, veho::common_frame::id_diff_type MinDiff>
                        struct get_least_diff_pair<TypeList,
                                boost::mp11::mp_size<TypeList>::value - 1, First, Second, MinDiff> {
                            using first = First;

                            using second = Second;

                            constexpr static const veho::common_frame::id_diff_type difference = MinDiff;
                        };

                        template <typename First, typename Second>
                        struct exact_less {
                            constexpr const static bool value = First::id < Second::id;
                        };

                        template <typename TypeList>
                        struct condense_closest_exact_filter_types_to_range {
                            using exacts = boost::mp11::mp_copy_if<TypeList, veho::detail::is_exact>;

                            static_assert(
                                    boost::mp11::mp_size<exacts>::value >= 2,
                                    "There must be at least two exact frame matchers to run optimization pass `condense_closest_exact_filters_to_range_pass`"
                                    );

                            using sorted_exacts = boost::mp11::mp_sort<exacts, exact_less>;

                            using least_diff_pair = get_least_diff_pair<sorted_exacts>;

                            using new_range = veho::frame_matchers::range<least_diff_pair::first::id, least_diff_pair::second::id>;
                        };

                        template <typename Controller, typename Range, typename MinCallback, typename MaxCallback>
                        struct min_max_only_callback_unifier {
                            constexpr min_max_only_callback_unifier(MinCallback&& min_callback,
                                                                    MaxCallback&& max_callback)
                                    : min_callback(std::forward<MinCallback>(min_callback)),
                                      max_callback(std::forward<MaxCallback>(max_callback)) {
                            }

                            template <typename Dependencies>
                            inline void operator()(Dependencies&& deps, const veho::frame<Controller>& frame) {
                                switch (frame.id) {
                                    case Range::min:
                                        min_callback(std::forward<Dependencies>(deps), frame);
                                        break;
                                    case Range::max:
                                        max_callback(std::forward<Dependencies>(deps), frame);
                                        break;
                                    default:
                                        break;
                                }
                            }

                            MinCallback min_callback;

                            MaxCallback max_callback;
                        };

                        template <typename Controller, typename TypeMap, typename Callbacks, typename Condensation, typename TupleAccumulator, typename FrontType = boost::mp11::mp_front<boost::mp11::mp_front<TypeMap>>>
                        struct update_accumulator {

                            using current_callback = boost::mp11::mp_second<boost::mp11::mp_front<TypeMap>>;

                            constexpr update_accumulator(Callbacks&& callbacks, TupleAccumulator&& accumulator)
                                    : value(veho::detail::tuple_push_back(std::forward<TupleAccumulator>(accumulator),
                                                                          veho::detail::get_tuple_elem_by_type<current_callback>(
                                                                                callbacks))) {}

                            using value_type = boost::mp11::mp_push_back<TupleAccumulator, current_callback>;

                            value_type value;

                        };

                        template <typename Controller, typename TypeMap, typename Callbacks, typename Condensation, typename TupleAccumulator>
                        struct update_accumulator<Controller, TypeMap, Callbacks, Condensation, TupleAccumulator, typename Condensation::least_diff_pair::first> {

                            using first_callback_t = boost::mp11::mp_second<boost::mp11::mp_map_find<TypeMap, typename Condensation::least_diff_pair::first>>;

                            using second_callback_t = boost::mp11::mp_second<boost::mp11::mp_map_find<TypeMap, typename Condensation::least_diff_pair::second>>;

                            using callback_unifier_type = min_max_only_callback_unifier<
                                    Controller,
                                    typename Condensation::new_range,
                                    first_callback_t,
                                    second_callback_t
                            >;

                            constexpr update_accumulator(Callbacks&& callbacks, TupleAccumulator&& accumulator)
                                    : value(veho::detail::tuple_push_back(std::forward<TupleAccumulator>(accumulator),
                                                                          callback_unifier_type(
                                                                                veho::detail::get_tuple_elem_by_type<first_callback_t>(
                                                                                callbacks),
                                                                                veho::detail::get_tuple_elem_by_type<second_callback_t>(
                                                                                callbacks)))) {}

                            using value_type = boost::mp11::mp_push_back<
                                    TupleAccumulator,
                                    callback_unifier_type
                            >;

                            value_type value;
                        };

                        template <typename Controller, typename TypeMap, typename Callbacks, typename Condensation, typename TupleAccumulator>
                        struct update_accumulator<Controller, TypeMap, Callbacks, Condensation, TupleAccumulator, typename Condensation::least_diff_pair::second> {

                            constexpr update_accumulator(Callbacks&&, TupleAccumulator&& accumulator) : value(
                                    std::forward<TupleAccumulator>(accumulator)) {}

                            using value_type = TupleAccumulator;

                            value_type value;
                        };

                        template <typename Controller, typename TypeMap, typename Callbacks, typename Condensation, typename TupleAccumulator = std::tuple<>,
                                std::size_t TypeMapSize = boost::mp11::mp_size<TypeMap>::value>
                        struct closest_exact_filter_values_to_range {
                            using accumulator_update = update_accumulator<Controller, TypeMap, Callbacks, Condensation, TupleAccumulator>;

                            using updated_accumulator_t = typename accumulator_update::value_type;

                            using next_step_t = closest_exact_filter_values_to_range<Controller, boost::mp11::mp_pop_front<TypeMap>, Callbacks, Condensation, updated_accumulator_t>;

                            constexpr closest_exact_filter_values_to_range(Callbacks&& callbacks,
                                                                           TupleAccumulator&& accumulator)
                                    : value(next_step_t(
                                    std::forward<Callbacks>(callbacks),
                                    accumulator_update(std::forward<Callbacks>(callbacks),
                                                       std::forward<TupleAccumulator>(accumulator)).value).value) {}

                            using new_tuple_t = typename next_step_t::new_tuple_t;

                            new_tuple_t value;
                        };

                        template <typename Controller, typename TypeMap, typename Callbacks, typename Condensation, typename TupleAccumulator>
                        struct closest_exact_filter_values_to_range<Controller, TypeMap, Callbacks, Condensation, TupleAccumulator, 0> {
                            constexpr closest_exact_filter_values_to_range(Callbacks&&, TupleAccumulator&& accumulator)
                                    : value(
                                    accumulator) {}

                            using new_tuple_t = TupleAccumulator;

                            new_tuple_t value;
                        };
                    }

                    template <typename TypeMap>
                    struct can_run_condense_closest_exact_filters_to_range_pass {
                        using matcher_list = boost::mp11::mp_map_keys<TypeMap>;

                        using exact_matchers = boost::mp11::mp_copy_if<matcher_list, veho::detail::is_exact>;

                        constexpr static const bool value = boost::mp11::mp_size<exact_matchers>::value >= 2;
                    };

                    template <typename Controller, typename TypeMap, typename Callbacks,
                            typename = typename std::enable_if<can_run_condense_closest_exact_filters_to_range_pass<TypeMap>::value>::type>
                    class condense_closest_exact_filters_to_range_pass {
                    private:
                        using condensed_filters = detail::condense_closest_exact_filter_types_to_range<boost::mp11::mp_map_keys<TypeMap>>;

                        using first_in_closest_pair = typename condensed_filters::least_diff_pair::first;

                        using second_in_closest_pair = typename condensed_filters::least_diff_pair::second;

                        using old_first_map_entry = boost::mp11::mp_map_find<TypeMap, first_in_closest_pair>;

                        using old_first_callback_type = boost::mp11::mp_second<old_first_map_entry>;

                        using old_second_callback_type = boost::mp11::mp_second<boost::mp11::mp_map_find<TypeMap, second_in_closest_pair>>;

                        using new_first_map_entry = boost::mp11::mp_list<
                                typename condensed_filters::new_range,
                                detail::min_max_only_callback_unifier<
                                        Controller,
                                        typename condensed_filters::new_range,
                                        old_first_callback_type,
                                        old_second_callback_type
                                >
                        >;

                    public:
                        constexpr explicit condense_closest_exact_filters_to_range_pass(Callbacks&& callbacks)
                                : new_callbacks(
                                detail::closest_exact_filter_values_to_range<Controller, TypeMap, Callbacks, condensed_filters>(
                                        std::forward<Callbacks>(callbacks), std::make_tuple()).value) {}

                        using pass_category = veho::facet::receiver::optimization::reduction_pass_tag;

                        using updated_type_map_type = boost::mp11::mp_map_erase<
                                boost::mp11::mp_replace<
                                        TypeMap,
                                        old_first_map_entry,
                                        new_first_map_entry
                                >,
                                second_in_closest_pair
                        >;

                        using updated_callbacks_type = typename detail::closest_exact_filter_values_to_range<Controller, TypeMap, Callbacks, condensed_filters>::new_tuple_t;

                        constexpr static const veho::common_frame::id_diff_type num_frames_wasted = condensed_filters::least_diff_pair::difference;

                        updated_callbacks_type new_callbacks;
                    };
                }
            }
        }
    }
}

#endif //VEHO_CONDENSE_CLOSEST_EXACT_FILTERS_TO_RANGE_PASS_HPP