#if defined IS_TEST_NET && defined CREA_ENABLE_SMT

#include <boost/test/unit_test.hpp>

#include <crea/protocol/exceptions.hpp>
#include <crea/protocol/hardfork.hpp>

#include <crea/chain/block_summary_object.hpp>
#include <crea/chain/database.hpp>
#include <crea/chain/history_object.hpp>
#include <crea/chain/crea_objects.hpp>

#include <crea/chain/util/reward.hpp>

#include <crea/plugins/debug_node/debug_node_plugin.hpp>

#include <fc/crypto/digest.hpp>

#include "../db_fixture/database_fixture.hpp"

#include <cmath>

using namespace crea;
using namespace crea::chain;
using namespace crea::protocol;

BOOST_FIXTURE_TEST_SUITE( smt_operation_time_tests, smt_database_fixture )

BOOST_AUTO_TEST_CASE( smt_liquidity_rewards )
{
   using std::abs;

   try
   {
      db->liquidity_rewards_enabled = false;

      ACTORS( (alice)(bob)(sam)(dave)(smtcreator) )

      //Create SMT and give some SMT to creators.
      signed_transaction tx;
      asset_symbol_type any_smt_symbol = create_smt( "smtcreator", smtcreator_private_key, 3);

      generate_block();
      vest( CREA_INIT_MINER_NAME, "alice", ASSET( "10.000 TESTS" ) );
      vest( CREA_INIT_MINER_NAME, "bob", ASSET( "10.000 TESTS" ) );
      vest( CREA_INIT_MINER_NAME, "sam", ASSET( "10.000 TESTS" ) );
      vest( CREA_INIT_MINER_NAME, "dave", ASSET( "10.000 TESTS" ) );

      tx.operations.clear();
      tx.signatures.clear();

      BOOST_TEST_MESSAGE( "Rewarding Bob with TESTS" );

      auto exchange_rate = price( ASSET( "1.250 TESTS" ), asset( 1000, any_smt_symbol ) );

      const account_object& alice_account = db->get_account( "alice" );
      FUND( "alice", asset( 25522, any_smt_symbol ) );
      asset alice_smt = db->get_balance( alice_account, any_smt_symbol );

      FUND( "alice", alice_smt.amount );
      FUND( "bob", alice_smt.amount );
      FUND( "sam", alice_smt.amount );
      FUND( "dave", alice_smt.amount );

      int64_t alice_smt_volume = 0;
      int64_t alice_crea_volume = 0;
      time_point_sec alice_reward_last_update = fc::time_point_sec::min();
      int64_t bob_smt_volume = 0;
      int64_t bob_crea_volume = 0;
      time_point_sec bob_reward_last_update = fc::time_point_sec::min();
      int64_t sam_smt_volume = 0;
      int64_t sam_crea_volume = 0;
      time_point_sec sam_reward_last_update = fc::time_point_sec::min();
      int64_t dave_smt_volume = 0;
      int64_t dave_crea_volume = 0;
      time_point_sec dave_reward_last_update = fc::time_point_sec::min();

      BOOST_TEST_MESSAGE( "Creating Limit Order for CREA that will stay on the books for 30 minutes exactly." );

      limit_order_create_operation op;
      op.owner = "alice";
      op.amount_to_sell = asset( alice_smt.amount.value / 20, any_smt_symbol ) ;
      op.min_to_receive = op.amount_to_sell * exchange_rate;
      op.orderid = 1;
      op.expiration = db->head_block_time() + fc::seconds( CREA_MAX_LIMIT_ORDER_EXPIRATION );

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      BOOST_TEST_MESSAGE( "Waiting 10 minutes" );

      generate_blocks( db->head_block_time() + CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10, true );

      BOOST_TEST_MESSAGE( "Creating Limit Order for SMT that will be filled immediately." );

      op.owner = "bob";
      op.min_to_receive = op.amount_to_sell;
      op.amount_to_sell = op.min_to_receive * exchange_rate;
      op.fill_or_kill = false;
      op.orderid = 2;

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, bob_private_key );
      db->push_transaction( tx, 0 );

      alice_crea_volume += ( asset( alice_smt.amount / 20, any_smt_symbol ) * exchange_rate ).amount.value;
      alice_reward_last_update = db->head_block_time();
      bob_crea_volume -= ( asset( alice_smt.amount / 20, any_smt_symbol ) * exchange_rate ).amount.value;
      bob_reward_last_update = db->head_block_time();

      auto ops = get_last_operations( 1 );
      const auto& liquidity_idx = db->get_index< liquidity_reward_balance_index >().indices().get< by_owner >();
      const auto& limit_order_idx = db->get_index< limit_order_index >().indices().get< by_account >();

      auto reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      auto fill_order_op = ops[0].get< fill_order_operation >();

      BOOST_REQUIRE( fill_order_op.open_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 1 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == asset( alice_smt.amount.value / 20, any_smt_symbol ).amount.value );
      BOOST_REQUIRE( fill_order_op.current_owner == "bob" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 2 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == ( asset( alice_smt.amount.value / 20, any_smt_symbol ) * exchange_rate ).amount.value );

      BOOST_CHECK( limit_order_idx.find( std::make_tuple( "alice", 1 ) ) == limit_order_idx.end() );
      BOOST_CHECK( limit_order_idx.find( std::make_tuple( "bob", 2 ) ) == limit_order_idx.end() );

      BOOST_TEST_MESSAGE( "Creating Limit Order for SMT that will stay on the books for 60 minutes." );

      op.owner = "sam";
      op.amount_to_sell = asset( ( alice_smt.amount.value / 20 ), CREA_SYMBOL );
      op.min_to_receive = asset( ( alice_smt.amount.value / 20 ), any_smt_symbol );
      op.orderid = 3;

      tx.signatures.clear();
      tx.operations.clear();
      tx.operations.push_back( op );
      sign( tx, sam_private_key );
      db->push_transaction( tx, 0 );

      BOOST_TEST_MESSAGE( "Waiting 10 minutes" );

      generate_blocks( db->head_block_time() + CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10, true );

      BOOST_TEST_MESSAGE( "Creating Limit Order for SMT that will stay on the books for 30 minutes." );

      op.owner = "bob";
      op.orderid = 4;
      op.amount_to_sell = asset( ( alice_smt.amount.value / 10 ) * 3 - alice_smt.amount.value / 20, CREA_SYMBOL );
      op.min_to_receive = asset( ( alice_smt.amount.value / 10 ) * 3 - alice_smt.amount.value / 20, any_smt_symbol );

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, bob_private_key );
      db->push_transaction( tx, 0 );

      BOOST_TEST_MESSAGE( "Waiting 30 minutes" );

      generate_blocks( db->head_block_time() + CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10, true );

      BOOST_TEST_MESSAGE( "Filling both limit orders." );

      op.owner = "alice";
      op.orderid = 5;
      op.amount_to_sell = asset( ( alice_smt.amount.value / 10 ) * 3, any_smt_symbol );
      op.min_to_receive = asset( ( alice_smt.amount.value / 10 ) * 3, CREA_SYMBOL );

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      alice_smt_volume -= ( alice_smt.amount.value / 10 ) * 3;
      alice_reward_last_update = db->head_block_time();
      sam_smt_volume += alice_smt.amount.value / 20;
      sam_reward_last_update = db->head_block_time();
      bob_smt_volume += ( alice_smt.amount.value / 10 ) * 3 - ( alice_smt.amount.value / 20 );
      bob_reward_last_update = db->head_block_time();
      ops = get_last_operations( 4 );

      fill_order_op = ops[1].get< fill_order_operation >();
      BOOST_REQUIRE( fill_order_op.open_owner == "bob" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 4 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == asset( ( alice_smt.amount.value / 10 ) * 3 - alice_smt.amount.value / 20, CREA_SYMBOL ).amount.value );
      BOOST_REQUIRE( fill_order_op.current_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 5 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == asset( ( alice_smt.amount.value / 10 ) * 3 - alice_smt.amount.value / 20, any_smt_symbol ).amount.value );

      fill_order_op = ops[3].get< fill_order_operation >();
      BOOST_REQUIRE( fill_order_op.open_owner == "sam" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 3 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == asset( alice_smt.amount.value / 20, CREA_SYMBOL ).amount.value );
      BOOST_REQUIRE( fill_order_op.current_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 5 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == asset( alice_smt.amount.value / 20, any_smt_symbol ).amount.value );

      reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      BOOST_TEST_MESSAGE( "Testing a partial fill before minimum time and full fill after minimum time" );

      op.orderid = 6;
      op.amount_to_sell = asset( alice_smt.amount.value / 20 * 2, any_smt_symbol );
      op.min_to_receive = asset( alice_smt.amount.value / 20 * 2, CREA_SYMBOL );

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      generate_blocks( db->head_block_time() + fc::seconds( CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10.to_seconds() / 2 ), true );

      op.owner = "bob";
      op.orderid = 7;
      op.amount_to_sell = asset( alice_smt.amount.value / 20, CREA_SYMBOL );
      op.min_to_receive = asset( alice_smt.amount.value / 20, any_smt_symbol );

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, bob_private_key );
      db->push_transaction( tx, 0 );

      generate_blocks( db->head_block_time() + fc::seconds( CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10.to_seconds() / 2 ), true );

      ops = get_last_operations( 3 );
      fill_order_op = ops[2].get< fill_order_operation >();

      BOOST_REQUIRE( fill_order_op.open_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 6 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == asset( alice_smt.amount.value / 20, any_smt_symbol ).amount.value );
      BOOST_REQUIRE( fill_order_op.current_owner == "bob" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 7 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == asset( alice_smt.amount.value / 20, CREA_SYMBOL ).amount.value );

      reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      generate_blocks( db->head_block_time() + CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10, true );

      op.owner = "sam";
      op.orderid = 8;

      tx.signatures.clear();
      tx.operations.clear();
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      sign( tx, sam_private_key );
      db->push_transaction( tx, 0 );

      alice_crea_volume += alice_smt.amount.value / 20;
      alice_reward_last_update = db->head_block_time();
      sam_crea_volume -= alice_smt.amount.value / 20;
      sam_reward_last_update = db->head_block_time();

      ops = get_last_operations( 2 );
      fill_order_op = ops[1].get< fill_order_operation >();

      BOOST_REQUIRE( fill_order_op.open_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 6 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == asset( alice_smt.amount.value / 20, any_smt_symbol ).amount.value );
      BOOST_REQUIRE( fill_order_op.current_owner == "sam" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 8 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == asset( alice_smt.amount.value / 20, CREA_SYMBOL ).amount.value );

      reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      BOOST_TEST_MESSAGE( "Trading to give Alice and Bob positive volumes to receive rewards" );

      transfer_operation transfer;
      transfer.to = "dave";
      transfer.from = "alice";
      transfer.amount = asset( alice_smt.amount / 2, any_smt_symbol );

      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( transfer );

      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      op.owner = "alice";
      op.amount_to_sell = asset( 8 * ( alice_smt.amount.value / 20 ), CREA_SYMBOL );
      op.min_to_receive = asset( op.amount_to_sell.amount, any_smt_symbol );
      op.orderid = 9;
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      generate_blocks( db->head_block_time() + CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10, true );

      op.owner = "dave";
      op.amount_to_sell = asset( 7 * ( alice_smt.amount.value / 20 ), any_smt_symbol );;
      op.min_to_receive = asset( op.amount_to_sell.amount, CREA_SYMBOL );
      op.orderid = 10;
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );

      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      sign( tx, dave_private_key );
      db->push_transaction( tx, 0 );

      alice_smt_volume += op.amount_to_sell.amount.value;
      alice_reward_last_update = db->head_block_time();
      dave_smt_volume -= op.amount_to_sell.amount.value;
      dave_reward_last_update = db->head_block_time();

      ops = get_last_operations( 1 );
      fill_order_op = ops[0].get< fill_order_operation >();

      BOOST_REQUIRE( fill_order_op.open_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 9 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == 7 * ( alice_smt.amount.value / 20 ) );
      BOOST_REQUIRE( fill_order_op.current_owner == "dave" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 10 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == 7 * ( alice_smt.amount.value / 20 ) );

      reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "dave" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "dave" ).id );
      BOOST_REQUIRE( reward->cbd_volume == dave_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == dave_crea_volume );
      BOOST_CHECK( reward->last_update == dave_reward_last_update );*/

      op.owner = "bob";
      op.amount_to_sell.amount = alice_smt.amount / 20;
      op.min_to_receive.amount = op.amount_to_sell.amount;
      op.orderid = 11;
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );
      sign( tx, bob_private_key );
      db->push_transaction( tx, 0 );

      alice_smt_volume += op.amount_to_sell.amount.value;
      alice_reward_last_update = db->head_block_time();
      bob_smt_volume -= op.amount_to_sell.amount.value;
      bob_reward_last_update = db->head_block_time();

      ops = get_last_operations( 1 );
      fill_order_op = ops[0].get< fill_order_operation >();

      BOOST_REQUIRE( fill_order_op.open_owner == "alice" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 9 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == alice_smt.amount.value / 20 );
      BOOST_REQUIRE( fill_order_op.current_owner == "bob" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 11 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == alice_smt.amount.value / 20 );

      reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "dave" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "dave" ).id );
      BOOST_REQUIRE( reward->cbd_volume == dave_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == dave_crea_volume );
      BOOST_CHECK( reward->last_update == dave_reward_last_update );*/

      transfer.to = "bob";
      transfer.from = "alice";
      transfer.amount = asset( alice_smt.amount / 5, any_smt_symbol );
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( transfer );
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      op.owner = "bob";
      op.orderid = 12;
      op.amount_to_sell = asset( 3 * ( alice_smt.amount / 40 ), any_smt_symbol );
      op.min_to_receive = asset( op.amount_to_sell.amount, CREA_SYMBOL );
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );
      sign( tx, bob_private_key );
      db->push_transaction( tx, 0 );

      generate_blocks( db->head_block_time() + CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC_HF10, true );

      op.owner = "dave";
      op.orderid = 13;
      op.amount_to_sell = op.min_to_receive;
      op.min_to_receive.symbol = any_smt_symbol;
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );
      sign( tx, dave_private_key );
      db->push_transaction( tx, 0 );

      bob_crea_volume += op.amount_to_sell.amount.value;
      bob_reward_last_update = db->head_block_time();
      dave_crea_volume -= op.amount_to_sell.amount.value;
      dave_reward_last_update = db->head_block_time();

      ops = get_last_operations( 1 );
      fill_order_op = ops[0].get< fill_order_operation >();

      BOOST_REQUIRE( fill_order_op.open_owner == "bob" );
      BOOST_REQUIRE( fill_order_op.open_orderid == 12 );
      BOOST_REQUIRE( fill_order_op.open_pays.amount.value == 3 * ( alice_smt.amount.value / 40 ) );
      BOOST_REQUIRE( fill_order_op.current_owner == "dave" );
      BOOST_REQUIRE( fill_order_op.current_orderid == 13 );
      BOOST_REQUIRE( fill_order_op.current_pays.amount.value == 3 * ( alice_smt.amount.value / 40 ) );

      reward = liquidity_idx.find( db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "alice" ).id );
      BOOST_REQUIRE( reward->cbd_volume == alice_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == alice_crea_volume );
      BOOST_CHECK( reward->last_update == alice_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "bob" ).id );
      BOOST_REQUIRE( reward->cbd_volume == bob_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == bob_crea_volume );
      BOOST_CHECK( reward->last_update == bob_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      reward = liquidity_idx.find( db->get_account( "dave" ).id );
      BOOST_REQUIRE( reward == liquidity_idx.end() );
      /*BOOST_REQUIRE( reward->owner == db->get_account( "dave" ).id );
      BOOST_REQUIRE( reward->cbd_volume == dave_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == dave_crea_volume );
      BOOST_CHECK( reward->last_update == dave_reward_last_update );*/

      auto alice_balance = db->get_account( "alice" ).balance;
      auto bob_balance = db->get_account( "bob" ).balance;
      auto sam_balance = db->get_account( "sam" ).balance;
      auto dave_balance = db->get_account( "dave" ).balance;

      BOOST_TEST_MESSAGE( "Generating Blocks to trigger liquidity rewards" );

      db->liquidity_rewards_enabled = true;
      generate_blocks( CREA_LIQUIDITY_REWARD_BLOCKS - ( db->head_block_num() % CREA_LIQUIDITY_REWARD_BLOCKS ) - 1 );

      BOOST_REQUIRE( db->head_block_num() % CREA_LIQUIDITY_REWARD_BLOCKS == CREA_LIQUIDITY_REWARD_BLOCKS - 1 );
      BOOST_REQUIRE( db->get_account( "alice" ).balance.amount.value == alice_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "bob" ).balance.amount.value == bob_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "sam" ).balance.amount.value == sam_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "dave" ).balance.amount.value == dave_balance.amount.value );

      generate_block();

      //alice_balance += CREA_MIN_LIQUIDITY_REWARD;

      BOOST_REQUIRE( db->get_account( "alice" ).balance.amount.value == alice_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "bob" ).balance.amount.value == bob_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "sam" ).balance.amount.value == sam_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "dave" ).balance.amount.value == dave_balance.amount.value );

      ops = get_last_operations( 1 );

      CREA_REQUIRE_THROW( ops[0].get< liquidity_reward_operation>(), fc::exception );
      //BOOST_REQUIRE( ops[0].get< liquidity_reward_operation>().payout.amount.value == CREA_MIN_LIQUIDITY_REWARD.amount.value );

      generate_blocks( CREA_LIQUIDITY_REWARD_BLOCKS );

      //bob_balance += CREA_MIN_LIQUIDITY_REWARD;

      BOOST_REQUIRE( db->get_account( "alice" ).balance.amount.value == alice_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "bob" ).balance.amount.value == bob_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "sam" ).balance.amount.value == sam_balance.amount.value );
      BOOST_REQUIRE( db->get_account( "dave" ).balance.amount.value == dave_balance.amount.value );

      ops = get_last_operations( 1 );

      CREA_REQUIRE_THROW( ops[0].get< liquidity_reward_operation>(), fc::exception );
      //BOOST_REQUIRE( ops[0].get< liquidity_reward_operation>().payout.amount.value == CREA_MIN_LIQUIDITY_REWARD.amount.value );

      alice_crea_volume = 0;
      alice_smt_volume = 0;
      bob_crea_volume = 0;
      bob_smt_volume = 0;

      BOOST_TEST_MESSAGE( "Testing liquidity timeout" );

      generate_blocks( sam_reward_last_update + CREA_LIQUIDITY_TIMEOUT_SEC - fc::seconds( CREA_BLOCK_INTERVAL / 2 ) - CREA_MIN_LIQUIDITY_REWARD_PERIOD_SEC , true );

      op.owner = "sam";
      op.orderid = 14;
      op.amount_to_sell = ASSET( "1.000 TESTS" );
      op.min_to_receive = ASSET( "1.000 TBD" );
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );

      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      sign( tx, sam_private_key );
      db->push_transaction( tx, 0 );

      generate_blocks( db->head_block_time() + ( CREA_BLOCK_INTERVAL / 2 ) + CREA_LIQUIDITY_TIMEOUT_SEC, true );

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      /*BOOST_REQUIRE( reward == liquidity_idx.end() );
      BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      generate_block();

      op.owner = "alice";
      op.orderid = 15;
      op.amount_to_sell.symbol = any_smt_symbol;
      op.min_to_receive.symbol = CREA_SYMBOL;
      tx.operations.clear();
      tx.signatures.clear();
      tx.operations.push_back( op );
      tx.set_expiration( db->head_block_time() + CREA_MAX_TIME_UNTIL_EXPIRATION );
      sign( tx, alice_private_key );
      db->push_transaction( tx, 0 );

      sam_smt_volume = ASSET( "1.000 TBD" ).amount.value;
      sam_crea_volume = 0;
      sam_reward_last_update = db->head_block_time();

      reward = liquidity_idx.find( db->get_account( "sam" ).id );
      /*BOOST_REQUIRE( reward == liquidity_idx.end() );
      BOOST_REQUIRE( reward->owner == db->get_account( "sam" ).id );
      BOOST_REQUIRE( reward->cbd_volume == sam_smt_volume );
      BOOST_REQUIRE( reward->crea_volume == sam_crea_volume );
      BOOST_CHECK( reward->last_update == sam_reward_last_update );*/

      validate_database();
   }
   FC_LOG_AND_RETHROW();
}

BOOST_AUTO_TEST_SUITE_END()
#endif


/// HEADER
#include <csapex/param/angle_parameter.h>

/// PROJECT
#include <csapex/serialization/parameter_serializer.h>
#include <csapex/serialization/io/std_io.h>
#include <csapex/param/value_parameter.h>

/// SYSTEM
#include <yaml-cpp/yaml.h>
#include <boost/any.hpp>

CSAPEX_REGISTER_PARAMETER_SERIALIZER(AngleParameter)

using namespace csapex;
using namespace param;

AngleParameter::AngleParameter() : ParameterImplementation("angle", ParameterDescription()), angle_(0.0), min_(-M_PI), max_(M_PI)
{
}

AngleParameter::AngleParameter(const std::string& name, const ParameterDescription& description, double angle, double min, double max)
  : ParameterImplementation(name, description), angle_(angle), min_(min), max_(max)
{
    set(angle_);
}

const std::type_info& AngleParameter::type() const
{
    Lock l = lock();
    return typeid(double);
}

std::string AngleParameter::toStringImpl() const
{
    return std::string("[angle: ") + std::to_string(angle_) + "rad, " + std::to_string(angle_ / M_PI * 180.0) + "°]";
}

void AngleParameter::get_unsafe(boost::any& out) const
{
    out = angle_;
}

bool AngleParameter::set_unsafe(const boost::any& v)
{
    double a = boost::any_cast<double>(v);
    if (a < min_ || a >= max_) {
        throw std::out_of_range(std::string("angle ") + std::to_string(a) + " is not in the valid interval [" + std::to_string(min_) + ", " + std::to_string(max_) + "]");
    }

    if (a != angle_) {
        angle_ = a;
        return true;
    }

    return false;
}

double AngleParameter::min() const
{
    return min_;
}
double AngleParameter::max() const
{
    return max_;
}

void AngleParameter::cloneDataFrom(const Clonable& other)
{
    if (const AngleParameter* angle = dynamic_cast<const AngleParameter*>(&other)) {
        if (angle_ != angle->angle_) {
            *this = *angle;
            triggerChange();
        }
    } else if (const ValueParameter* value = dynamic_cast<const ValueParameter*>(&other)) {
        if (angle_ != value->as<double>()) {
            angle_ = value->as<double>();
            triggerChange();
        }
    } else {
        throw std::runtime_error("bad setFrom, invalid types");
    }
}

void AngleParameter::doSerialize(YAML::Node& n) const
{
    n["value"] = angle_;
    n["min"] = min_;
    n["max"] = max_;
}

void AngleParameter::doDeserialize(const YAML::Node& n)
{
    if (!n["value"].IsDefined()) {
        return;
    }

    double a = n["value"].as<double>();
    set(a);
    // min_ = n["min"].as<double>();
    // max_ = n["max"].as<double>();
}

void AngleParameter::serialize(SerializationBuffer& data, SemanticVersion& version) const
{
    Parameter::serialize(data, version);

    data << angle_;
    data << min_;
    data << max_;
}

void AngleParameter::deserialize(const SerializationBuffer& data, const SemanticVersion& version)
{
    Parameter::deserialize(data, version);

    data >> angle_;
    data >> min_;
    data >> max_;
}


#pragma once

// system includes -----------------------------------------------
#include <omp.h>
#include <Eigen/Core>
#include <Eigen/Sparse>
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <boost/multi_array.hpp>
#include <functional>
#include <iomanip>
#include <iostream>
// own includes --------------------------------------------------
#include "aux/message.hpp"
#include "aux/timer.hpp"
#include "collision_tensor/assembly/gain.hpp"
#include "collision_tensor/assembly/loss.hpp"
#include "collision_tensor/assembly/scheduler.hpp"
#include "enum/enum.hpp"
#include "matrix/assembly/velocity_var_form.hpp"
#include "quadrature/quadrature_handler.hpp"
#include "spectral/basis/toolbox/spectral_basis.hpp"

// #include "collision_tensor/assembly/outer.hpp"
#include "spectral/laguerren_ks.hpp"
#include "spectral/pl_radial_eval.hpp"

namespace boltzmann {
namespace collision_tensor_assembly {

namespace local_ {
class SparseMatrixWrapper
{
 public:
  SparseMatrixWrapper(unsigned int n)
      : matrix_(n, n)
  { /* empty */ }

  void insert(unsigned int i, unsigned int j, double v)
  {
    constexpr const double tol = 1e-11;
    if (std::abs(v) > tol) matrix_.insert(i, j) = v;
  }

  const Eigen::SparseMatrix<double>& get() { return matrix_; }

 private:
  Eigen::SparseMatrix<double> matrix_;
};

// ----------------------------------------------------------------------
template <typename S, typename M>
void
make_mass_matrix(M& m, const S& s)
{
  for (auto it = s.begin(); it != s.end(); ++it) {
    const int j1 = it->row;
    const int j2 = it->col;
    const double val = it->val;
    m.insert(j1, j2) = val;
  }
  m.makeCompressed();
}

}  // end namespace local_

// ----------------------------------------------------------------------
class CollisionOperatorBase
{
 public:
  CollisionOperatorBase() { /* empty */}

 protected:
  template <typename SPECTRAL_BASIS, typename QUAD>
  void init(const SPECTRAL_BASIS& test_basis,
            const SPECTRAL_BASIS& trial_basis,
            const QUAD& quad,
            const double beta = 2.0);

  template <typename EXPORTER>
  void export_mass_matrix(EXPORTER& exporter, std::size_t n);

 protected:
  typedef boost::multi_array<double, 2> array_t;
  /// shared across threads
  array_t basis_functions;
  VelocityVarForm<2> var_form;

  std::vector<std::size_t> Loffsets;
};

// ----------------------------------------------------------------------
template <typename SPECTRAL_BASIS, typename QUAD>
void
CollisionOperatorBase::init(const SPECTRAL_BASIS& test_basis,
                            const SPECTRAL_BASIS& trial_basis,
                            const QUAD& quad,
                            const double beta)
{
  const unsigned int n_dofs = trial_basis.n_dofs();
  const unsigned int n_quad_points = quad.size();

  typedef typename SPECTRAL_BASIS::elem_t elem_t;

  // angular basis
  typedef typename std::tuple_element<0, typename SPECTRAL_BASIS::elem_t::container_t>::type
      angular_elem_t;
  typename elem_t::Acc::template get<angular_elem_t> acc_ang;

  unsigned int K = spectral::get_max_k(trial_basis);
  LaguerreNKS<double> L_rad(K);
  PLRadialEval<decltype(L_rad), elem_t> L_poly_eval(L_rad);

  unsigned int nqa = quad.dims()[0];
  unsigned int nqr = quad.dims()[1];

  std::vector<double> pts_r(nqr);
  for (unsigned int i = 0; i < nqr; ++i) {
    pts_r[i] = quad.pts(i)[1];
  }

  L_rad.compute(pts_r.data(), nqr, 1 / beta);

  basis_functions.resize(boost::extents[n_dofs][n_quad_points]);

  for (auto elem_it = trial_basis.begin(); elem_it < trial_basis.end(); ++elem_it) {
    unsigned int idx = elem_it - trial_basis.begin();
    // TODO: use LaguerreNKS for evaluation of basis functions
    for (unsigned int iqa = 0; iqa < nqa; ++iqa) {
      const double phi = quad.pts(iqa * nqr)[0];
      const double fa = acc_ang(*elem_it).evaluate(phi);
      for (unsigned int iqr = 0; iqr < nqr; ++iqr) {
        unsigned int q = iqa * nqr + iqr;
        const double r = quad.pts(iqr)[1];
        basis_functions[idx][q] =
            L_poly_eval(*elem_it, iqr) * fa * (quad.wts(q) * std::exp(r * r / beta));
      }
    }
  }

  var_form.init(test_basis, trial_basis, beta);

  // compute l-index offsets for basis
  unsigned int L = spectral::get_max_l(trial_basis);

  Loffsets.resize(L + 2);
  std::vector<unsigned int> Lvalues;

  typedef typename SPECTRAL_BASIS::elem_t elem_t;
  typedef typename std::tuple_element<0, typename elem_t::container_t>::type angular_elem_t;
  typename elem_t::Acc::template get<angular_elem_t> get_xir;
  for_each(trial_basis.begin(), trial_basis.end(), [&](const elem_t& e) {
    Lvalues.push_back(get_xir(e).get_id().l);
  });
  for (unsigned int i = 0; i < L + 2; ++i) {
    Loffsets[i] = std::lower_bound(Lvalues.begin(), Lvalues.end(), i) - Lvalues.begin();
  }
}

// ----------------------------------------------------------------------
template <typename EXPORTER>
void
CollisionOperatorBase::export_mass_matrix(EXPORTER& exporter, std::size_t n)
{
  Eigen::SparseMatrix<double> M(n, n);
  local_::make_mass_matrix(M, var_form.get_s0());
  exporter.write_mass_matrix(M);
}

// ----------------------------------------------------------------------
template <typename BASIS_TYPE, enum KERNEL_TYPE, typename QANGLE, typename QRADIAL>
class CollisionOperator { /* abstract template */ };

// ----------------------------------------------------------------------
// Implementation:

}  // end namespace collision_tensor_assembly
}  // end namespace boltzmann

// Maxwellian molecules
#include "impl/ct_factory_maxwellian.hxx"
// general variable hard spheres kernel
#include "impl/ct_factory_vhs.hxx"


//
//=======================================================================
// Copyright 2002 Marc Wintermantel (wintermantel@even-ag.ch)
// ETH Zurich, Center of Structure Technologies (www.imes.ethz.ch/st)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#ifndef BOOST_GRAPH_PROFILE_HPP
#define BOOST_GRAPH_PROFILE_HPP

#include <boost/graph/graph_traits.hpp>
#include <boost/detail/numeric_traits.hpp>
#include <boost/graph/bandwidth.hpp>

namespace boost {

  template <typename Graph, typename VertexIndexMap>
  typename graph_traits<Graph>::vertices_size_type
  profile(const Graph& g, VertexIndexMap index)
  {
    typename graph_traits<Graph>::vertices_size_type b = 0;
    typename graph_traits<Graph>::vertex_iterator i, end;
    for (boost::tie(i, end) = vertices(g); i != end; ++i){
      b += ith_bandwidth(*i, g, index) + 1;
    }

    return b;
  }

  template <typename Graph>
  typename graph_traits<Graph>::vertices_size_type
  profile(const Graph& g)
  {
    return profile(g, get(vertex_index, g));
  }

} // namespace boost

#endif // BOOST_GRAPH_PROFILE_HPP


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_MATRIX_SWAP_ROW
#define MTL_MATRIX_SWAP_ROW

#include <boost/numeric/mtl/utility/exception.hpp>
#include <boost/numeric/mtl/utility/is_row_major.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/utility/enable_if.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/interface/vpt.hpp>

namespace mtl {

namespace mat {

    namespace detail {

	template <typename Matrix, typename Orientation>
	inline void swap_row(Matrix& A, typename Collection<Matrix>::size_type i,
			     typename Collection<Matrix>::size_type j, tag::dense, Orientation)
	{
	    // swap(A[irange(i,i+1)][iall], A[irange(j,j+1)][iall];
	    using std::swap;
	    for (typename Collection<Matrix>::size_type k= 0; k < num_cols(A); k++)
		swap(A[i][k], A[j][k]);
	}

	template <typename Matrix>
	inline void swap_row(Matrix&, typename Collection<Matrix>::size_type,
			     typename Collection<Matrix>::size_type, tag::sparse, boost::mpl::true_)
	{
	    MTL_THROW(logic_error("This is not implemented yet."));
	}

	template <typename Matrix>
	inline void swap_row(Matrix&, typename Collection<Matrix>::size_type,
			     typename Collection<Matrix>::size_type, tag::sparse, boost::mpl::false_)
	{
	    MTL_THROW(logic_error("This is an ugly operation and not implemented yet."));
	}

    }
    ///Row i and j are swapped in %matrix A
    template <typename Matrix>
    typename mtl::traits::enable_if_matrix<Matrix>::type
    inline swap_row(Matrix& A, typename Collection<Matrix>::size_type i,
		    typename Collection<Matrix>::size_type j)
    {
    vampir_trace<3039> tracer;
	if (i == j) return;
	detail::swap_row(A, i, j, typename mtl::traits::category<Matrix>::type(),
			 mtl::traits::is_row_major<Matrix>());
    }

} // namespace matrix

namespace vec {
    ///Entry i and j are swapped in %vector v
    template <typename Vector>
    typename mtl::traits::enable_if_vector<Vector>::type
    inline swap_row(Vector& v, typename Collection<Vector>::size_type i,
		    typename Collection<Vector>::size_type j)
    {
    vampir_trace<236> tracer;
	using std::swap;
	if (i == j) return;
	swap(v[i], v[j]);
    }

} // vector

using mat::swap_row;
using vec::swap_row;

} // namespace mtl

#endif // MTL_MATRIX_SWAP_ROW


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

  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/function/insert.hpp>
#include <boost/simd/pack.hpp>
#include <simd_test.hpp>
#include <array>

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

template <typename T, std::size_t N, typename Env>
void test(Env& $)
{
  bs::pack<T, N>  p;
  std::array<T,N> a;

  for(std::size_t i = 0; i < N; ++i)
  {
    bs::insert(p, i, T(i));
    a[i] = T(i);
  }

  bs::pack<T, N> ref(&a[0], &a[0] + N);
  STF_IEEE_EQUAL(ref, p);
}

STF_CASE_TPL("Check dynamic insert on pack" , STF_NUMERIC_TYPES)
{
  static const std::size_t N = boost::simd::pack<T>::static_size;

  test<T, N>($);
  test<T, N/2>($);
  test<T, N*2>($);
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*   Files: mpi_main.cpp clusters.cpp  clusters.h utils.h utils.cpp          */
/*   			dbscan.cpp dbscan.h kdtree2.cpp kdtree2.hpp          */
/*			geometric_partitioning.h geometric_partitioning.cpp  */
/*		    						             */
/*   Description: an mpi implementation of dbscan clustering algorithm       */
/*				using the disjoint set data structure        */
/*                                                                           */
/*   Author:  Md. Mostofa Ali Patwary                                        */
/*            EECS Department, Northwestern University                       */
/*            email: mpatwary@eecs.northwestern.edu                          */
/*                                                                           */
/*   Copyright, 2012, Northwestern University                                */
/*   See COPYRIGHT notice in top-level directory.                            */
/*                                                                           */
/*   Please cite the following publication if you use this package 	     */
/* 									     */
/*   Md. Mostofa Ali Patwary, Diana Palsetia, Ankit Agrawal, Wei-keng Liao,  */
/*   Fredrik Manne, and Alok Choudhary, "A New Scalable Parallel DBSCAN      */
/*   Algorithm Using the Disjoint Set Data Structure", Proceedings of the    */
/*   International Conference on High Performance Computing, Networking,     */
/*   Storage and Analysis (Supercomputing, SC'12), pp.62:1-62:11, 2012.	     */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "geometric_partitioning.h"
//#include <boost/foreach.hpp>

namespace NWUClustering
{
	void get_extra_points(ClusteringAlgo& dbs)
	{
		#ifdef _DEBUG_GP
		MPI_Barrier(MPI_COMM_WORLD);
		double end, start = MPI_Wtime();
		#endif

		int k, rank, nproc, i, j;
        	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
        	MPI_Comm_size(MPI_COMM_WORLD, &nproc);

        	#ifdef _DEBUG
        	if(rank == proc_of_interest) cout << "extra point time part 0 strating " << endl;
        	#endif

		interval* local_box = new interval[dbs.m_pts->m_i_dims];
		compute_local_bounding_box(dbs, local_box);

		// extend the box
		//cout << "proc " << rank << " org: upper "<< local_box[0].upper << " lower " << local_box[0].lower << endl;
		float eps = sqrt(dbs.m_epsSquare);

		for(i = 0; i < dbs.m_pts->m_i_dims; i++)
		{
			local_box[i].upper += eps;
			local_box[i].lower -= eps;
		}

		//cout << "proc " << rank << " ext: upper "<< local_box[0].upper << " lower " << local_box[0].lower << endl;

		// all together all the extending bounding box
		interval* gather_local_box = new interval[dbs.m_pts->m_i_dims * nproc];

        	// gather the local bounding box first
        	MPI_Allgather(local_box, sizeof(interval) * dbs.m_pts->m_i_dims, MPI_BYTE, gather_local_box,
        						sizeof(interval) * dbs.m_pts->m_i_dims, MPI_BYTE, MPI_COMM_WORLD);

		/*#ifdef _DEBUG_GP
		if(rank == proc_of_interest)
		{
			for(i = 0; i < dbs.m_pts->m_i_dims * nproc; i++)
			{
					if(i % dbs.m_pts->m_i_dims == 0)
						cout << "\nproc " << i / dbs.m_pts->m_i_dims << " bbox: ";

					if(i % dbs.m_pts->m_i_dims == dbs.m_pts->m_i_dims - 1)
                		cout << "(" << gather_local_box[i].upper << ", " << gather_local_box[i].lower << ")";
            		else
                		cout << "(" << gather_local_box[i].upper << ", " << gather_local_box[i].lower << "), ";
			}
			cout << endl;
		}
		#endif
		*/

		bool if_inside, overlap;
		int count = 0, gcount;

		vector <float> empty;
		vector <vector <float> > send_buf;
		vector <vector <float> > recv_buf;
		send_buf.resize(nproc, empty);
		recv_buf.resize(nproc, empty);

	        vector <int> empty_i;
        	vector <vector <int> > send_buf_ind;
        	vector <vector <int> > recv_buf_ind;
        	send_buf_ind.resize(nproc, empty_i);
        	recv_buf_ind.resize(nproc, empty_i);

		#ifdef _DEBUG_GP
		MPI_Barrier(MPI_COMM_WORLD);
		end = MPI_Wtime();
		#ifdef _DEBUG
		if(rank == proc_of_interest) cout << "extra point time part 1: " << end - start << endl;
		#endif
		start = end;
		#endif

		for(k = 0; k < nproc; k++)
		{
			if(k == rank) // self
				continue;

			// check the two extended bounding box of proc rank and k. If they don't overlap, there must be no points
			// SHOULD SUBTRACT EPS

			overlap = true;
			for(j = 0; j < dbs.m_pts->m_i_dims; j++)
			{
				if(gather_local_box[rank * dbs.m_pts->m_i_dims + j].lower < gather_local_box[k * dbs.m_pts->m_i_dims +j].lower)
				{
					//if(gather_local_box[rank * dbs.m_pts->m_i_dims + j].upper < gather_local_box[k * dbs.m_pts->m_i_dims + j].lower)
					if(gather_local_box[rank * dbs.m_pts->m_i_dims + j].upper - gather_local_box[k * dbs.m_pts->m_i_dims + j].lower < eps)
					{
						overlap = false;
	                    			break;
					}
				}
				else
				{
                    			//if(gather_local_box[k * dbs.m_pts->m_i_dims + j].upper < gather_local_box[rank * dbs.m_pts->m_i_dims + j].lower)
                    			if(gather_local_box[k * dbs.m_pts->m_i_dims + j].upper - gather_local_box[rank * dbs.m_pts->m_i_dims + j].lower < eps)
					{
                        			overlap = false;
                        			break;
                    			}
				}
			}

			// the two bouding boxes are different, so continue to the next processors
			if(overlap == false)
				continue;

			// get the overlapping regions
			for(i = 0; i < dbs.m_pts->m_i_num_points; i++)
			{
				if_inside = true;
				for(j = 0; j < dbs.m_pts->m_i_dims; j++)
				{
					if(dbs.m_pts->m_points[i][j] < gather_local_box[k * dbs.m_pts->m_i_dims + j].lower ||
						dbs.m_pts->m_points[i][j] > gather_local_box[k * dbs.m_pts->m_i_dims + j].upper)
					{
						if_inside = false;
						break;
					}
				}

				if(if_inside == true)
				{
					for(j = 0; j < dbs.m_pts->m_i_dims; j++)
						send_buf[k].push_back(dbs.m_pts->m_points[i][j]);

					send_buf_ind[k].push_back(i);
					count++;
				}
			}
		}

        	#ifdef _DEBUG_GP
		MPI_Barrier(MPI_COMM_WORLD);
        	end = MPI_Wtime();
        	#ifdef _DEBUG
        	if(rank == proc_of_interest) cout << "extra point time part 2: " << end - start << endl;
        	#endif
        	start = end;
        	#endif

		// now send buf have all the points. Send the size first to everyone
		vector <int> send_buf_size, recv_buf_size;
		send_buf_size.resize(nproc, 0);
		recv_buf_size.resize(nproc, 0);

		/*int tid, irecv[nproc], isend[nproc];
		MPI_Request s_req_recv[nproc], s_req_send[nproc], d_req_send[nproc], d_req_recv[nproc]; // better to malloc the memory
        MPI_Status  s_stat, d_stat_send[nproc], d_stat;

		double start, stop;
	    MPI_Barrier(MPI_COMM_WORLD);
        start = MPI_Wtime();

        for(tid = 0; tid < nproc; tid++)
        	MPI_Irecv(&irecv[tid], 1, MPI_INT, tid, 1000, MPI_COMM_WORLD, &s_req_recv[tid]);

        //int scount = 0, pos;
        int pos;
        for(tid = 0; tid < nproc; tid++)
        {
			isend[tid] = send_buf[tid].size();
            MPI_Isend(&isend[tid], 1, MPI_INT, tid, 1000, MPI_COMM_WORLD, &s_req_send[tid]);
       	}

        for(tid = 0; tid < nproc; tid++)
        {
                MPI_Waitany(nproc, &s_req_recv[0], &pos, &s_stat);
		}

		MPI_Barrier(MPI_COMM_WORLD);

		if(rank == proc_of_interest)
		{
		    stop = MPI_Wtime();
			cout << "Size sending time ISIR " << stop - start << endl;
		}

		MPI_Barrier(MPI_COMM_WORLD);
		start = MPI_Wtime();
		*/

		for(i = 0; i < nproc; i++)
			send_buf_size[i] = send_buf[i].size();

		MPI_Alltoall(&send_buf_size[0], 1, MPI_INT, &recv_buf_size[0], 1, MPI_INT, MPI_COMM_WORLD);
		/*MPI_Barrier(MPI_COMM_WORLD);

		#ifdef _DEBUG
		if(rank == proc_of_interest) cout << "Size sending time AtA " << MPI_Wtime() - start << endl;
		#endif
		*/

		//return;
		int tag = 200, send_count, recv_count;
		MPI_Request req_send[2 * nproc], req_recv[2 * nproc];
		MPI_Status stat_send[2 * nproc], stat_recv;

		recv_count = 0;
		for(i = 0; i < nproc; i++)
		{
			if(recv_buf_size[i] > 0)
			{
				recv_buf[i].resize(recv_buf_size[i], 0);
				recv_buf_ind[i].resize(recv_buf_size[i] / dbs.m_pts->m_i_dims, -1);

				MPI_Irecv(&recv_buf[i][0], recv_buf_size[i], MPI_FLOAT, i, tag, MPI_COMM_WORLD, &req_recv[recv_count++]);
				MPI_Irecv(&recv_buf_ind[i][0], recv_buf_size[i] / dbs.m_pts->m_i_dims, MPI_INT, i, tag + 1, MPI_COMM_WORLD, &req_recv[recv_count++]);
			}
		}

		send_count = 0;
        	for(i = 0; i < nproc; i++)
        	{
            		if(send_buf_size[i] > 0)
            		{
                		MPI_Isend(&send_buf[i][0], send_buf_size[i], MPI_FLOAT, i, tag, MPI_COMM_WORLD, &req_send[send_count++]);
				MPI_Isend(&send_buf_ind[i][0], send_buf_size[i] / dbs.m_pts->m_i_dims, MPI_INT, i, tag + 1, MPI_COMM_WORLD, &req_send[send_count++]);
			}
        	}

		int rtag, rsource, rpos;

		#if _GET_PARTION_STAT == 0
		dbs.allocate_outer(dbs.m_pts->m_i_dims);
		#endif

		//cout << "proc " << rank << " recv_count " << recv_count << endl;

		for(i = 0; i < recv_count; i++)
		{
			MPI_Waitany(recv_count, &req_recv[0], &rpos, &stat_recv);

			rtag = stat_recv.MPI_TAG;
			rsource = stat_recv.MPI_SOURCE;

			if(rtag == tag)
			{
				// process the request
				//cout << "proc " << rank << " add points called " << endl;
				#if _GET_EXTRA_POINT_STAT == 0  // WHY THIS IS HERE??????????????????????????????????????????????????????????????
				dbs.addPoints(rsource, recv_buf_size[rsource], dbs.m_pts->m_i_dims, recv_buf[rsource]);
				#endif
				recv_buf[rsource].clear();
			}
			else if(rtag == tag + 1)
			{
				// postpond this computation and call update points later
				// processing immediately might lead to invalid computation
			}
		}

		//cout << "proc " << rank << " send_count " << send_count << endl;

		// MAY NOT NEED THIS
		if(send_count > 0)
			MPI_Waitall(send_count, &req_send[0], &stat_send[0]);

        	#ifdef _DEBUG_GP
		MPI_Barrier(MPI_COMM_WORLD);
        	end = MPI_Wtime();
	        #ifdef _DEBUG
        	if(rank == proc_of_interest) cout << "extra point time part 3: " << end - start << endl;
        	#endif
        	start = end;
        	#endif

		// got all the points
		// now update the indices of the outer points

		#if _GET_EXTRA_POINT_STAT == 0 // WHY THIS IS HERE??????????????????????????????????????????????????????????????
		dbs.updatePoints(recv_buf_ind);
		#endif

		/*if(rank == 3)
		{
			cout << "proc " << rank << " recv ";
			for(i = 0; i < nproc; i++)
			{
				cout << recv_buf_size[i] << " ";
			}

			cout << endl;
		}
		*/

		//#ifdef _DEBUG
		MPI_Reduce(&count, &gcount, 1, MPI_INT, MPI_SUM, proc_of_interest, MPI_COMM_WORLD);

		//dbs.m_extra_point_per_processor = gcount/nproc; // save extra point per processor for future use if needed
		#ifdef _DEBUG
		if(rank == proc_of_interest)
		{
			cout << "Total extra point " << gcount << endl;
			cout << "Extra point per processor " << gcount/nproc << endl;
		}
		#endif

        	#ifdef _DEBUG_GP
		MPI_Barrier(MPI_COMM_WORLD);
        	end = MPI_Wtime();
	        #ifdef _DEBUG
		if(rank == proc_of_interest) cout << "extra point time part 4: " << end - start << endl;
        	#endif
        	start = end;
        	#endif

		empty.clear();
		send_buf.clear();
		recv_buf.clear();
		send_buf_size.clear();
		recv_buf_size.clear();
		send_buf_ind.clear();
		recv_buf_ind.clear();

        	delete [] gather_local_box;
		delete [] local_box;
	}

	void start_partitioning(ClusteringAlgo& dbs)
	{
		int r_count, s_count, rank, nproc, i, j, k;
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
		MPI_Comm_size(MPI_COMM_WORLD, &nproc);

		// compute the local bouding box for each dimention
		interval* box = new interval[dbs.m_pts->m_i_dims];
		//compute_local_bounding_box(dbs, box);
		// we don't need to compute as we have stored this while reading

		for(i = 0; i < dbs.m_pts->m_i_dims; i++)
		{
			box[i].upper = dbs.m_pts->m_box[i].upper;
			box[i].lower = dbs.m_pts->m_box[i].lower;
		}

		// compute the global bouding box for each dimention
		interval* gbox = new interval[dbs.m_pts->m_i_dims];
		compute_global_bounding_box(dbs, box, gbox, nproc);

		//delete [] box;
		//delete [] gbox;
		//return;

		#ifdef _DEBUG
        	if(rank == proc_of_interest) cout << "Partitioning: Pos 1" << endl;
        	#endif

		// find the loop count for nproc processors
		int internal_nodes, partner_rank, loops, b, color, sub_rank, d, max, sub_nprocs;
		//MPI_Comm new_comm;
		MPI_Status status;

		loops = 0;
		i = nproc;
		internal_nodes = 1;
		while((i = i >> 1) > 0)
		{
			loops++;
			internal_nodes = internal_nodes << 1;
		}

		internal_nodes = internal_nodes << 1;

		//gbox for each node in the tree [ONLY upto to reaching each processor]
		interval** nodes_gbox = new interval*[internal_nodes];
		for(i = 0; i < internal_nodes; i++)
			nodes_gbox[i] = new interval[dbs.m_pts->m_i_dims];

		copy_global_box_to_each_node(dbs, nodes_gbox, gbox, internal_nodes);
		// now each node in the tree has gbox

		/*
 		#ifdef _DEBUG
		if(rank == proc_of_interest)
		{	cout << "proc " << rank << " nodes " << internal_nodes << " loops "<< loops << endl;
			print_points(dbs, rank);
		}
		#endif
		*/

	       	#ifdef _DEBUG
        	if(rank == proc_of_interest) cout << "Partitioning: Pos 2" << endl;
        	#endif

		vector <float> send_buf;
		vector <int>   invalid_pos_as;
		vector <float> recv_buf;

		int pow2_i;
		float median;

		for(i = 0; i < loops; i++)
		{
			pow2_i = POW2(i);
			b  = nproc - (int) (nproc / pow2_i);
			color = (int)((rank & b) / POW2(loops - i ));
			partner_rank = rank ^ (int)(nproc/POW2(i + 1));

			MPI_Comm new_comm;
            		MPI_Comm_split(MPI_COMM_WORLD, color, rank, &new_comm);
           		MPI_Comm_rank(new_comm, &sub_rank);

			//	cout << "i " << i << " proc " << rank << " b " << b << " color " << color << " partner_rank " << partner_rank << " sub_rank " << sub_rank << " pow_2i " << pow2_i << endl;

			if(sub_rank == 0)
			{
				d = 0;
				for(j = 1; j < dbs.m_pts->m_i_dims; j++)
				{

					if(nodes_gbox[pow2_i + color][j].upper - nodes_gbox[pow2_i + color][j].lower >
							nodes_gbox[pow2_i + color][d].upper - nodes_gbox[pow2_i + color][d].lower)
						d = j;
				}
			}

			MPI_Bcast(&d, 1, MPI_INT, 0, new_comm);

            		//#ifdef _DEBUG
			//MPI_Barrier(MPI_COMM_WORLD);
            		//if(rank == proc_of_interest) cout << "At before starting median - round " << i << endl;
            		//#endif

			// compute the median in this dimension
			float median  = get_median(dbs, d, new_comm);

	        	//#ifdef _DEBUG
    	    		//if(rank == proc_of_interest) cout << "median " << median << " round " << i << endl;
			//#endif

			s_count = get_points_to_send(dbs, send_buf, invalid_pos_as, median, d, rank, partner_rank);

			if (rank < partner_rank)
			{
				MPI_Sendrecv(&s_count, 1, MPI_INT, partner_rank, 4, &r_count, 1, MPI_INT, partner_rank, 5, MPI_COMM_WORLD, &status);
				recv_buf.resize(r_count * dbs.m_pts->m_i_dims, 0.0);
			    	MPI_Sendrecv(&send_buf[0], s_count * dbs.m_pts->m_i_dims, MPI_FLOAT, partner_rank, 2,
							&recv_buf[0], r_count * dbs.m_pts->m_i_dims, MPI_FLOAT, partner_rank, 3, MPI_COMM_WORLD, &status);
				send_buf.clear();
			}
			else
			{
				MPI_Sendrecv(&s_count, 1, MPI_INT, partner_rank, 5, &r_count, 1, MPI_INT, partner_rank, 4, MPI_COMM_WORLD, &status);
				recv_buf.resize(r_count * dbs.m_pts->m_i_dims, 0.0);
				MPI_Sendrecv(&send_buf[0], s_count * dbs.m_pts->m_i_dims, MPI_FLOAT, partner_rank, 3,
							&recv_buf[0], r_count * dbs.m_pts->m_i_dims, MPI_FLOAT, partner_rank, 2, MPI_COMM_WORLD, &status);
				send_buf.clear();
			}

			//#ifdef _DEBUG
			//MPI_Barrier(MPI_COMM_WORLD);
    	    		//if(rank == proc_of_interest) cout << "AT before update points - round " << i << endl;
			//#endif

			update_points(dbs, s_count, invalid_pos_as, recv_buf);
			recv_buf.clear();

			//#ifdef _DEBUG
			//MPI_Barrier(MPI_COMM_WORLD);
    	    		//if(rank == proc_of_interest) cout << "AT after update points - round " << i << endl;
			//#endif

			/*
			#ifdef _DEBUG
			if(rank == proc_of_interest)
			{
				cout << "proc " << rank << " sub proc " << sub_rank << " median " << median << " of dimension " << d << " points to recv " << r_count << " points to send " << s_count << " partner " << partner_rank << endl;

				print_points(dbs, rank);
			}
			#endif

			#ifdef _DEBUG
			cout << "proc " << rank << " round " << i << " points " << dbs.m_pts->m_i_num_points << endl;
			#endif
			*/

			//#ifdef _DEBUG
			//MPI_Barrier(MPI_COMM_WORLD);
    	    		//if(rank == proc_of_interest) cout << "AT before copying box - round " << i << endl;
			//#endif

			copy_box(dbs, nodes_gbox[LOWER(pow2_i+color)], nodes_gbox[pow2_i+color]);
			nodes_gbox[LOWER(pow2_i+color)][d].upper =  median;
			copy_box(dbs, nodes_gbox[UPPER(pow2_i+color)], nodes_gbox[pow2_i+color]);
			nodes_gbox[UPPER(pow2_i+color)][d].lower =  median;

            		/*
  			#ifdef _DEBUG
			MPI_Barrier(MPI_COMM_WORLD);
            		if(rank == proc_of_interest) cout << "AT after copying box - round " << i << " rank " << rank << endl;
            		#endif

			#ifdef _DEBUG
			cout << "proc " << rank << " round " << i << " points " << dbs.m_pts->m_i_num_points << endl;
			#endif
			*/
			MPI_Comm_free(&new_comm);
		}

		#ifdef _DEBUG
        	if(rank == proc_of_interest) cout << "Partitioning: Pos 3" << endl;
        	#endif

		/*#ifdef _DEBUG
       		cout << "proc " << rank << " points " << dbs.m_pts->m_i_num_points << endl;
        	#endif
		*/

		/*compute_local_bounding_box(dbs, box);

		#ifdef _DEBUG
		if(rank == proc_of_interest)
		{
			print_box(dbs, rank, box);
		}
		#endif
		*/

		// free the allocated memory
		for(i = 0; i < nproc; i++)
			delete [] nodes_gbox[i];

		delete [] nodes_gbox;
		delete [] gbox;
		delete [] box;

		// free communicator
		 //MPI_Comm_free(&new_comm);
	}

	void update_points(ClusteringAlgo& dbs, int s_count, vector <int>& invalid_pos_as, vector <float>& recv_buf)
	{
		int i, j, k, l, r_count = recv_buf.size() / dbs.m_pts->m_i_dims;

		//cout << "r_count " << r_count << " s_count " << s_count << endl;

		if(r_count >= s_count)
		{
			//invalid_pos_as.reserve(dbs.m_pts->m_i_num_points + r_count - s_count);
			invalid_pos_as.resize(dbs.m_pts->m_i_num_points + r_count - s_count, 1);

			//dbs.m_pts->m_points.reserve(dbs.m_pts->m_i_num_points + r_count - s_count);
			//dbs.m_pts->m_points.resize(extents[dbs.m_pts->m_i_num_points + r_count - s_count][dbs.m_pts->m_i_dims]);

			//allocate memory for the points
                	dbs.m_pts->m_points.resize(dbs.m_pts->m_i_num_points + r_count - s_count);
                	for(int ll = 0; ll < dbs.m_pts->m_i_num_points + r_count - s_count; ll++)
                		dbs.m_pts->m_points[ll].resize(dbs.m_pts->m_i_dims);

			j = 0;
			for(i = 0; i < invalid_pos_as.size(); i++)
			{
				if(invalid_pos_as[i] == 1)
				{
					for(k = 0; k < dbs.m_pts->m_i_dims; k++)
						dbs.m_pts->m_points[i][k] = recv_buf[j++];
				}
			}

			dbs.m_pts->m_i_num_points = dbs.m_pts->m_i_num_points + r_count - s_count;
		}
		else
		{
			j = 0;
			i = 0;
			if(recv_buf.size() > 0)
			{
				for(i = 0; i < dbs.m_pts->m_i_num_points; i++)
				{
					if(invalid_pos_as[i] == 1)
					{
						for(k = 0; k < dbs.m_pts->m_i_dims; k++)
							dbs.m_pts->m_points[i][k] = recv_buf[j++];

						if(j == recv_buf.size())
						{
							i++;
							break;
						}
					}
				}
			}

			l = dbs.m_pts->m_i_num_points;
			for( ; i < invalid_pos_as.size(); i++)
			{
				if(invalid_pos_as[i] == 1)
				{
					while(l > i)
					{
						l--;
						if(invalid_pos_as[l] == 0)
							break;
					}

					if(invalid_pos_as[l] == 0)
						for(k = 0; k < dbs.m_pts->m_i_dims; k++)
							dbs.m_pts->m_points[i][k] = dbs.m_pts->m_points[l][k];
				}
			}

			//dbs.m_pts->m_points.resize(extents[dbs.m_pts->m_i_num_points + r_count - s_count][dbs.m_pts->m_i_dims]);

			//allocate memory for the points
                	dbs.m_pts->m_points.resize(dbs.m_pts->m_i_num_points + r_count - s_count);
                	for(int ll = 0; ll < dbs.m_pts->m_i_num_points + r_count - s_count; ll++)
                		dbs.m_pts->m_points[ll].resize(dbs.m_pts->m_i_dims);

			dbs.m_pts->m_i_num_points = dbs.m_pts->m_i_num_points + r_count - s_count;
		}
	}

	int get_points_to_send(ClusteringAlgo& dbs, vector <float>& send_buf, vector <int>& invalid_pos_as, float median, int d, int rank, int partner_rank)
	{
		int i, count = 0, j;
		send_buf.reserve(dbs.m_pts->m_i_num_points * dbs.m_pts->m_i_dims);
		invalid_pos_as.clear();
		invalid_pos_as.resize(dbs.m_pts->m_i_num_points, 0);

		for(i = 0; i < dbs.m_pts->m_i_num_points; i++)
		{
			if (rank < partner_rank)
			{
				if(dbs.m_pts->m_points[i][d] > median)
				{
					invalid_pos_as[i] = 1;
					count++;
					for(j = 0; j < dbs.m_pts->m_i_dims; j++)
						send_buf.push_back(dbs.m_pts->m_points[i][j]);
				}
			}
			else
			{
                if(dbs.m_pts->m_points[i][d] <= median)
                {
                    invalid_pos_as[i] = 1;
                    count++;
                    for(j = 0; j < dbs.m_pts->m_i_dims; j++)
                        send_buf.push_back(dbs.m_pts->m_points[i][j]);
                }
			}
		}

		return count;
	}

	float get_median(ClusteringAlgo& dbs, int d, MPI_Comm& new_comm)
	{
		/*
		double sum, g_sum;
		//float sum, g_sum;

		sum = 0.0;
		g_sum = 0.0;

    	for (int k = 0; k < dbs.m_pts->m_i_num_points; k++)
      		sum += dbs.m_pts->m_points[k][d];

		//MPI_Allreduce(&sum, &g_sum, 1, MPI_FLOAT, MPI_SUM, new_comm);
		MPI_Allreduce(&sum, &g_sum, 1, MPI_DOUBLE, MPI_SUM, new_comm);

		int g_point_count = 0;
		MPI_Allreduce(&dbs.m_pts->m_i_num_points, &g_point_count, 1, MPI_INT, MPI_SUM, new_comm);

		//return g_sum / static_cast<float>(g_point_count);
		return g_sum / static_cast<double>(g_point_count);
		*/

		// ADDITIONAL CODE
		float median;

		vector <float> data;
		data.reserve(dbs.m_pts->m_i_num_points);
		data.resize(dbs.m_pts->m_i_num_points, 0);

		for (int k=0; k < dbs.m_pts->m_i_num_points; k++)
	      	//data.push_back(dbs.m_pts->m_points[k][d]);
	      	data[k] = dbs.m_pts->m_points[k][d];

		/*
       	#ifdef _DEBUG
		int rank;
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
        //MPI_Barrier(new_comm);
        MPI_Barrier(MPI_COMM_WORLD);
       	//if(rank == proc_of_interest)
       	cout << "AT median Pos 0 - rank " << rank << " data size " << data.size() << endl;
        #endif
		*/

		median = findKMedian(data, data.size()/2);
		data.clear();

		#ifdef _DEBUG
		int rank;
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
		if(rank == proc_of_interest) cout << "Median VALUE " << median << endl;
		#endif

       	/*#ifdef _DEBUG
		//int rank;
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
        //MPI_Barrier(new_comm);
        MPI_Barrier(MPI_COMM_WORLD);
       	//if(rank == proc_of_interest)
		cout << "AT median Pos 1 - rank " << rank << " data size " << data.size() << endl;
        #endif
		*/

		int proc_count;
		MPI_Comm_size(new_comm, &proc_count);

	    //#ifdef _DEBUG
		//int rank;
		//MPI_Comm_rank(new_comm, &rank);
    	//if(rank == proc_of_interest) cout << "proc_count " << proc_count << endl;
		//#endif

		//cout << "proc count " << proc_count << endl;

		vector <float> all_medians;
		all_medians.resize(proc_count, 0);

		MPI_Allgather(&median, sizeof(int), MPI_BYTE, &all_medians[0], sizeof(int), MPI_BYTE, new_comm);

  		median = findKMedian(all_medians, all_medians.size()/2);
		all_medians.clear();

		return median;
	}

	void compute_local_bounding_box(ClusteringAlgo& dbs, interval* box)
	{
		int i, j;

		//we assume each processor has at least one point
		//if(dbs.m_pts->m_i_num_points > 0)
		{

			for(i = 0; i < dbs.m_pts->m_i_dims; i++)
			{
				box[i].upper = dbs.m_pts->m_points[0][i];
				box[i].lower = dbs.m_pts->m_points[0][i];
			}

			for(i = 0; i < dbs.m_pts->m_i_dims; i++)
			{
				for(j = 1; j < dbs.m_pts->m_i_num_points; j++)
				{
					if(box[i].lower > dbs.m_pts->m_points[j][i])
						box[i].lower = dbs.m_pts->m_points[j][i];
					else if(box[i].upper < dbs.m_pts->m_points[j][i])
                	    box[i].upper = dbs.m_pts->m_points[j][i];
				}
				//if(rank == 0)
    	    		//  cout << "proc " << rank << " upper " << box[i].upper << " lower " << box[i].lower << endl;
			}
		}
		/*else
		{
			// WHAT TO DO IF THERE IS NO POINTS
			// FOR THE TIME BEING set 0
			for(i = 0; i < dbs.m_pts->m_i_dims; i++)
			{
				box[i].upper = 0;
				box[i].lower = 0;
			}
		}*/
	}

	void compute_global_bounding_box(ClusteringAlgo& dbs, interval* box, interval* gbox, int nproc)
	{
		int i, j, k;

		interval* gather_local_box = new interval[dbs.m_pts->m_i_dims * nproc];

		// gather the local bounding box first
		MPI_Allgather(box, sizeof(interval) * dbs.m_pts->m_i_dims, MPI_BYTE, gather_local_box,
					sizeof(interval) * dbs.m_pts->m_i_dims, MPI_BYTE, MPI_COMM_WORLD);

		// compute the global bounding box
		for(i = 0; i < dbs.m_pts->m_i_dims; i++)
		{
			gbox[i].lower = gather_local_box[i].lower;
			gbox[i].upper = gather_local_box[i].upper;

			k = i;
			for(j = 0; j < nproc; j++, k += dbs.m_pts->m_i_dims)
			{
				if(gbox[i].lower > gather_local_box[k].lower)
					gbox[i].lower = gather_local_box[k].lower;

				if(gbox[i].upper < gather_local_box[k].upper)
                    gbox[i].upper = gather_local_box[k].upper;
			}
		}

		delete [] gather_local_box;
	}

	void copy_global_box_to_each_node(ClusteringAlgo& dbs, interval** nodes_gbox, interval* gbox, int internal_nodes)
	{
        int i, j;
        for(i = 0; i < internal_nodes; i++)
        {
            for(j = 0; j < dbs.m_pts->m_i_dims; j++)
            {
                nodes_gbox[i][j].upper = gbox[j].upper;
                nodes_gbox[i][j].lower = gbox[j].lower;
            }
        }
	}

	void copy_box(ClusteringAlgo& dbs, interval* target_box, interval* source_box)
	{
			for(int j = 0; j < dbs.m_pts->m_i_dims; j++)
			{
				target_box[j].upper = source_box[j].upper;
				target_box[j].lower = source_box[j].lower;
			}
	}

	void print_points(ClusteringAlgo& dbs, int rank)
	{
		cout << "proc " << rank << " owned points: " << dbs.m_pts->m_points.size() << " verify " << dbs.m_pts->m_i_num_points << endl;
		int u, v;

		/*
		for(u = 0; u < dbs.m_pts->m_i_num_points; u++)
        {
        	for(v = 0; v < dbs.m_pts->m_i_dims; v++)
            	cout << dbs.m_pts->m_points[u][v] << " ";
            cout << endl;
        }
		*/

		cout << "proc " << rank << " outer points: " << dbs.m_pts_outer->m_points.size() << " verify " << dbs.m_pts_outer->m_i_num_points << endl;

		/*
		for(u = 0; u < dbs.m_pts_outer->m_i_num_points; u++)
        {
            for(v = 0; v < dbs.m_pts_outer->m_i_dims; v++)
                cout << dbs.m_pts_outer->m_points[u][v] << " ";
            cout << endl;
        }
		*/
	}

    void print_box(ClusteringAlgo& dbs, int rank, interval* box)
    {
        cout << "proc " << rank << " bbox: ";

		for(int v = 0; v < dbs.m_pts->m_i_dims; v++)
		{
        	if(v == dbs.m_pts->m_i_dims - 1)
				cout << "(" << box[v].upper << ", " << box[v].lower << ")";
			else
				cout << "(" << box[v].upper << ", " << box[v].lower << "), ";
		}

        cout << endl;
    }
};


//===----------------------------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//  Adaptation to Boost of the libcxx
//  Copyright 2010 Vicente J. Botet Escriba
//  Distributed under the Boost Software License, Version 1.0.
//  See http://www.boost.org/LICENSE_1_0.txt

#include <boost/ratio/ratio_io.hpp>

//typedef boost::ratio_string<boost::ratio_add<boost::ratio<1,2>, boost::ratio<1,3> >, char> R1;
typedef boost::ratio_string<int, char> R1;

void test() {

    std::string str = R1::symbol();
}


/**
 * $Id$
 *
 * Copyright (C)
 * 2015 - $Date$
 *     Martin Wolf <ndhist@martin-wolf.org>
 *
 * This file is distributed under the BSD 2-Clause Open Source License
 * (See LICENSE file).
 *
 */
#include <boost/python.hpp>

#include <ndhist/stats/expectation.hpp>
#include <ndhist/stats/mean.hpp>

namespace bp = boost::python;
namespace bn = boost::numpy;

namespace ndhist {
namespace stats {
namespace py {

bp::object
mean(ndhist const & h, bp::object const & axis)
{
    return expectation(h, 1, axis);
}

}// namespace py
}// namespace stats
}// namespace ndhist


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
/*!
 * \file
**/
#ifndef BOOST_SIMD_PREDICATES_FUNCTIONS_IS_FLINT_HPP_INCLUDED
#define BOOST_SIMD_PREDICATES_FUNCTIONS_IS_FLINT_HPP_INCLUDED
#include <boost/simd/include/functor.hpp>
#include <boost/dispatch/include/functor.hpp>

/*!
 * \ingroup boost_simd_predicates
 * \defgroup boost_simd_predicates_is_flint is_flint
 *
 * \par Description
 * Returns True<result_type>() or False<result_type>() according a0 is a flint or not.
 * \par
 * A flint is a 'floating integer' i.e. a floating number
 * representing an integer value
 * \par
 * Be conscious that all sufficiently great floating points values are flint...
 *
 * \par Header file
 *
 * \code
 * #include <nt2/include/functions/is_flint.hpp>
 * \endcode
 *
 *
 * \synopsis
 *
 * \code
 * namespace boost::simd
 * {
 *   template <class A0>
 *     meta::call<tag::is_flint_(A0)>::type
 *     is_flint(const A0 & a0);
 * }
 * \endcode
 *
 * \param a0 the unique parameter of is_flint
 *
 * \return an integer value
 *
 * \par Notes
 * In SIMD mode, this function acts elementwise on the inputs vectors elements
 * \par
 * This is a predicate operation. Such operations return bool in scalar mode,
 * but not in SIMD mode.
 * \par
 * The return type in SIMD mode is the common type of the parameters and is
 * a 'signed boolean' type. This means that in this case True has all its bits
 * sets to one. This is to facilitate masking operations. You are invited to
 * consult the rationale.
 *
**/

namespace boost { namespace simd { namespace tag
  {
    /*!
     * \brief Define the tag is_flint_ of functor is_flint
     *        in namespace boost::simd::tag for toolbox boost.simd.predicates
    **/
    struct is_flint_ : ext::elementwise_<is_flint_> { typedef ext::elementwise_<is_flint_> parent; };
  }
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::is_flint_, is_flint, 1)
} }

#endif

// modified by jt the 25/12/2010


/*
    This file is part of control-lib.

    Copyright (c) 2020, 2021, 2022 Bernardo Fichera <bernardo.fichera@gmail.com>

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
*/

#ifndef CONTROLLIB_CONTROLLERS_ABSTRACCONTROLLER_HPP
#define CONTROLLIB_CONTROLLERS_ABSTRACCONTROLLER_HPP

#include "control_lib/spatial/SE3.hpp"
#include "control_lib/tools/macros.hpp"
#include <Eigen/Core>
#include <memory>

namespace control_lib {
    namespace defaults {
        struct controller {
            // Output space dimension (by default SE3 dimensionality)
            PARAM_SCALAR(double, d, 6);

            // Integration time step controller
            PARAM_SCALAR(double, dt, 0.001);

            // Reference vector at the moment by default in SE3
            PARAM_VECTOR(double, xr, 0, 0, 0, 0, 0, 0);
        };
    } // namespace defaults

    namespace controllers {
        template <typename Params, typename Space>
        class AbstractController {
        public:
            AbstractController() : _d(Params::controller::d()),
                                   _dt(Params::controller::dt()),
                                   _xr(Params::controller::xr()),
                                   _u(Eigen::VectorXd::Zero(_d)) {}

            const size_t& dimension() const { return _d; };

            const double& step() const { return _dt; };

            const Space& reference() const { return _xr; };

            AbstractController& setDimension(const size_t& d)
            {
                _d = d;
                return *this;
            }

            AbstractController& setStep(const double& dt)
            {
                _dt = dt;
                return *this;
            }

            AbstractController& setReference(const Space& x)
            {
                _xr = x;
                return *this;
            }

            virtual void update(const Space& x) = 0;

            const Eigen::VectorXd& action(const Space& x)
            {
                update(x);
                return _u;
            }

        protected:
            // Output dimension
            size_t _d;

            // Integration time step
            double _dt;

            // Operation space
            Space _xr;

            // Control state
            Eigen::VectorXd _u;
        };
    } // namespace controllers
} // namespace control_lib

#endif // CONTROLLIB_CONTROLLERS_ABSTRACCONTROLLER_HPP


#include <boost/test/unit_test.hpp>

#include "logs.h"
#include "route.h"
#include "track.h"

using namespace GPS;

// Documentation has been done suitably in the form of comments in this file.

/********************************************
* Test Suite for TotalHeightGain - N0693955 *
* *******************************************/

/*
 * The function of this test suite is to test the correctness
 * of the totalHeightGain function which I implemented. It aims to
 * check for typical, atypical, and edge case scenarios for the
 * totalHeightGain function.
*/

/*
 *   STRUCTURE OF THE TEST SUITE
 *
 * Test Case 1 is a general purpose test case that tests the basic functionality.
 *
 * Test Cases 2-3 handle testing for inclination and declination above sea level.
 *
 * Test Cases 4-5 handle testing for inclination and declination below sea level
 *
 * Test Case 6 handles testing for inclination from below sea level to above sea level.
 *
 * Test Case 7 handles testing for declination from above sea level to below sea level.
 *
 * Test Case 8 handles testing for points below sea level that don't follow a continuous inclination.
 *
 * Test Case 9 handles testing for points above sea level that don't follow a continuous declination.
 *
 * Test Case 10 handles a singular point.
 *
*/

BOOST_AUTO_TEST_SUITE( TotalHeightGain_n0693955 )

const bool isFileName = true;

                                    /*************
                                    * TEST CASES *
                                    **************/

/***********************************************************************************/

//TEST CASE 1
/*
 * This test case checks the usual functionality of the function,
 * wherein the function ignores negative height differences and
 * adds the positive height differences
*/

BOOST_AUTO_TEST_CASE( ABBM )
{
    Route route = Route(LogFiles::GPXRoutesDir + "ABBM-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 50);
}

/***********************************************************************************/

//TEST CASE 2
/*
 * This test case checks if the function evaluates the height values
 * correctly when the heights are inclining in succession. Since all the
 * differences in heights will give a positive non-zero value, these
 * differences should be added to the total height gain.
 *
 * Possible Scenario: Going on an inclination from sea level to above sea level.
*/

BOOST_AUTO_TEST_CASE( IncliningHeights1 )
{
    Route route = Route(LogFiles::GPXRoutesDir + "IncliningHeights1-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 200);
}

/***********************************************************************************/

//TEST CASE 3
/*
 * This test case checks if the function evaluates the height values
 * correctly when the heights are declining in succession. Since all the
 * differences in height values would be negative in this scenario,
 * these differences should not be added to the total height gain.
 *
 * Possible Scenario: Going on a declination from above sea level to sea level.
*/

BOOST_AUTO_TEST_CASE( DecliningHeights1 )
{
    Route route = Route(LogFiles::GPXRoutesDir + "DecliningHeights1-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 0);
}

/***********************************************************************************/

//TEST CASE 4
/*
 * This test case checks if the function evaluates the height values
 * correctly when the heights are inclining in succession. Since all the
 * height values here are negative, this test case checks if it evaluates the
 * difference in each case correctly.
 *
 * Possible Scenario: Going on an inclination from below sea level to sea level.
*/

BOOST_AUTO_TEST_CASE( IncliningHeights2 )
{
    Route route = Route(LogFiles::GPXRoutesDir + "IncliningHeights2-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 200);
}

/***********************************************************************************/

//TEST CASE 5
/*
 * This test case checks if the function evaluate the height values
 * correctly when the heights are declining in succession. Since all the values
 * here are negative, this test case checks if it evaluates the difference
 * in each case correctly.
 *
 * Possible Scenario: Going on a declination from sea level to below sea level.
*/

BOOST_AUTO_TEST_CASE( DecliningHeights2 )
{
    Route route = Route(LogFiles::GPXRoutesDir + "DecliningHeights2-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 0);
}

/***********************************************************************************/

//TEST CASE 6
/*
 * This test case checks if the function evaluates the difference in
 * elevations correctly when the height being subtracted is a
 * negative value.
 *
 * Possible Scenario: Going on an inclination from below sea level to above sea level.
*/

BOOST_AUTO_TEST_CASE( STG )
{
    Route route = Route(LogFiles::GPXRoutesDir + "STG-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 200);
}

/***********************************************************************************/

//TEST CASE 7
/*
 * This purpose of this test case is similar to the previous one, however in this case
 * the height being subtracted initially is not a negative elevation value.
 *
 * Possible Scenario: Going on a declination from above sea level to below sea level.
*/

BOOST_AUTO_TEST_CASE( GTS )
{
    Route route = Route(LogFiles::GPXRoutesDir + "GTS-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 0);
}

/***********************************************************************************/

//TEST CASE 8
/*
 * This test case checks if the function evaluates correctly when all the
 * height values present are negative.
 *
 * Possibile Scenario: All points below sea level.
*/

BOOST_AUTO_TEST_CASE( AllNegatives )
{
    Route route = Route(LogFiles::GPXRoutesDir + "AllNegatives-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 50);
}

/***********************************************************************************/

//TEST CASE 9
/*
 * This test case checks if the function evaluates correctly when all the
 * height values present are positive.
 *
 * Possibile Scenario: All points above sea level.
*/

BOOST_AUTO_TEST_CASE( AllPositives )
{
    Route route = Route(LogFiles::GPXRoutesDir + "AllPositives-n0693955.gpx", isFileName);
    BOOST_CHECK_EQUAL(route.totalHeightGain(), 100);
}

/***********************************************************************************/

//TEST CASE 10
/*
 * This test case disregards the data if there is only one point,
 * and therefore, only one height value. The total height gain
 * cannot be calculated if there is only one value available for
 * a subtraction where two values must be present.
 *
 * Possible Scenario: Singular point, regardless of whether it is above, beyond, or
 * at sea level.
 *
 * NOTE: This test case is set up to throw up an invalid argument exception as the gpx file
 * holds just one height value.
*/

BOOST_AUTO_TEST_CASE ( SingleHeight )
{
    Route route = Route(LogFiles::GPXRoutesDir + "SingleHeight-n0693955.gpx", isFileName);
    BOOST_CHECK_THROW(route.totalHeightGain(), std::invalid_argument);
}

/***********************************************************************************/

BOOST_AUTO_TEST_SUITE_END()


/*$Id$*/

/* Copyright (c) 2008 Alexey Ozeritsky
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Alexey Ozeritsky.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <boost/gil/extension/io/jpeg_dynamic_io.hpp>

#include "c2_gradient.h"
#include "c2_matrix.h"
#include "c2_gauss.h"
#include "c2_canny.h"

template < typename Img, typename Img2 >
void abs_max_grad(const Img & img, const Img2 & img_x, const Img2 & img_y)
{
	gray8s_image_t img_out(img.dimensions());
	fill_pixels(view(img_out),bits8s(0));

	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_abs_max_cast_channels (view(img_out)));

	jpeg_write_view("out-abs_max_gradient.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename Img, typename Img2 >
void abs_sum_grad(const Img & img, const Img2 & img_x, const Img2 & img_y)
{
	gray8s_image_t img_out(img.dimensions());
	fill_pixels(view(img_out),bits8s(0));

	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_abs_sum_cast_channels (view(img_out)));

	jpeg_write_view("out-abs_sum_gradient.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename Img, typename Img2 >
void sqrt_grad(const Img & img,  const Img2 & img_x, const Img2 & img_y)
{
	gray8s_image_t img_out(img.dimensions());
	fill_pixels(view(img_out),bits8s(0));

	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_sqrt_cast_channels (const_view(img_x)));

	jpeg_write_view("out-sqrt_gradient.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename Img, typename Img2 >
void sum_grad(const Img & img, const Img2 & img_x, const Img2 & img_y)
{
	gray8s_image_t img_out(img.dimensions());
	fill_pixels(view(img_out),bits8s(0));

	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_halfsum_cast_channels (view(img_out)));

	jpeg_write_view("out-sum_gradient.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void soebel(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	gray8s_image_t img_x(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_x),bits8s(0));

	gray8s_image_t img_y(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_y),bits8s(0));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_x), IntMatrix::Soebel_x());
//	jpeg_write_view("out-soebel_x.jpg",
//		color_converted_view<gray_pixel_t>(const_view(img_x)));

	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_y), IntMatrix::Soebel_y());
//	jpeg_write_view("out-soebel_y.jpg",
//		color_converted_view<gray_pixel_t>(const_view(img_y)));

	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_abs_sum_cast_channels (const_view(img_out)));

	jpeg_write_view("out-soebel.jpg",
		color_converted_view<gray_pixel_t>(const_view(img_out)));
}

template < typename View >
void previt(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	gray8s_image_t img_x(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_x),bits8s(0));

	gray8s_image_t img_y(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_y),bits8s(0));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_x), IntMatrix::Previt_x());
	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_y), IntMatrix::Previt_y());
	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_abs_sum_cast_channels (const_view(img_x)));

	jpeg_write_view("out-previt.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void roberts(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	gray8s_image_t img_x(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_x),bits8s(0));

	gray8s_image_t img_y(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_y),bits8s(0));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_x), IntMatrix::Roberts_x());
	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_y), IntMatrix::Roberts_y());
	image_join(const_view(img_x), const_view(img_y), view(img_out),
		make_abs_sum_cast_channels (const_view(img_x)));

	jpeg_write_view("out-roberts.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void log_3_3(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_out), IntMatrix::LOG_3_3());

	jpeg_write_view("out-log_3_3.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void log_11_11(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	apply_matrix(color_converted_view<gray_pixel_t>(v),
		view(img_out), IntMatrix::LOG_11_11());

	jpeg_write_view("out-log_11_11.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void gauss_3_3(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	//	jpeg_write_view("out-tttt.jpg",
	//		color_converted_view<gray8_pixel_t>(v));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	Gauss < 3 > filter;
	filter(color_converted_view<gray_pixel_t>(v), view(img_out));

	//	transform_1(color_converted_view<gray8_pixel_t>(v),
	//		view(img_out));

	//	copy_and_convert_pixels(color_converted_view<gray8_pixel_t>(v),
	//		view(img_out));

	jpeg_write_view("out-gauss_3_3.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void gauss_7_7(const View & v)
{
	gray8s_image_t img_out(gray8s_image_t::point_t(v.width(), v.height()));
	fill_pixels(view(img_out),bits8s(0));

	typedef pixel<typename channel_type<View>::type,
		gray_layout_t> gray_pixel_t;

	Gauss < 7 > filter;
	filter(color_converted_view<gray_pixel_t>(v), view(img_out));

	jpeg_write_view("out-gauss_7_7.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_out)));
}

template < typename View >
void gauss_3_3_color(const View & v)
{
	rgb8_image_t img_out(rgb8_image_t::point_t(v.width(), v.height()));

	apply_matrix(v, view(img_out), IntMatrix::Gauss_3_3(), -1, true);

	jpeg_write_view("out-gauss_3_3_color.jpg", const_view(img_out));
}

template < typename View >
void gauss_7_7_color(const View & v)
{
	rgb8_image_t img_out(rgb8_image_t::point_t(v.width(), v.height()));
//	fill_pixels(view(img_out),bits8s(0));

	apply_matrix(v,	view(img_out), IntMatrix::Gauss_7_7(), -1, true);

	jpeg_write_view("out-gauss_7_7_color.jpg", const_view(img_out));
}

int main() {
	rgb8_image_t img;
	jpeg_read_image("test.jpg", img);

	jpeg_write_view("out-lum.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img)));

	gray8s_image_t img_x(img.dimensions());
	gray8s_image_t img_y(img.dimensions());
	fill_pixels(view(img_x),bits8s(0));
	fill_pixels(view(img_y),bits8s(0));

	x_luminosity_gradient(const_view(img), view(img_x));
	y_luminosity_gradient(const_view(img), view(img_y));

	abs_max_grad(img, img_x, img_y);
	abs_sum_grad(img, img_x, img_y);
	sqrt_grad(img, img_x, img_y);
	sum_grad(img, img_x, img_y);

	soebel(const_view(img));
	previt(const_view(img));
	roberts(const_view(img));
	log_3_3(const_view(img));
	log_11_11(const_view(img));
	gauss_3_3(const_view(img));
	gauss_3_3_color(const_view(img));
	gauss_7_7(const_view(img));
	gauss_7_7_color(const_view(img));

	Canny < 3 > c;
	c(const_view(img), view(img_x), 10, 10);

	jpeg_write_view("out-x_gradient.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_x)));

	jpeg_write_view("out-y_gradient.jpg",
		color_converted_view<gray8_pixel_t>(const_view(img_y)));

	return 0;
}