#include <chainparams.h>
#include <masternodes/masternodes.h>
#include <masternodes/poolpairs.h>
#include <validation.h>

#include <test/setup_common.h>

#include <boost/test/unit_test.hpp>

inline uint256 NextTx()
{
    static int txs_counter = 1;

    std::stringstream stream;
    stream << std::hex << txs_counter++;
    return uint256S(stream.str() );
}

DCT_ID CreateToken(CCustomCSView &mnview, std::string const & symbol, uint8_t flags = (uint8_t)CToken::TokenFlags::Default)
{
    CTokenImplementation token;
    token.creationTx = NextTx();
    token.symbol = symbol;
    token.flags = flags;

    auto res = mnview.CreateToken(token, false);
    if (!res.ok) printf("%s\n", res.msg.c_str());
    BOOST_REQUIRE(res.ok);
    return *res.val;
}

// tokenz A,B,LP
std::tuple<DCT_ID, DCT_ID, DCT_ID> CreatePoolNTokens(CCustomCSView &mnview, std::string const & symbolA, std::string const & symbolB)
{
    DCT_ID idA = CreateToken(mnview, symbolA);
    DCT_ID idB = CreateToken(mnview, symbolB);

    DCT_ID idPool = CreateToken(mnview, symbolA + "-" +  symbolB, (uint8_t)CToken::TokenFlags::Default | (uint8_t)CToken::TokenFlags::DAT | (uint8_t)CToken::TokenFlags::LPS);
    {
        CPoolPair pool{};
        pool.idTokenA = idA;
        pool.idTokenB = idB;
        pool.commission = 1000000; // 1%
    //    poolMsg.ownerAddress = CScript();
        pool.status = true;
        BOOST_REQUIRE(mnview.SetPoolPair(idPool, 1, pool).ok);
    }
    return std::tuple<DCT_ID, DCT_ID, DCT_ID>(idA, idB, idPool); // ! simple initialization list (as "{a,b,c}")  doesn't work here under ubuntu 16.04 - due to older gcc?
}

Res AddPoolLiquidity(CCustomCSView &mnview, DCT_ID idPool, CAmount amountA, CAmount amountB, CScript const & shareAddress)
{
    auto optPool = mnview.GetPoolPair(idPool);
    BOOST_REQUIRE(optPool);

    const auto res = optPool->AddLiquidity(amountA, amountB, [&] /*onMint*/(CAmount liqAmount) -> Res {
        BOOST_CHECK(liqAmount > 0);

        auto add = mnview.AddBalance(shareAddress, { idPool, liqAmount });
        if (!add.ok) {
            return add;
        }
        //insert update ByShare index
        const auto setShare = mnview.SetShare(idPool, shareAddress, 1);
        if (!setShare.ok) {
            return setShare;
        }
        return Res::Ok();
    });
    BOOST_REQUIRE(res.ok);
    return mnview.SetPoolPair(idPool, 1, *optPool);
}

BOOST_FIXTURE_TEST_SUITE(liquidity_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(math_liquidity_and_trade)
{
    auto FAIL_onMint = [](CAmount)-> Res { BOOST_REQUIRE(false); return Res::Err("it should not happen"); };
    auto FAIL_onSwap = [](const CTokenAmount &)-> Res { BOOST_REQUIRE(false); return Res::Err("it should not happen"); };

    CCustomCSView mnview(*pcustomcsview);

    DCT_ID idA, idB, idPool;
    std::tie(idA, idB, idPool) = CreatePoolNTokens(mnview, "AAA", "BBB");
    auto optPool = mnview.GetPoolPair(idPool);
    BOOST_REQUIRE(optPool);

    Res res{};
    { // basic fails
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(-1, 1000, FAIL_onMint);
        BOOST_CHECK(!res.ok && res.msg == "amounts should be positive");
        res = pool.AddLiquidity(0, 1000, FAIL_onMint);
        BOOST_CHECK(!res.ok && res.msg == "amounts should be positive");
        res = pool.AddLiquidity(1, 1000, FAIL_onMint);
        BOOST_CHECK(!res.ok && res.msg == "liquidity too low");
        res = pool.AddLiquidity(10, 100000, FAIL_onMint);
        BOOST_CHECK(!res.ok && res.msg == "liquidity too low"); // median == MINIMUM_LIQUIDITY
    }

    { // amounts a bit larger MINIMUM_LIQUIDITY
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(11, 100000, [](CAmount liq)-> Res {
            BOOST_CHECK(liq == 48); // sqrt (11*100000) - MINIMUM_LIQUIDITY
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK(pool.reserveA == 11);
        BOOST_CHECK(pool.reserveB == 100000);
    }

    {   // one limit
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(std::numeric_limits<CAmount>::max(), 1, [](CAmount liq)-> Res {
            BOOST_CHECK(liq == 3036999499); // == sqrt(limit)-MINIMUM_LIQUIDITY
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK(pool.reserveA == std::numeric_limits<CAmount>::max());
        BOOST_CHECK(pool.reserveB == 1);
        BOOST_CHECK(pool.totalLiquidity == 3036999499 + CPoolPair::MINIMUM_LIQUIDITY);

        // plus 1
        res = pool.AddLiquidity(1, 1, FAIL_onMint);
        BOOST_CHECK(!res.ok && res.msg == "amounts too low, zero liquidity");

        // we can't swap forward even 1 satoshi
        res = pool.Swap(CTokenAmount{pool.idTokenA, 1}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, FAIL_onSwap);
        BOOST_CHECK(!res.ok && res.msg == "Lack of liquidity.");

        // and backward too
        res = pool.Swap(CTokenAmount{pool.idTokenB, 2}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, FAIL_onSwap);
        BOOST_CHECK(!res.ok && res.msg == "Lack of liquidity.");

        // thats all, we can't place anything here until removing. trading disabled due to reserveB < SLOPE_SWAP_RATE
    }

    {   // two limits
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(std::numeric_limits<CAmount>::max(), std::numeric_limits<CAmount>::max(), [](CAmount liq)-> Res {
            BOOST_CHECK(liq == std::numeric_limits<CAmount>::max() - CPoolPair::MINIMUM_LIQUIDITY);
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK(pool.reserveA == std::numeric_limits<CAmount>::max());
        BOOST_CHECK(pool.reserveB == std::numeric_limits<CAmount>::max());
        BOOST_CHECK(pool.totalLiquidity == std::numeric_limits<CAmount>::max());

        res = pool.AddLiquidity(1, 1, FAIL_onMint);
        BOOST_CHECK(!res.ok && res.msg.find("can't add") != res.msg.npos); // in fact we got liquidity overflows

        // thats all, we can't do anything here until removing
    }

    // trying to swap moooore than reserved on low reserves (sliding)
    // it works extremely bad on low reserves, but it's okay, just bad trade.
    {
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(1001, 1001, [](CAmount liq)-> Res {
            BOOST_CHECK(liq == 1001 - CPoolPair::MINIMUM_LIQUIDITY);
            return Res::Ok();
        });
        res = pool.Swap(CTokenAmount{pool.idTokenA, 1000000}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
            BOOST_CHECK_EQUAL(ta.nValue, 1000);
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK_EQUAL(pool.blockCommissionA, 10000);
        BOOST_CHECK_EQUAL(pool.reserveA, 991001);
        BOOST_CHECK_EQUAL(pool.reserveB, 1);
    }

    // trying to swap moooore than reserved (sliding), but on "resonable" reserves
    {
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(COIN, COIN, [](CAmount liq)-> Res {
            return Res::Ok();
        });
        res = pool.Swap(CTokenAmount{pool.idTokenA, 2*COIN}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
            BOOST_CHECK_EQUAL(ta.nValue, 66442954); // pre-optimization: 66464593
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK_EQUAL(pool.blockCommissionA, 2000000);
        BOOST_CHECK_EQUAL(pool.reserveA, 298000000);
        BOOST_CHECK_EQUAL(pool.reserveB, 33557046); // pre-optimization: 33535407

    }

    {
//        printf("2 COIN (1:1000)\n");
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(COIN, 1000*COIN, [](CAmount liq)-> Res {
            return Res::Ok();
        });
        res = pool.Swap(CTokenAmount{pool.idTokenA, 2*COIN}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
            BOOST_CHECK_EQUAL(ta.nValue, 66442953021); // pre-optimization: 66465256146
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK_EQUAL(pool.blockCommissionA, 2000000);
        BOOST_CHECK_EQUAL(pool.reserveA, 298000000);
        BOOST_CHECK_EQUAL(pool.reserveB, 33557046979); // pre-optimization: 33534743854
    }
    {
//        printf("1 COIN (1:1000)\n");
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(COIN, 1000*COIN, [](CAmount liq)-> Res {
            return Res::Ok();
        });
        res = pool.Swap(CTokenAmount{pool.idTokenA, COIN}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
            BOOST_CHECK_EQUAL(ta.nValue, 49748743719); // pre-optimization: 49773755285
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK_EQUAL(pool.blockCommissionA, 1000000);
        BOOST_CHECK_EQUAL(pool.reserveA, 199000000);
        BOOST_CHECK_EQUAL(pool.reserveB, 50251256281); // pre-optimization: 50226244715
    }
    {
//        printf("COIN/1000 (1:1000) (no slope due to commission)\n");
        CPoolPair pool = *optPool;
        res = pool.AddLiquidity(COIN, 1000*COIN, [](CAmount liq)-> Res {
            return Res::Ok();
        });
        res = pool.Swap(CTokenAmount{pool.idTokenA, COIN/1000}, PoolPrice{std::numeric_limits<CAmount>::max(), 0}, [&] (CTokenAmount const &ta) -> Res{
            BOOST_CHECK_EQUAL(ta.nValue, 98902087); // pre-optimization: 99000000
            return Res::Ok();
        });
        BOOST_CHECK(res.ok);
        BOOST_CHECK_EQUAL(pool.blockCommissionA, 1000);
        BOOST_CHECK_EQUAL(pool.reserveA, 100099000);
        BOOST_CHECK_EQUAL(pool.reserveB, 99901097913); // pre-optimization: 99901000000

//       printf("comissionA = %ld\n", pool.blockCommissionA);
//       printf("reserveA = %ld\n", pool.reserveA);
//       printf("reserveB = %ld\n", pool.reserveB);
    }
}

void SetPoolRewardPct(CCustomCSView & mnview, DCT_ID idPool, CAmount pct)
{
    BOOST_REQUIRE(mnview.SetRewardPct(idPool, 1, pct));
}

void SetPoolTradeFees(CCustomCSView & mnview, DCT_ID idPool, CAmount A, CAmount B)
{
    auto optPool = mnview.GetPoolPair(idPool);
    BOOST_REQUIRE(optPool);
    optPool->blockCommissionA = A;
    optPool->blockCommissionB = B;
    optPool->swapEvent = true;
    mnview.SetPoolPair(idPool, 1, *optPool);
}

BOOST_AUTO_TEST_CASE(math_rewards)
{
    const int PoolCount = 10; // less than DCT_ID_START!
    const int ProvidersCount = 10000;

    CCustomCSView mnview(*pcustomcsview);

    // create pools
    for (int i = 0; i < PoolCount; ++i) {
        DCT_ID idA, idB, idPool;
        std::tie(idA, idB, idPool) = CreatePoolNTokens(mnview, "A"+std::to_string(i), "B"+std::to_string(i));
        auto optPool = mnview.GetPoolPair(idPool);
        BOOST_REQUIRE(optPool);

    }
    // create shares
    mnview.ForEachPoolId([&] (DCT_ID const & idPool) {
//            printf("pool id = %s\n", idPool.ToString().c_str());
        for (int i = 0; i < ProvidersCount; ++i) {
            CScript shareAddress = CScript(idPool.v * ProvidersCount + i);
            Res res = AddPoolLiquidity(mnview, idPool, idPool.v*COIN, idPool.v*COIN, shareAddress);
            BOOST_CHECK(res.ok);
        }
        return true;
    });

    {
        CCustomCSView cache(mnview);

        // set pool rewards rates
        const DCT_ID RWD50 = DCT_ID{1};
        const DCT_ID RWD25 = DCT_ID{2};
        SetPoolRewardPct(cache, RWD50, COIN/2); // 50%
        SetPoolRewardPct(cache, RWD25, COIN/4); // 25%

        SetPoolRewardPct(cache, DCT_ID{3}, COIN/10); // 10%
        SetPoolRewardPct(cache, DCT_ID{4}, COIN/10); // 10%
        SetPoolRewardPct(cache, DCT_ID{5}, COIN/100);
        SetPoolRewardPct(cache, DCT_ID{6}, COIN/100);
        SetPoolRewardPct(cache, DCT_ID{7}, COIN/100);
        SetPoolRewardPct(cache, DCT_ID{8}, COIN/100);
        SetPoolRewardPct(cache, DCT_ID{9}, COIN/100);
        /// DCT_ID{10} - 0

        // set "traded fees" here too, just to estimate proc.load
        cache.ForEachPoolId([&] (DCT_ID const & idPool) {
            SetPoolTradeFees(cache, idPool, idPool.v * COIN, idPool.v * COIN*2);
            return true;
        });

        // distribute 100 coins
        CAmount totalRwd = 100*COIN*2880;
        cache.SetDailyReward(1, totalRwd);
        // fund community
        cache.AddCommunityBalance(CommunityAccountType::IncentiveFunding, totalRwd);

        // check it
        auto rwd25 = 25 * COIN / ProvidersCount;
        auto rwd50 = 50 * COIN / ProvidersCount;
        cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner, uint32_t) {

            cache.CalculateOwnerRewards(owner, 2); // one block
            // check only first couple of pools and the last (zero)
            if (id == RWD25 && owner != CScript(id.v * ProvidersCount)) { // first got slightly less due to MINIMUM_LIQUIDITY
                CAmount rwd = cache.GetBalance(owner, DCT_ID{0}).nValue;
                BOOST_CHECK(rwd == rwd25);
            }
            if (id == RWD50 && owner != CScript(id.v * ProvidersCount)) { // first got slightly less due to MINIMUM_LIQUIDITY
                CAmount rwd = cache.GetBalance(owner, DCT_ID{0}).nValue;
                BOOST_CHECK(rwd == rwd50);
            }
            if (id == DCT_ID{10} ) { // first got slightly less due to MINIMUM_LIQUIDITY
                CAmount rwd = cache.GetBalance(owner, DCT_ID{0}).nValue;
                BOOST_CHECK(rwd == 0);
            }
            return true;
        });

        // check trade comissions for one of pools
        {
            DCT_ID idPool = DCT_ID{1};
            auto optPool = cache.GetPoolPair(idPool);
            cache.ForEachPoolShare([&] (DCT_ID const & id, CScript const & owner, uint32_t) {
                if (id != idPool)
                    return false;

                if (owner != CScript(id.v * ProvidersCount)) { // first got slightly less due to MINIMUM_LIQUIDITY
                    CAmount rwdA = cache.GetBalance(owner, DCT_ID{optPool->idTokenA}).nValue;
                    CAmount rwdB = cache.GetBalance(owner, DCT_ID{optPool->idTokenB}).nValue;
                    BOOST_CHECK(rwdA == id.v * COIN / ProvidersCount);
                    BOOST_CHECK(rwdB == id.v * COIN * 2 / ProvidersCount);
                }
                return true;
            }, PoolShareKey{idPool, {}});
        }
    }
}

BOOST_AUTO_TEST_CASE(owner_rewards)
{
    CCustomCSView mnview(*pcustomcsview);

    constexpr const int PoolCount = 10;
    CScript shareAddress[PoolCount];

    // create pools
    for (int i = 0; i < PoolCount; ++i) {
        DCT_ID idA, idB, idPool;
        std::tie(idA, idB, idPool) = CreatePoolNTokens(mnview, "A"+std::to_string(i), "B"+std::to_string(i));
        shareAddress[i] = CScript(idPool.v * PoolCount + i);
        auto optPool = mnview.GetPoolPair(idPool);
        BOOST_REQUIRE(optPool);
    }

    // create shares
    mnview.ForEachPoolId([&] (DCT_ID const & idPool) {
        for (int i = 0; i < PoolCount; ++i) {
            Res res = AddPoolLiquidity(mnview, idPool, idPool.v*COIN, idPool.v*COIN, shareAddress[i]);
            BOOST_CHECK(res.ok);
        }
        return true;
    });

    mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) {
        pool.blockCommissionA = idPool.v * COIN;
        pool.blockCommissionB = idPool.v * COIN * 2;
        pool.swapEvent = true;
        pool.ownerAddress = shareAddress[0];
        mnview.SetPoolPair(idPool, 1, pool);
        mnview.SetRewardPct(idPool, 1, COIN/(idPool.v + 1));
        return true;
    });

    mnview.SetDailyReward(3, COIN);

    auto oldRewardCalculation = [](CAmount liquidity, const CPoolPair& pool) -> CAmount {
        constexpr const uint32_t PRECISION = 10000;
        int32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity;
        return COIN / 2880 * pool.rewardPct / COIN * liqWeight / PRECISION;
    };

    auto oldCommissionCalculation = [](CAmount liquidity, const CPoolPair& pool) -> std::pair<CAmount, CAmount> {
        constexpr const uint32_t PRECISION = 10000;
        int32_t liqWeight = liquidity * PRECISION / pool.totalLiquidity;
        auto feeA = pool.blockCommissionA * liqWeight / PRECISION;
        auto feeB = pool.blockCommissionB * liqWeight / PRECISION;
        return std::make_pair(feeA, feeB);
    };

    mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) {
        auto onLiquidity = [&]() -> CAmount {
            return mnview.GetBalance(shareAddress[0], idPool).nValue;
        };
        mnview.CalculatePoolRewards(idPool, onLiquidity, 1, 10,
            [&](RewardType type, CTokenAmount amount, uint32_t height) {
                switch(type) {
                case RewardType::Coinbase:
                    BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(onLiquidity(), pool));
                    break;
                case RewardType::Commission:
                    if (amount.nTokenId == pool.idTokenA) {
                        BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).first);
                    } else {
                        BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).second);
                    }
                    break;
                default:
                    BOOST_REQUIRE(false);
                }
                mnview.AddBalance(shareAddress[0], amount);
            }
        );
        return true;
    });

    // new calculation
    const_cast<int&>(Params().GetConsensus().BayfrontGardensHeight) = 6;

    auto newRewardCalculation = [](CAmount liquidity, const CPoolPair& pool) -> CAmount {
        return COIN / 2880 * pool.rewardPct / COIN * liquidity / pool.totalLiquidity;
    };

    auto newCommissionCalculation = [](CAmount liquidity, const CPoolPair& pool) -> std::pair<CAmount, CAmount> {
        auto feeA = pool.blockCommissionA * liquidity / pool.totalLiquidity;
        auto feeB = pool.blockCommissionB * liquidity / pool.totalLiquidity;
        return std::make_pair(feeA, feeB);
    };

    mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) {
        pool.swapEvent = true;
        pool.ownerAddress = shareAddress[1];
        pool.rewards = CBalances{TAmounts{{DCT_ID{idPool.v+1}, COIN}}};
        mnview.SetPoolPair(idPool, 8, pool);
        return false;
    });

    mnview.ForEachPoolPair([&] (DCT_ID const & idPool, CPoolPair pool) {
        auto onLiquidity = [&] () -> CAmount {
            return mnview.GetBalance(shareAddress[1], idPool).nValue;
        };
        mnview.CalculatePoolRewards(idPool, onLiquidity, 1, 10,
            [&](RewardType type, CTokenAmount amount, uint32_t height) {
                if (height >= Params().GetConsensus().BayfrontGardensHeight) {
                    if (type == RewardType::Pool) {
                        for (const auto& reward : pool.rewards.balances) {
                            auto providerReward = static_cast<CAmount>((arith_uint256(reward.second) * arith_uint256(onLiquidity()) / arith_uint256(pool.totalLiquidity)).GetLow64());
                            BOOST_CHECK_EQUAL(amount.nValue, providerReward);
                        }
                    } else if (type == RewardType::Coinbase) {
                        BOOST_CHECK_EQUAL(amount.nValue, newRewardCalculation(onLiquidity(), pool));
                    } else if (amount.nTokenId == pool.idTokenA) {
                        BOOST_CHECK_EQUAL(amount.nValue, newCommissionCalculation(onLiquidity(), pool).first);
                    } else {
                        BOOST_CHECK_EQUAL(amount.nValue, newCommissionCalculation(onLiquidity(), pool).second);
                    }
                } else {
                    if (type & RewardType::Rewards) {
                        BOOST_CHECK_EQUAL(amount.nValue, oldRewardCalculation(onLiquidity(), pool));
                    } else if (amount.nTokenId == pool.idTokenA) {
                        BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).first);
                    } else {
                        BOOST_CHECK_EQUAL(amount.nValue, oldCommissionCalculation(onLiquidity(), pool).second);
                    }
                }
                mnview.AddBalance(shareAddress[1], amount);
            }
        );
        return false;
    });
}

BOOST_AUTO_TEST_SUITE_END()


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

#pragma once

#include <array>
#include <vector>
#include <utility>
#include <iostream>
#include <cstdint>
#include <map>
#include <random>
#include <boost/multiprecision/cpp_int.hpp>

using namespace boost::multiprecision;

//------------------------------------
//              Factors
//------------------------------------

typedef std::pair<unsigned long, int> Factor;

class Factors
{
 private:
  unsigned long n_;
  std::vector<unsigned long> all_factors_;
  std::vector<std::vector<unsigned long>> cofactors_;

  unsigned long ISqrt_(unsigned long x);

  void CalculateAllFactors_();

  // Return a vector of all order-way cofactor sets of n.
  std::vector<std::vector<unsigned long>> MultiplicativeSplitRecursive_(unsigned long n, int order);

 public:
  Factors();
  Factors(const unsigned long n, const int order);
  Factors(const unsigned long n, const int order, std::map<unsigned, unsigned long> given);

  void PruneMax(std::map<unsigned, unsigned long>& max);

  std::vector<unsigned long>& operator[](int index);

  std::size_t size();

  void Print();
  void PrintAllFactors();
  void PrintCoFactors();

  friend std::ostream& operator<<(std::ostream& out, const Factors& f);
};

//------------------------------------
//        Cartesian Counter
//------------------------------------

template <int order>
class CartesianCounter
{
 private:
  // Note: base is variable for each position!
  std::array<uint128_t, order> base_;
  std::array<uint128_t, order> value_;
  checked_uint128_t integer_;
  checked_uint128_t endint_;

  bool IncrementRecursive_(int position)
  {
    if (value_[position] < base_[position] - 1)
    {
      value_[position]++;
      return true;
    }
    else if (position < order - 1)
    {
      value_[position] = 0;
      return IncrementRecursive_(position + 1);
    }
    else
    {
      // Overflow! We could do one of 3 things:
      // (a) Throw an exception.
      // (b) Wrap-around to 0.
      // (c) Saturate and return a code.
      // We choose (c) for this implementation.
      return false;
    }
  }

  void UpdateIntegerFromValue()
  {
    integer_ = 0;
    checked_uint128_t base = 1;
    for (int i = 0; i < order; i++)
    {
      integer_ += (checked_uint128_t(value_[i]) * base);
      base *= base_[i];
    }
  }

  void UpdateValueFromInteger()
  {
    uint128_t n = integer_;
    for (int i = 0; i < order; i++)
    {
      value_[i] = n % base_[i];
      n = n / base_[i];
    }
  }

 public:
  CartesianCounter(std::array<uint128_t, order> base = {})
  {
    Init(base);
  }

  template<typename T>
  void Init(T base)
  {
    value_ = {};
    integer_ = 0;
    endint_ = 1;
    for (int i = 0; i < order; i++)
    {
      base_[i] = base[i];
      endint_ *= checked_uint128_t(base_[i]);
    }
  }

  bool Increment()
  {
    integer_++;
    return IncrementRecursive_(0);
  }

  std::array<uint128_t, order> Read() const
  {
    return value_;
  }

  std::array<uint128_t, order> Base() const
  {
    return base_;
  }

  uint128_t operator[] (int dim)
  {
    return value_[dim];
  }

  void Set(uint128_t n)
  {
    assert(checked_uint128_t(n) < endint_);
    integer_ = n;
    UpdateValueFromInteger();
  }

  void Set(int dim, uint128_t v)
  {
    value_[dim] = v;
    UpdateIntegerFromValue();
    assert(integer_ < endint_);
  }

  void Set(std::array<uint128_t, order> v)
  {
    value_ = v;
    UpdateIntegerFromValue();
    assert(integer_ < endint_);
  }

  uint128_t EndInteger() const
  {
    return endint_;
  }

  uint128_t Integer() const
  {
    return integer_;
  }
};

//------------------------------------
//    Cartesian Counter (dynamic)
//------------------------------------

class CartesianCounterDynamic
{
 private:
  // Note: base is variable for each position!
  int order_;
  std::vector<uint128_t> base_;
  std::vector<uint128_t> value_;
  checked_uint128_t integer_;
  checked_uint128_t endint_;

  bool IncrementRecursive_(int position)
  {
    if (value_[position] < base_[position] - 1)
    {
      value_[position]++;
      return true;
    }
    else if (position < order_ - 1)
    {
      value_[position] = 0;
      return IncrementRecursive_(position + 1);
    }
    else
    {
      // Overflow! We could do one of 3 things:
      // (a) Throw an exception.
      // (b) Wrap-around to 0.
      // (c) Saturate and return a code.
      // We choose (c) for this implementation.
      return false;
    }
  }

  void UpdateIntegerFromValue()
  {
    integer_ = 0;
    checked_uint128_t base = 1;
    for (int i = 0; i < order_; i++)
    {
      integer_ += (checked_uint128_t(value_[i]) * base);
      base *= base_[i];
    }
  }

  void UpdateValueFromInteger()
  {
    uint128_t n = integer_;
    for (int i = 0; i < order_; i++)
    {
      value_[i] = n % base_[i];
      n = n / base_[i];
    }
  }

 public:
  CartesianCounterDynamic() = delete;

  CartesianCounterDynamic(unsigned order) :
      order_(order)
  {
    base_.resize(order_);
    value_.resize(order_);
  }

  CartesianCounterDynamic(std::vector<uint128_t> base) :
      CartesianCounterDynamic(base.size())
  {
    Init(base);
  }

  template<typename T>
  void Init(T base)
  {
    integer_ = 0;
    endint_ = 1;
    for (int i = 0; i < order_; i++)
    {
      base_[i] = base[i];
      endint_ *= checked_uint128_t(base_[i]);
      value_[i] = 0;
    }
  }

  bool Increment()
  {
    integer_++;
    return IncrementRecursive_(0);
  }

  std::vector<uint128_t> Read() const
  {
    return value_;
  }

  std::vector<uint128_t> Base() const
  {
    return base_;
  }

  uint128_t operator[] (int dim)
  {
    return value_[dim];
  }

  void Set(uint128_t n)
  {
    assert(order_ != 0);
    assert(checked_uint128_t(n) < endint_);
    integer_ = n;
    UpdateValueFromInteger();
  }

  void Set(int dim, uint128_t v)
  {
    assert(dim < order_);
    value_[dim] = v;
    UpdateIntegerFromValue();
    assert(integer_ < endint_);
  }

  void Set(std::vector<uint128_t> v)
  {
    order_ = v.size();
    value_ = v;
    UpdateIntegerFromValue();
    assert(integer_ < endint_);
  }

  uint128_t EndInteger() const
  {
    return endint_;
  }

  uint128_t Integer() const
  {
    return integer_;
  }
};

//------------------------------------
//             Factoradic
//------------------------------------

template <class T>
class Factoradic
{
 private:
  const static std::size_t MaxLength = 20;  // 21! > 2^64.
  std::uint64_t factorial_table_[MaxLength + 1];

 public:
  Factoradic()
  {
    factorial_table_[0] = 1;
    std::uint64_t fact = 1;
    for (std::uint64_t i = 1; i <= MaxLength; i++)
    {
      fact = fact * i;
      factorial_table_[i] = fact;
    }
  }

  std::uint64_t Factorial(std::uint64_t n)
  {
    assert(n <= MaxLength);
    return factorial_table_[n];
  }

  void Permute(T* buffer, std::size_t length, std::uint64_t index)
  {
    std::uint64_t scale = factorial_table_[length];
    assert(index < scale);

    for (std::size_t i = 0; i < length - 1; i++)
    {
      scale /= (std::uint64_t)(length - i);
      std::size_t d = index / scale;
      index %= scale;
      if (d > 0)
      {
        const T c = buffer[i + d];
        memmove(buffer + i + 1, buffer + i, d * sizeof(T));
        buffer[i] = c;
      }
    }
  }
};

//------------------------------------
//        PatternGenerator128
//------------------------------------

class PatternGenerator128
{
 protected:
  const std::uint64_t uint64_max_ = std::numeric_limits<std::uint64_t>::max();
  uint128_t bound_;

 public:
  PatternGenerator128(uint128_t bound);

  virtual uint128_t Next() = 0;
};

class SequenceGenerator128 final : public PatternGenerator128
{
 private:
  bool autoloop_;
  uint128_t cur_;

 public:
  SequenceGenerator128(uint128_t bound, bool autoloop = true);

  uint128_t Next();
};

class RandomGenerator128 final : public PatternGenerator128
{
 private:
  bool use_two_generators_;

  std::default_random_engine engine_;
  std::uniform_int_distribution<std::uint64_t> low_gen_;
  std::uniform_int_distribution<std::uint64_t> high_gen_;

 public:
  RandomGenerator128(uint128_t bound);

  uint128_t Next();
};

//------------------------------------
//           Miscellaneous
//------------------------------------

// Returns the smallest factor of an integer and the quotient after
// division with the smallest factor.
void SmallestFactor(uint64_t n, uint64_t& factor, uint64_t& residue);

// Helper function to get close-to-square layouts of arrays
// containing a given number of nodes.
void GetTiling(uint64_t num_elems, uint64_t& height, uint64_t& width);

double LinearInterpolate(double x,
                         double x0, double x1,
                         double q0, double q1);

double BilinearInterpolate(double x, double y,
                           double x0, double x1,
                           double y0, double y1,
                           double q00, double q01, double q10, double q11);

//---------------------------------------------
// STL Utility Functions: move to another file
//---------------------------------------------

template<typename K>
struct TaggedBound
{
  bool valid;
  K bound;
};

template<typename K, typename V>
std::tuple<TaggedBound<K>, TaggedBound<K>>
FindBounds(std::map<K, V> map, K key)
{
  TaggedBound<K> lower = { false, K() };
  TaggedBound<K> upper = { false, K() };

  if (!map.empty())
  {
    // Find consecutive entries in the map that bound the width parameter.
    typename std::map<K, V>::iterator it;
    for (it = map.begin(); it != map.end() && key > it->first; it++);

    if (it != map.end())
    {
      upper.valid = true;
      upper.bound = it->first;
    }

    if (it != map.begin())
    {
      it--;
      lower.valid = true;
      lower.bound = it->first;
    }
    else if (key == it->first)
    {
      lower.valid = true;
      lower.bound = it->first;
    }
  }

  return std::make_tuple(lower, upper);
}


#include "common/common.hpp"

#include "geodb/klee.hpp"

#include <boost/program_options.hpp>
#include <fmt/ostream.h>

#include <iostream>
#include <string>
#include <sstream>

using namespace std;
using namespace geodb;
namespace po = boost::program_options;

static std::string input;

struct averager {
private:
    double m_value = 0;
    u64 m_count = 0;

public:
    void push(double value) {
        m_value += value;
        m_count += 1;
    }

    u64 count() const {
        return m_count;
    }

    double average() const {
        return m_count != 0 ? m_value / double(m_count) : 0;
    }
};

struct tree_stats {
    averager entry_area;    // Volume of data entries
    averager leaf_area;     // Volume of leaf nodes

    averager index_size;    // Number of postings lists per index
    averager list_size;     // Number of entries per posting list
    std::map<size_t, averager> index_size_level;

    averager internal_volume_ratio;
    std::map<size_t, averager> internal_volume_ratio_level;
};

struct analyze_result {
    bounding_box mbb;
    double entry_area = 0;
    double leaf_area = 0;

    double index_size = 0;
    std::vector<double> index_size_level;
    double list_size = 0;

    double internal_volume_ratio = 0;
    std::vector<double> internal_volume_ratio_level;
};

static void parse_options(int argc, char** argv) {
    po::options_description options;
    options.add_options()
            ("help,h", "Show this message.")
            ("tree", po::value(&input)->value_name("PATH")->required(),
             "Path to the tree directory on disk.");

    po::variables_map vm;
    try {
        po::command_line_parser p(argc, argv);
        p.options(options);
        po::store(p.run(), vm);

        if (vm.count("help")) {
            fmt::print(cerr, "Usage: {0} OPTION...\n"
                             "\n"
                             "Analyses the given tree and prints statistics.\n"
                             "\n"
                             "{1}",
                       argv[0], options);
            throw exit_main(0);
        }

        po::notify(vm);
    } catch (const po::error& e) {
        fmt::print(cerr, "Failed to parse arguments: {}.\n", e.what());
        throw exit_main(1);
    }
}

template<typename T>
std::string to_string(const T& t) {
    std::stringstream ss;
    ss << t;
    return ss.str();
}

/// Returns the path of the given node, as a string.
static std::string path(const external_tree::cursor& node) {
    std::string result;
    for (auto id : node.path()) {
        if (!result.empty())
            result += "/";
        result += std::to_string(id);
    }
    return result;
}

/// a / b except when b is very close to 0 in which case it returns 0.
double div0(double a, double b) {
    if (std::fabs(b) < std::numeric_limits<double>::epsilon())
        return 0;
    return a / b;
}

/// Returns the bounding boxes of the given internal node's entries
/// as a vector of rect3d.
static std::vector<rect3d> get_rectangles(external_tree::cursor& node) {
    geodb_assert(node.is_internal(), "must be an internal node");
    geodb_assert(node.size() > 0, "nodes cannot be empty");

    const size_t size = node.size();

    std::vector<rect3d> rects;
    rects.reserve(size);
    for (size_t i = 0; i < size; ++i) {
        const bounding_box mbb = node.mbb(i);
        rect3d rect(vector3d(mbb.min().x(), mbb.min().y(), mbb.min().t()),
                    vector3d(mbb.max().x(), mbb.max().y(), mbb.max().t()));

        rects.push_back(rect);
    }
    return rects;
}

static double volume_ratio(external_tree::cursor& node) {
    const std::vector<rect3d> rects = get_rectangles(node);

    double sum = 0.0;
    double max = 0.0;
    double usum = union_area(rects);

    for (const rect3d& r : rects) {
        const double vol = r.size();

        sum += vol;
        max = std::max(max, vol);
    }

    geodb_assert(usum >= max && usum <= sum,
                 "union volume must be in range");

    // 1.   Sum of volumes equals maximum volume, thus
    //      usum must be 0 or very small too.
    // 2.   Default case.
    return sum <= std::numeric_limits<double>::epsilon()
            ? 1 : usum / sum;
}

static void analyze(external_tree::cursor& node, tree_stats& stats) {
    if (node.is_leaf()) {
        for (size_t i = 0; i < node.size(); ++i) {
            stats.entry_area.push(node.mbb(i).size());
        }

        stats.leaf_area.push(node.mbb().size());
    } else {
        auto index = node.inverted_index();

        size_t index_size = index->size();
        stats.index_size.push(index_size);
        stats.index_size_level[node.level()].push(index_size);
        // does not count the "total" list (its full anyway).
        for (const auto& entry : *index) {
            auto list = entry.postings_list();
            stats.list_size.push(list->size());
        }

        double ratio = volume_ratio(node);
        stats.internal_volume_ratio.push(ratio);
        stats.internal_volume_ratio_level[node.level()].push(ratio);

        for (size_t i = 0; i < node.size(); ++i) {
            node.move_child(i);
            analyze(node, stats);
            node.move_parent();
        }
    }
}

static analyze_result analyze(const external_tree& tree) {
    analyze_result result;

    if (!tree.empty()) {
        tree_stats stats;

        auto root = tree.root();
        analyze(root, stats);

        result.mbb = root.mbb();
        result.entry_area = stats.entry_area.average() / result.mbb.size();
        result.leaf_area = stats.leaf_area.average() / result.mbb.size();

        result.index_size = stats.index_size.average();
        for (const auto& pair : stats.index_size_level) {
            result.index_size_level.push_back(pair.second.average());
        }
        result.list_size = stats.list_size.average();

        result.internal_volume_ratio = stats.internal_volume_ratio.average();
        for (const auto& pair : stats.internal_volume_ratio_level) {
            result.internal_volume_ratio_level.push_back(pair.second.average());
        }
    }

    return result;
}

int main(int argc, char** argv) {
    (void) path;

    return tpie_main([&]{
        parse_options(argc, argv);

        external_tree tree{external_storage(input)};

        analyze_result stats = analyze(tree);

        json result = json::object();
        result["lambda"] = tree.lambda();
        result["path"] = input;
        result["height"] = tree.height();
        result["mbb"] = to_string(stats.mbb);

        result["entry_count"] = tree.size();
        result["entry_area"] = div0(stats.entry_area, tree.size());

        result["leaf_nodes"] = tree.leaf_node_count();
        result["leaf_utilization"] = div0(tree.size(), tree.leaf_node_count() * tree.max_leaf_entries());
        result["leaf_area"] = div0(stats.leaf_area, tree.leaf_node_count());

        result["internal_nodes"] = tree.internal_node_count();
        result["internal_area_ratio_level"] = stats.internal_volume_ratio_level;
        result["internal_index_size"] = stats.index_size;
        result["internal_index_size_level"] = stats.index_size_level;
        result["internal_list_size"] = stats.list_size;

        std::cout << result.dump(4) << std::endl;
        return 0;
    });
}


#include <iostream>
#include <iomanip>
#include <fstream>
#include <chrono>
#include <boost/algorithm/string.hpp>
#include "estimation.hpp"
#include "utils.hpp"
#include "pointmatcher/PointMatcher.h"

typedef PointMatcher<double> PM;
typedef PM::DataPoints DP;
using namespace PointMatcherSupport;  // NOLINT

int main(int argc, const char *argv[]) {
    std::string root, config;
    if (validateArgs(argc, argv, root, config) != 0) {
        return 1;
    }
    std::vector<std::string> lidar_files;
    get_file_names(root + "lidar/", lidar_files, "bin");
    std::string lidar_pose_file = root + "applanix/lidar_poses_ypr_ref2.csv";
    std::string lidar_odom_file = root + "applanix/lidar_odom_poses.csv";
    std::ofstream ofs;
    ofs.open(lidar_odom_file, std::ios::out);
    ofs << "TIME1,TIME2,x,y,z,yaw,pitch,roll\n";
    ofs.close();

    PM::ICP icp;
    if (config.empty()) {
        icp.setDefault();
    } else {
        std::ifstream ifs(config.c_str());
        icp.loadFromYaml(ifs);
    }

    DP::Labels labels;
    labels.push_back(DP::Label("x", 1));
    labels.push_back(DP::Label("y", 1));
    labels.push_back(DP::Label("z", 1));
    labels.push_back(DP::Label("w", 1));

    std::shared_ptr<PM::Transformation> rigidTrans;
    rigidTrans = PM::get().REG(Transformation).create("RigidTransformation");

    std::shared_ptr<PM::DataPointsFilter> removeScanner =
        PM::get().DataPointsFilterRegistrar.create("MinDistDataPointsFilter",
        {{"minDist", "2.0"}});

    std::shared_ptr<PM::DataPointsFilter> randSubsample =
        PM::get().DataPointsFilterRegistrar.create("RandomSamplingDataPointsFilter",
        {{"prob", toParam(0.90)}});

    std::deque<DP> sliding_window;
    uint window_size = 10;
    Eigen::Matrix4d T_enu_map = Eigen::Matrix4d::Identity();
    Eigen::Matrix4d T_prev = Eigen::Matrix4d::Identity();

    uint start = 92;
    for (uint i = start; i < lidar_files.size(); ++i) {
        std::cout << i << " / " << lidar_files.size() - 1 << std::endl;
        Eigen::MatrixXd pc, intensities;
        std::vector<float> times;
        load_velodyne(root + "lidar/" + lidar_files[i], pc, intensities, times);
        std::vector<double> gt;
        // GPSTime,x,y,z,vel_x,vel_y,vel_z,roll,pitch,heading,ang_vel_z
        assert(get_groundtruth_data(lidar_pose_file, lidar_files[i], gt));
        Eigen::Matrix4d T_enu_sensor = getTransformFromGT(gt);
        removeMotionDistortion(pc, times, T_enu_sensor, gt);

        DP newCloud(pc, labels);
        newCloud = removeScanner->filter(newCloud);
        if (i == start) {
            T_enu_map = T_enu_sensor;
            sliding_window.push_back(newCloud);
            continue;
        }
        Eigen::Matrix4d T_map_sensor = get_inverse_tf(T_enu_map) * T_enu_sensor;
        Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
        try {
            DP submap = sliding_window[0];
            for (uint j = 1; j < sliding_window.size(); ++j) {
                submap.concatenate(sliding_window[j]);
            }
            submap = randSubsample->filter(submap);  // random subsample to speed up ICP
            T = icp(randSubsample->filter(newCloud), submap, T_map_sensor);
        } catch (PM::ConvergenceError& error) {
            std::cout << "ERROR PM::ICP failed to converge: " << error.what() << std::endl;
            return 1;
        }
        std::cout << T << std::endl;
        DP transformed = rigidTrans->compute(newCloud, T);
        sliding_window.push_back(transformed);
        if (sliding_window.size() > window_size)
            sliding_window.pop_front();

        Eigen::Matrix4d T_rel = get_inverse_tf(T_prev) * T;
        T_prev = T;

        ofs.open(lidar_odom_file, std::ios::app);
        std::string time1, time2;
        get_name_from_file(lidar_files[i - 1], time1);
        get_name_from_file(lidar_files[i], time2);
        double yaw = 0, pitch = 0, roll = 0;
        Eigen::Matrix3d C = T_rel.block(0, 0, 3, 3);
        rotToYawPitchRoll(C, yaw, pitch, roll);
        ofs << time1 << "," << time2 << "," << T_rel(0, 3) << "," << T_rel(1, 3) << "," << T_rel(2, 3) << ","
            << yaw << "," << pitch << "," << roll << "\n";
        ofs.close();
    }
}


//
// Created by Andrew Bailey on 2019-07-15.
//

#ifndef EMBED_FAST5_SRC_MAXKMERS_HPP_
#define EMBED_FAST5_SRC_MAXKMERS_HPP_

#include "AssignmentFile.hpp"
#include "AlignmentFile.hpp"

#include "EmbedUtils.hpp"
#include <boost/filesystem.hpp>
#include <boost/heap/priority_queue.hpp>
#include <mutex>

using namespace std;
using namespace boost::filesystem;
using namespace embed_utils;

template <class T>
class MaxKmers{
 public:

  /**
  Initialize locks and heaps and other important data structures
  */
  MaxKmers(size_t heap_size, string alphabet, uint64_t kmer_length, double min_prob= 0.0) :
      alphabet(sort_string(alphabet)), alphabet_size(alphabet.length()),
      kmer_length(kmer_length), n_kmers(pow(this->alphabet_size, this->kmer_length)), max_heap(heap_size),
      min_prob(min_prob)
  {
    throw_assert(this->kmer_length > 0, "Kmer length must be greater than 0.")
    this->initialize_heap();
    this->initialize_locks();
  }
  ~MaxKmers() = default;
//    this->destroy_locks();

  string alphabet;
  int alphabet_size;
  uint64_t kmer_length;
  int n_kmers;
  size_t max_heap;
  double min_prob;
  std::vector<boost::heap::priority_queue<T>> kmer_queues;
  std::vector<mutex> locks;

  /**
  Create all kmers given an alphabet and kmer length

  eg. create_kmers(BA, 1) = {"A", "B"}

  @param alphabet: string with each character is apart of the alphabet
  @param kmer_length: length of the string
  @return vector of alphabetically ordered strings
  */
  vector<string> create_kmers(string& alphabet1, uint64_t kmer_length1) {
    return all_string_permutations(alphabet1, kmer_length1);
  }

  /**
  Get kmer index based on alphabet and kmer length

  eg. get_kmer_index(AAAAA) = 0

  @param kmer: input kmer
  @return index
  */
  size_t get_kmer_index(string& kmer){
    int64_t id = 0;
    int64_t step = 1;
    throw_assert(kmer.length() == this->kmer_length, "Kmer length is different than expected: " << kmer.length() << " != " << this->kmer_length)
    uint64_t index;

    for (int64_t i = this->kmer_length - 1; i >= 0; i--) {
      index = this->alphabet.find(kmer[i]);
      throw_assert( index != string::npos, "Kmer (" + kmer + ") has character not in (" + this->alphabet + ") alphabet")
      id += step * index;
      step *= this->alphabet_size;
    }
    return id;
  }

  /**
  Get kmer based on kmer index.

  eg. get_index_kmer(0) = AAAAA

  @param index: kmer index
  @return kmer
  */
  string get_index_kmer(size_t &kmer_index) {
    string kmer;
    size_t id_remainder = kmer_index;

    for (int64_t i = this->kmer_length - 1; i >= 0; i--) {
      kmer += this->alphabet[id_remainder % this->alphabet_size];
      id_remainder /= this->alphabet_size;
    }
    reverse(kmer.begin(), kmer.end());
    return kmer;
  }

  /**
  Initialize vector of heaps
  */
  void initialize_heap() {
    kmer_queues.resize(n_kmers);
  }

  /**
  Initialize vector of locks
  */
  void initialize_locks() {
    locks = std::vector<std::mutex>(this->n_kmers);;
  }

//  /**
//Destroy vector of locks
//*/
//  void destroy_locks() {
//    for (int i=0; i<this->n_kmers; i++){
//      omp_destroy_lock(&(locks[i]));
//    }
//  }

  /**
   * Write all kmers in the kmer_queues to output path
   * @param output_path
   */
  void write_to_file(boost::filesystem::path &output_path, bool write_full) {
    std::ofstream out_file;
    out_file.open(output_path.string());
    for (auto &pq: this->kmer_queues){
      for (auto &event: pq){
        out_file << event.format_line(write_full);
      }
    }
    out_file.close();
  }

  /**
   * Write all kmers in the kmer_queues to output path and write info about the kmers to log_path
   * @param output_path: path to output file
   * @param log_path: path to output log file
   * @param write_full: boolean option to write full output
   */
  void write_to_file(boost::filesystem::path &output_path, boost::filesystem::path &log_path, bool write_full) {
    std::ofstream out_file;
    out_file.open(output_path.string());

    std::ofstream out_log;
    out_log.open(log_path.string());
    out_log << "kmers" << '\t' << "num_events" << '\t' << "min_prob" << '\n';

    size_t kmer_index = 0;
    for (auto &pq: this->kmer_queues){
// loop through all queues
      for (auto &event: pq){
        out_file << event.format_line(write_full);
//      keep track of number of events
      }
      //    log info about queue
      int n_events = pq.size();
      float min_p;
      if (n_events > 0){
        auto top = pq.top();
        min_p = top.posterior_probability;
      } else{
        min_p = 0.0;
      }
      out_log << this->get_index_kmer(kmer_index) << '\t' << n_events << '\t' << min_p << '\n';
      kmer_index += 1;

    }
    out_file.close();
    out_log.close();
  }

  /**
  Add kmer data to the heap data structure for said kmer if probability is greater than the smallest probability

  @param kmer: kmer to add
  @param b: second eventkmer

  @return a.posterior_probability > b.posterior_probability
  */
  void add_to_heap(T& kmer_struct){
    size_t index = this->get_kmer_index(kmer_struct.path_kmer);
    if (kmer_struct.posterior_probability >= min_prob){
      std::unique_lock<std::mutex> lock(this->locks[index]);
      boost::heap::priority_queue<T>& queue = this->kmer_queues[index];
      if (queue.empty()) {
//    add to queue if not at capacity
        queue.push(kmer_struct);
//    if at capacity check to see if prob is greater than min
      } else if (queue.top().posterior_probability < kmer_struct.posterior_probability ||
          queue.size() < this->max_heap) {
        queue.push(kmer_struct);
        while (queue.size() > this->max_heap){
          queue.pop();
        }
      }
      lock.unlock();
    }
  }
};

bool operator<(const eventkmer& a, const eventkmer& b);
bool operator<(const FullSaEvent& a, const FullSaEvent& b);

#endif //EMBED_FAST5_SRC_MAXKMERS_HPP_


//
//  common.hpp
//  Elasticity
//
//  Created by Wim van Rees on 2/15/16.
//  Copyright © 2016 Wim van Rees. All rights reserved.
//

#ifndef common_hpp
#define common_hpp

#include <iostream>
#include <cassert>
#include <random>
#include <limits>
#include <utility>
#include <vector>
#include <string>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <map>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <array>
#include <set>
#include <memory>

#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <Eigen/StdVector>

//#include "ArgumentParser.h"

typedef double Real;
typedef unsigned long long tUint;
typedef std::vector<Eigen::Matrix2d, Eigen::aligned_allocator<Eigen::Matrix2d>> tVecMat2d;
typedef std::vector<Eigen::Matrix3d, Eigen::aligned_allocator<Eigen::Matrix3d>> tVecMat3d;
enum MeshLayer {single, bottom, top};

//#define USETANTHETA

// little helper to avoid unused warnings when something is called in release mode and variable only checked in debug
#define _unused(x) ((void)(x))

namespace rnd
{
    /*! global random number generator */
    static std::mt19937 gen;
}

namespace helpers
{

    /*! convert an integer to a string with a specified number of zeros */
    inline std::string ToString(int value,int digitsCount)
    {
        std::ostringstream os;
        os<<std::setfill('0')<<std::setw(digitsCount)<<value;
        return os.str();
    }

    /*! check if two numbers are close (within some accuracy) */
    inline bool isclose(const Real val1, const Real val2, const bool printOnly=false, const Real rtol=1e-5, const Real atol=1e-8)
    {
        const Real diff = std::abs(val1-val2) ;
        bool retval = true;
        if(diff > (atol + rtol * std::abs(val2)))
            retval = false;
        if(diff > (atol + rtol * std::abs(val1)))
            retval = false;

        if(printOnly && not retval)
        {
            std::cout << "PROBLEM : " << val1 << "\t" << val2 << "\t" << diff << std::endl;
            retval = true;
        }

        return retval;
    }

    inline bool isPointInProjectedTriangle(const Real x, const Real y, const Eigen::Vector3d & v0, const Eigen::Vector3d & v1, const Eigen::Vector3d & v2)
    {
        // dont use the third dimension
        const Real xp[3] = {v0(0),v1(0),v2(0)};
        const Real yp[3] = {v0(1),v1(1),v2(1)};

        const int npoly = 3; // triangle

        int i,j,c=0;
        for (i = 0, j = npoly-1; i < npoly; j = i++) {
            if ((((yp[i] <= y) && (y < yp[j])) ||
                 ((yp[j] <= y) && (y < yp[i]))) &&
                (x < (xp[j] - xp[i]) * (y - yp[i]) / (yp[j] - yp[i]) + xp[i]))
                c = !c;
        }
        return c;
    }

    inline Eigen::Vector3d getBaryCentricWeights2D(const Eigen::Vector3d & v0, const Eigen::Vector3d & v1, const Eigen::Vector3d & v2, const Real x, const Real y)
    {
        assert(isPointInProjectedTriangle(x, y, v0, v1, v2));

        Eigen::Vector3d me;
        me << x, y, 0;
        // compute my point in barycentric coordinates of undeformed configuration
        // Compute barycentric coordinates (u, v, w) for
        // point p with respect to triangle (a, b, c)
        // http://gamedev.stackexchange.com/questions/23743/whats-the-most-efficient-way-to-find-barycentric-coordinates
        const Eigen::Vector3d tmp0 = v1 - v0;
        const Eigen::Vector3d tmp1 = v2 - v0;
        const Eigen::Vector3d tmp2 = me - v0;
        const Real d00 = tmp0.dot(tmp0);
        const Real d01 = tmp0.dot(tmp1);
        const Real d11 = tmp1.dot(tmp1);
        const Real d20 = tmp2.dot(tmp0);
        const Real d21 = tmp2.dot(tmp1);
        const Real denom = d00 * d11 - d01 * d01;
        const Real lambda1 = (d11 * d20 - d01 * d21) / denom;
        const Real lambda2 = (d00 * d21 - d01 * d20) / denom;
        const Real lambda0 = 1.0 - lambda1 - lambda2;
        // if me == v0 --> lambda0 = 1
        // if me == v1 --> lambda1 = 1
        // if me == v2 --> lambda2 = 1

        Eigen::Vector3d retval;
        retval << lambda0, lambda1, lambda2;
        return retval;
    }

    template<int component>
    inline Real getDisplacementOfPointInRestTriangle(const Eigen::Vector3d & rv0, const Eigen::Vector3d & rv1, const Eigen::Vector3d & rv2, const Eigen::Vector3d & v0, const Eigen::Vector3d & v1, const Eigen::Vector3d & v2, const Real x, const Real y)
    {

        const Eigen::Vector3d lambdas = getBaryCentricWeights2D(rv0, rv1, rv2, x, y);

        const Real displ_0 = (v0 - rv0)(component);
        const Real displ_1 = (v1 - rv1)(component);
        const Real displ_2 = (v2 - rv2)(component);

        // get my displacement
        return -(lambdas(0)*displ_0 + lambdas(1)*displ_1 + lambdas(2)*displ_2);
    }

    inline Real interpolateOverTriangle(const Eigen::Vector3d & v0, const Eigen::Vector3d & v1, const Eigen::Vector3d & v2, const Real x, const Real y)
    {
        // linear interpolation over the triangle
        const Real A = v0(1)*(v1(2)-v2(2)) + v1(1)*(v2(2)-v0(2)) + v2(1)*(v0(2)-v1(2));
        const Real B = v0(2)*(v1(0)-v2(0)) + v1(2)*(v2(0)-v0(0)) + v2(2)*(v0(0)-v1(0));
        const Real C = v0(0)*(v1(1)-v2(1)) + v1(0)*(v2(1)-v0(1)) + v2(0)*(v0(1)-v1(1));
        const Real D = v0(0)*(v1(1)*v2(2)-v1(2)*v2(1)) + v1(0)*(v2(1)*v0(2)-v2(2)*v0(1)) + v2(0)*(v0(1)*v1(2)-v0(2)*v1(1));

        const Real ptZ = (D-A*x-B*y)/C;

        return ptZ;
    }

    inline Real computeDistanceToLineSegment(const Eigen::Vector2d & l1, const Eigen::Vector2d & l2, const Eigen::Vector2d & pt)
    {
        //http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
        const Real Lsq = (l1-l2).squaredNorm();
        if(Lsq < std::numeric_limits<Real>::epsilon())
            return (l1-pt).norm();

        const Real t =  std::max(0.0, std::min(1.0, (pt-l1).dot(l2-l1) / Lsq));
        const Eigen::Vector2d projection = l1 + t * (l2 - l1);
        return (projection - pt).norm();
    }

    inline int getIntersectionPointBetweenVectors(const Eigen::Vector2d & pt1, const Eigen::Vector2d & n1, const Eigen::Vector2d & pt2, const Eigen::Vector2d & n2, Eigen::Vector2d & result)
    {
        // return 0 if intersection is found
        // return 1 if an intersection is found, but beyond the extent of pt1+n1 and pt2+n2
        // return 2 if no intersection is found (parallel vectors)

        const Eigen::Vector2d n1_hat = n1.normalized();
        const Eigen::Vector2d n2_hat = n2.normalized();

        const Real pt1_vec1 = pt1.dot(n1_hat);
        const Real pt1_vec2 = pt1.dot(n2_hat);
        const Real pt2_vec1 = pt2.dot(n1_hat);
        const Real pt2_vec2 = pt2.dot(n2_hat);
        const Real vec1_vec2 = n1_hat.dot(n2_hat);

        // deal with orthogonal
        if(std::abs(vec1_vec2 - 1) < std::numeric_limits<Real>::epsilon())
        {
            if((pt1-pt2).norm()  < std::numeric_limits<Real>::epsilon())
            {
                result = pt1;
                return 0;
            }
            else
            {
                return 2;
            }
        }

        const Real beta1 = (pt2_vec1 + (pt1_vec2 - pt2_vec2) * vec1_vec2 - pt1_vec1) / (1.0 - std::pow(vec1_vec2,2));
        const Real beta2 = (pt1_vec2 + (pt2_vec1 - pt1_vec1) * vec1_vec2 - pt2_vec2) / (1.0 - std::pow(vec1_vec2,2));

        //const Eigen::Vector2d retval1 = pt1 + beta1*n1_hat;
        const Eigen::Vector2d retval2 = pt2 + beta2*n2_hat;

        // set the intersection point
        result = retval2;

        const bool inRange_1 = (beta1 >= 0 && beta1 <= n1.norm());
        const bool inRange_2 = (beta2 >= 0 && beta2 <= n2.norm());
        return (inRange_1 && inRange_2) ? 0 : 1;
    }

    inline std::string removeExtension(const std::string input, const std::string extension)
    {
        // remove extension from input. Assume extension is given as (eg) .vtp (including the period)
        // if not found, return input unaltered
        const std::size_t found_ext = input.rfind(extension);
        const std::string output = (found_ext != std::string::npos ? input.substr(0, found_ext) : input);
        return output;
    }

    inline void catastrophe(std::string s, const char* file, const int line, const bool isFatal=true)    // write ``error: s and exit program
    {
        std::cerr << "Something went wrong, error: " << s << std::endl;
        std::cerr << "Called from file : " << file << " at line number " << line << std::endl;
        if(isFatal)
        {
            std::cout << "Exiting..." << std::endl;
            std::exit(1);
        }
    }

    template<class Matrix>
    inline void write_matrix_binary(const std::string & filename, const Matrix& matrix)
    {
        // from https://stackoverflow.com/a/25389481
        std::ofstream out(filename, std::ios::out | std::ios::binary | std::ios::trunc);
        typename Matrix::Index rows=matrix.rows(), cols=matrix.cols();
        out.write((char*) (&rows), sizeof(typename Matrix::Index));
        out.write((char*) (&cols), sizeof(typename Matrix::Index));
        out.write((char*) matrix.data(), rows*cols*sizeof(typename Matrix::Scalar) );
        out.close();
    }

    template<class Matrix>
    inline void read_matrix_binary(const std::string & filename, Matrix& matrix)
    {
        // from https://stackoverflow.com/a/25389481
        std::ifstream in(filename, std::ios::in | std::ios::binary);
        typename Matrix::Index rows=0, cols=0;
        in.read((char*) (&rows),sizeof(typename Matrix::Index));
        in.read((char*) (&cols),sizeof(typename Matrix::Index));
        matrix.resize(rows, cols);
        in.read( (char *) matrix.data() , rows*cols*sizeof(typename Matrix::Scalar) );
        in.close();
    }

    template<class Matrix>
    inline void write_vecmat_binary(const std::string & filename, const std::vector<Matrix, Eigen::aligned_allocator<Matrix>> & vecmat)
    {
        // from https://stackoverflow.com/a/25389481
        std::ofstream out(filename, std::ios::out | std::ios::binary | std::ios::trunc);
        const size_t vecsize = vecmat.size();
        if(vecsize==0) return;
        typename Matrix::Index rows=vecmat[0].rows(), cols=vecmat[0].cols();
        out.write((char*) &vecsize, sizeof(vecsize));
        out.write((char*) (&rows), sizeof(typename Matrix::Index));
        out.write((char*) (&cols), sizeof(typename Matrix::Index));
        out.write((char*)&vecmat[0], vecsize * rows*cols*sizeof(typename Matrix::Scalar));
        out.close();
    }

    template<class Matrix>
    inline void read_vecmat_binary(const std::string & filename, std::vector<Matrix, Eigen::aligned_allocator<Matrix>> & vecmat)
    {
        std::ifstream in(filename, std::ios::in | std::ios::binary);
        size_t vecsize = 0;
        typename Matrix::Index rows=0, cols=0;

        in.read((char*) (&vecsize),sizeof(vecsize));
        assert(vecsize > 0);

        in.read((char*) (&rows),sizeof(typename Matrix::Index));
        in.read((char*) (&cols),sizeof(typename Matrix::Index));

        assert(rows == Matrix::RowsAtCompileTime);
        assert(cols == Matrix::ColsAtCompileTime);

        vecmat.resize(vecsize);
        in.read( (char *) &vecmat[0] , vecsize * rows*cols*sizeof(typename Matrix::Scalar) );
        in.close();
    }
}

namespace Eigen
{
    /*! typedef vector of booleans */
    typedef Matrix<bool, 3, 1> Vector3b;

    /*! typedef vector of booleans */
    typedef Matrix<bool, Dynamic, 1> VectorXb;
    typedef Matrix<bool, Dynamic, Dynamic> MatrixXb;
}

#endif /* common_hpp */


///////////////////////////////////////////////////////////////////////////////
// weighted_tail_variate_means.hpp
//
//  Copyright 2006 Daniel Egloff, Olivier Gygi. 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_ACCUMULATORS_STATISTICS_WEIGHTED_TAIL_VARIATE_MEANS_HPP_DE_01_01_2006
#define BOOST_ACCUMULATORS_STATISTICS_WEIGHTED_TAIL_VARIATE_MEANS_HPP_DE_01_01_2006

#include <numeric>
#include <vector>
#include <limits>
#include <functional>
#include <sstream>
#include <stdexcept>
#include <boost/throw_exception.hpp>
#include <boost/parameter/keyword.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/accumulators/numeric/functional.hpp>
#include <boost/accumulators/framework/accumulator_base.hpp>
#include <boost/accumulators/framework/extractor.hpp>
#include <boost/accumulators/framework/parameters/sample.hpp>
#include <boost/accumulators/statistics_fwd.hpp>
#include <boost/accumulators/statistics/tail.hpp>
#include <boost/accumulators/statistics/tail_variate.hpp>
#include <boost/accumulators/statistics/tail_variate_means.hpp>
#include <boost/accumulators/statistics/weighted_tail_mean.hpp>
#include <boost/accumulators/statistics/parameters/quantile_probability.hpp>

#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable: 4127) // conditional expression is constant
#endif

namespace boost
{
    // for _BinaryOperatrion2 in std::inner_product below
    // multiplies two values and promotes the result to double
    namespace numeric { namespace functional
    {
        ///////////////////////////////////////////////////////////////////////////////
        // numeric::functional::multiply_and_promote_to_double
        template<typename T, typename U>
        struct multiply_and_promote_to_double
          : multiplies<T, double const>
        {
        };
    }}
}

namespace boost { namespace accumulators
{

namespace impl
{
    /**
        @brief Estimation of the absolute and relative weighted tail variate means (for both left and right tails)

        For all \f$j\f$-th variates associated to the

        \f[
            \lambda = \inf\left\{ l \left| \frac{1}{\bar{w}_n}\sum_{i=1}^{l} w_i \geq \alpha \right. \right\}
        \f]

        smallest samples (left tail) or the weighted mean of the

        \f[
            n + 1 - \rho = n + 1 - \sup\left\{ r \left| \frac{1}{\bar{w}_n}\sum_{i=r}^{n} w_i \geq (1 - \alpha) \right. \right\}
        \f]

        largest samples (right tail), the absolute weighted tail means \f$\widehat{ATM}_{n,\alpha}(X, j)\f$
        are computed and returned as an iterator range. Alternatively, the relative weighted tail means
        \f$\widehat{RTM}_{n,\alpha}(X, j)\f$ are returned, which are the absolute weighted tail means
        normalized with the weighted (non-coherent) sample tail mean \f$\widehat{NCTM}_{n,\alpha}(X)\f$.

        \f[
            \widehat{ATM}_{n,\alpha}^{\mathrm{right}}(X, j) =
                \frac{1}{\sum_{i=\rho}^n w_i}
                \sum_{i=\rho}^n w_i \xi_{j,i}
        \f]

        \f[
            \widehat{ATM}_{n,\alpha}^{\mathrm{left}}(X, j) =
                \frac{1}{\sum_{i=1}^{\lambda}}
                \sum_{i=1}^{\lambda} w_i \xi_{j,i}
        \f]

        \f[
            \widehat{RTM}_{n,\alpha}^{\mathrm{right}}(X, j) =
                \frac{\sum_{i=\rho}^n w_i \xi_{j,i}}
            {\sum_{i=\rho}^n w_i \widehat{NCTM}_{n,\alpha}^{\mathrm{right}}(X)}
        \f]

        \f[
            \widehat{RTM}_{n,\alpha}^{\mathrm{left}}(X, j) =
                \frac{\sum_{i=1}^{\lambda} w_i \xi_{j,i}}
            {\sum_{i=1}^{\lambda} w_i \widehat{NCTM}_{n,\alpha}^{\mathrm{left}}(X)}
        \f]
    */

    ///////////////////////////////////////////////////////////////////////////////
    // weighted_tail_variate_means_impl
    //  by default: absolute weighted_tail_variate_means
    template<typename Sample, typename Weight, typename Impl, typename LeftRight, typename VariateType>
    struct weighted_tail_variate_means_impl
      : accumulator_base
    {
        typedef typename numeric::functional::average<Weight, Weight>::result_type float_type;
        typedef typename numeric::functional::average<typename numeric::functional::multiplies<VariateType, Weight>::result_type, Weight>::result_type array_type;
        // for boost::result_of
        typedef iterator_range<typename array_type::iterator> result_type;

        weighted_tail_variate_means_impl(dont_care) {}

        template<typename Args>
        result_type result(Args const &args) const
        {
            float_type threshold = sum_of_weights(args)
                             * ( ( is_same<LeftRight, left>::value ) ? args[quantile_probability] : 1. - args[quantile_probability] );

            std::size_t n = 0;
            Weight sum = Weight(0);

            while (sum < threshold)
            {
                if (n < static_cast<std::size_t>(tail_weights(args).size()))
                {
                    sum += *(tail_weights(args).begin() + n);
                    n++;
                }
                else
                {
                    if (std::numeric_limits<float_type>::has_quiet_NaN)
                    {
                        std::fill(
                            this->tail_means_.begin()
                          , this->tail_means_.end()
                          , std::numeric_limits<float_type>::quiet_NaN()
                        );
                    }
                    else
                    {
                        std::ostringstream msg;
                        msg << "index n = " << n << " is not in valid range [0, " << tail(args).size() << ")";
                        boost::throw_exception(std::runtime_error(msg.str()));
                    }
                }
            }

            std::size_t num_variates = tail_variate(args).begin()->size();

            this->tail_means_.clear();
            this->tail_means_.resize(num_variates, Sample(0));

            this->tail_means_ = std::inner_product(
                tail_variate(args).begin()
              , tail_variate(args).begin() + n
              , tail_weights(args).begin()
              , this->tail_means_
              , numeric::functional::plus<array_type const, array_type const>()
              , numeric::functional::multiply_and_promote_to_double<VariateType const, Weight const>()
            );

            float_type factor = sum * ( (is_same<Impl, relative>::value) ? non_coherent_weighted_tail_mean(args) : 1. );

            std::transform(
                this->tail_means_.begin()
              , this->tail_means_.end()
              , this->tail_means_.begin()
              , std::bind2nd(numeric::functional::divides<typename array_type::value_type const, float_type const>(), factor)
            );

            return make_iterator_range(this->tail_means_);
        }

    private:

        mutable array_type tail_means_;

    };

} // namespace impl

///////////////////////////////////////////////////////////////////////////////
// tag::absolute_weighted_tail_variate_means
// tag::relative_weighted_tail_variate_means
//
namespace tag
{
    template<typename LeftRight, typename VariateType, typename VariateTag>
    struct absolute_weighted_tail_variate_means
      : depends_on<non_coherent_weighted_tail_mean<LeftRight>, tail_variate<VariateType, VariateTag, LeftRight>, tail_weights<LeftRight> >
    {
        typedef accumulators::impl::weighted_tail_variate_means_impl<mpl::_1, mpl::_2, absolute, LeftRight, VariateType> impl;
    };
    template<typename LeftRight, typename VariateType, typename VariateTag>
    struct relative_weighted_tail_variate_means
      : depends_on<non_coherent_weighted_tail_mean<LeftRight>, tail_variate<VariateType, VariateTag, LeftRight>, tail_weights<LeftRight> >
    {
        typedef accumulators::impl::weighted_tail_variate_means_impl<mpl::_1, mpl::_2, relative, LeftRight, VariateType> impl;
    };
}

///////////////////////////////////////////////////////////////////////////////
// extract::weighted_tail_variate_means
// extract::relative_weighted_tail_variate_means
//
namespace extract
{
    extractor<tag::abstract_absolute_tail_variate_means> const weighted_tail_variate_means = {};
    extractor<tag::abstract_relative_tail_variate_means> const relative_weighted_tail_variate_means = {};

    BOOST_ACCUMULATORS_IGNORE_GLOBAL(weighted_tail_variate_means)
    BOOST_ACCUMULATORS_IGNORE_GLOBAL(relative_weighted_tail_variate_means)
}

using extract::weighted_tail_variate_means;
using extract::relative_weighted_tail_variate_means;

// weighted_tail_variate_means<LeftRight, VariateType, VariateTag>(absolute) -> absolute_weighted_tail_variate_means<LeftRight, VariateType, VariateTag>
template<typename LeftRight, typename VariateType, typename VariateTag>
struct as_feature<tag::weighted_tail_variate_means<LeftRight, VariateType, VariateTag>(absolute)>
{
    typedef tag::absolute_weighted_tail_variate_means<LeftRight, VariateType, VariateTag> type;
};

// weighted_tail_variate_means<LeftRight, VariateType, VariateTag>(relative) -> relative_weighted_tail_variate_means<LeftRight, VariateType, VariateTag>
template<typename LeftRight, typename VariateType, typename VariateTag>
struct as_feature<tag::weighted_tail_variate_means<LeftRight, VariateType, VariateTag>(relative)>
{
    typedef tag::relative_weighted_tail_variate_means<LeftRight, VariateType, VariateTag> type;
};

}} // namespace boost::accumulators

#ifdef _MSC_VER
# pragma warning(pop)
#endif

#endif


/* Copyright 2016 Ramakrishnan Kannan */

#ifndef DISTNTF_DISTNTFIO_HPP_
#define DISTNTF_DISTNTFIO_HPP_

#include <unistd.h>
#include <armadillo>
#include <limits>  // for limits of standard data types
#include <string>
#include <vector>
#include "common/distutils.hpp"
#include "common/ncpfactors.hpp"
#include "common/npyio.hpp"
#include "common/tensor.hpp"
#include "distntf/distntfmpicomm.hpp"

/*
 * File name formats
 * A is the filename
 * 1D distribution Arows_totalpartitions_rank or Acols_totalpartitions_rank
 * Double 1D distribution (both row and col distributed)
 * Arows_totalpartitions_rank and Acols_totalpartitions_rank
 * TWOD distribution A_totalpartition_rank
 * Just send the first parameter Arows and the second parameter Acols to be
 * zero.
 */
namespace planc {
class DistNTFIO {
 private:
  const NTFMPICommunicator &m_mpicomm;
  Tensor &m_A;
  // don't start getting prime number from 2;
  static const int kPrimeOffset = 10;
  // Hope no one hits on this number.
  static const int kW_seed_idx = 1210873;
  static const int kalpha = 1;
  static const int kbeta = 0;
  UVEC m_global_dims;
  UVEC m_local_dims;

  /*
   * Uses the pattern from the input matrix X but
   * the value is computed as low rank.
   */
  void randomLowRank(const UVEC i_global_dims, const UVEC i_local_dims,
                     const UVEC i_start_rows, const UWORD i_k) {
    // start with the same seed_idx with the global dimensions
    // on all the MPI processor.
    NCPFactors global_factors(i_global_dims, i_k, false);
    global_factors.randu(kW_seed_idx);
    global_factors.normalize();
    NCPFactors local_factors(i_local_dims, i_k, false);
    UWORD start_row, end_row;
    for (int i = 0; i < local_factors.modes(); i++) {
      start_row = i_start_rows(i);
      end_row = start_row + i_local_dims(i) - 1;
      local_factors.factor(i) =
          global_factors.factor(i).rows(start_row, end_row);
    }
    local_factors.rankk_tensor(m_A);
  }

  /*
  * Every process reads the global tensor and extracts its local tensor.
  * It is no more needed. We will use distributed mpi io.

  void build_local_tensor() {
    // assumption is that the m_A has global tensor with it now.
    // Local_size = global_size/proc_grids // mode wise division. Local_size is
    // a vector of size modes For i over modes // at the end of this loop we
    // will have the start_idx and end_idx of every  mode for every rank
    // Start_idx(i) = MPI_FIBER_RANK(i) * local_dims(i)
    // End_idx(i) = start_idx(i) + local_size(i) - 1
    // List of subscripts = {set of subs for every mode} .
    // Find the Cartesian product of the list of idxs.
    // This will give us the list of tensor global subscripts
    // and should be in the row - major order.
    // Call the sub2ind of the tensor for all the global
    // subscripts and extract those data from the global data.

    // Local_size = global_size/proc_grids
    UVEC global_dims = m_A.dimensions();
    UVEC local_dims = global_dims / this->m_mpicomm.proc_grids();
    size_t num_modes = m_A.modes();
    std::vector<std::vector<size_t>> idxs_for_mode;
    size_t current_start_idx, current_end_idx, num_idxs_for_mode;
    // For i over modes
    for (size_t i = 0; i < num_modes; i++) {
      // Start_idx(i) = MPI_FIBER_RANK(i) * global_dims(i)
      current_start_idx = MPI_FIBER_RANK(i) * local_dims(i);
      // current_end_idx = current_start_idx + local_dims(i);
      // num_idxs_for_mode = current_end_idx -  current_start_idx;
      // UVEC idxs_current_mode = arma::linspace<UVEC>(current_start_idx,
      //                          current_end_idx - 1,
      //                          num_idxs_for_mode);
      std::vector<size_t> idxs_current_mode(local_dims(i));
      std::iota(std::begin(idxs_current_mode), std::end(idxs_current_mode),
                current_start_idx);
      // arma::conv_to<std::vector<size_t>>::from(idxs_current_mode);
      idxs_for_mode.push_back(idxs_current_mode);
    }
    // we have to now determing the cartesian product of this set.
    std::vector<std::vector<size_t>> global_idxs =
        cartesian_product(idxs_for_mode);
    Tensor local_tensor(local_dims);
    UVEC global_idxs_uvec = arma::zeros<UVEC>(global_idxs[0].size());
    for (size_t i = 0; i < global_idxs.size(); i++) {
      global_idxs_uvec = arma::conv_to<UVEC>::from(global_idxs[i]);
      local_tensor.m_data[i] = m_A.at(global_idxs_uvec);
    }
    this->m_A = local_tensor;
  }
  */

 public:
  explicit DistNTFIO(const NTFMPICommunicator &mpic, Tensor &in)
      : m_mpicomm(mpic), m_A(in) {}
  ~DistNTFIO() {
    // delete this->m_A;
  }
  /*void readInput(const std::string file_name) {
    // In this case we are reading from a file.
    // We can read .bin file, .npy file, .txt file.
    // Look at the extension and appropriately
    // .bin files must be supported by .info
    std::string extension = file_name.substr(file_name.find_last_of(".") + 1);
    std::stringstream ss;
    ss << file_name;
    if (MPI_SIZE > 1) {
      ss << "." << MPI_RANK;
      DISTPRINTINFO("Reading input file::" << ss.str());
    }
    if (extension == "bin") {
      // make sure you have corresponding info file
      // in txt format. First line specifier number of modes
      // second line specifies dimensions of each mode seperated
      // by space
      std::ios_base::openmode mode = std::ios_base::in | std::ios_base::binary;
      m_A.read(ss.str(), mode);
    } else if (extension == "npy") {
      NumPyArray npyreader;
      npyreader.load(file_name);
      if (MPI_RANK == 0) {
        npyreader.printInfo();
      }
      this->m_A = *(npyreader.m_input_tensor);
      if (MPI_SIZE > 1) {
        build_local_tensor();
      }
    } else if (extension == "txt") {
      m_A.read(ss.str());
    }
  }*/

  /*
      Reading from real input file.
      Expecting a .tensor text file and .bin file.
      .info text file every one will read
      .bin file will be an mpi io file
      Read distributed tensor
  */
  UVEC read_dist_tensor(const std::string filename,
                        UVEC *start_idxs_uvec = NULL) {
    // all processes reading the file_name.info file.
    std::string filename_no_extension =
        filename.substr(0, filename.find_last_of("."));
    filename_no_extension.append(".info");
    std::ifstream ifs;
    int modes;
    PRINTROOT("Reading tensor" << filename);
    // info file always in text mode
    ifs.open(filename_no_extension, std::ios_base::in);
    // write modes
    ifs >> modes;
    // dimension of modes
    // UVEC global_dims = arma::zeros<UVEC>(this->m_modes);
    int *global_dims = new int[modes];
    int *local_dims = new int[modes];
    int *start_idxs = new int[modes];
    this->m_local_dims = arma::zeros<UVEC>(modes);
    this->m_global_dims = arma::zeros<UVEC>(modes);
    UVEC tmp_start_idxs_uvec = arma::zeros<UVEC>(modes);
    UVEC tmp_proc_grids = this->m_mpicomm.proc_grids();
    for (int i = 0; i < modes; i++) {
      ifs >> global_dims[i];
      this->m_global_dims[i] = global_dims[i];
      local_dims[i] = itersplit(global_dims[i], tmp_proc_grids[i],
                                this->m_mpicomm.fiber_rank(i));
      this->m_local_dims[i] = local_dims[i];
      start_idxs[i] = startidx(global_dims[i], tmp_proc_grids[i],
                               this->m_mpicomm.fiber_rank(i));
      if (start_idxs_uvec != NULL) start_idxs_uvec[i] = start_idxs[i];
      tmp_start_idxs_uvec[i] = start_idxs[i];
    }
    ifs.close();
    DISTPRINTINFO("global dims::" << this->m_global_dims
                                  << "Local Tensor Dims::" << this->m_local_dims
                                  << "::start_idxs::" << tmp_start_idxs_uvec);
    Tensor rc(this->m_local_dims);
    // Create the datatype associated with this layout
    MPI_Datatype view;
    MPI_Type_create_subarray(modes, global_dims, local_dims, start_idxs,
                             MPI_ORDER_FORTRAN, MPI_DOUBLE, &view);
    MPI_Type_commit(&view);
    // Open the file
    MPI_File fh;
    // const MPI_Comm &comm = Y->getDistribution()->getComm(true);
    // get confirmed with grey
    int ret = MPI_File_open(MPI_COMM_WORLD, filename.c_str(), MPI_MODE_RDONLY,
                            MPI_INFO_NULL, &fh);
    if (ret != MPI_SUCCESS) {
      DISTPRINTINFO("Error: Could not read file " << filename << std::endl);
    }
    // Set the view
    MPI_Offset disp = 0;
    MPI_File_set_view(fh, disp, MPI_DOUBLE, view, "native", MPI_INFO_NULL);
    // Read the file
    int count = rc.numel();
    DISTPRINTINFO("reading::" << count << "::in gbs::"
                              << (count * 8.0) / (1024 * 1024 * 1024));
    assert(count * 8 <= std::numeric_limits<int>::max());
    if (ISROOT && 8 * count > std::numeric_limits<int>::max()) {
      PRINTROOT("file read size ::" << 8.0 * count << " > 2GB" << std::endl);
    }
    MPI_Status status;
    ret = MPI_File_read_all(fh, &rc.m_data[0], count, MPI_DOUBLE, &status);
    int nread;
    MPI_Get_count(&status, MPI_DOUBLE, &nread);
    if (ret != MPI_SUCCESS) {
      DISTPRINTINFO("Error: Could not read file " << filename << std::endl);
    }
    // Close the file
    MPI_File_close(&fh);
    // Free the datatype
    MPI_Type_free(&view);

    // free the allocated things.
    delete[] global_dims;
    delete[] local_dims;
    delete[] start_idxs;
    swap(this->m_A, rc);
    return this->m_global_dims;
  }
  /**
   * Writes distributed tensor.
   * Expecting a .tensor text file and .bin file.
   * .info text file will be written by root processor alone
   * .bin file will be an mpi io file
   * Read distributed tensor
   * @param[in] filename
   * @param[in] local_tensor
   */
  void write_dist_tensor(const std::string filename,
                         const Tensor &local_tensor) {
    // all processes reading the file_name.info file.
    std::string filename_no_extension =
        filename.substr(0, filename.find_last_of("."));
    int modes = local_tensor.modes();
    int *gsizes = new int[modes];
    int *lsizes = new int[modes];
    int *starts = new int[modes];
    UVEC tmp_proc_grids = this->m_mpicomm.proc_grids();
    for (int i = 0; i < modes; i++) {
      lsizes[i] = local_tensor.dimension(i);
      MPI_Allreduce(&lsizes[i], &gsizes[i], 1, MPI_INT, MPI_SUM,
                    this->m_mpicomm.fiber(i));
      starts[i] =
          startidx(gsizes[i], tmp_proc_grids[i], this->m_mpicomm.fiber_rank(i));
    }
    // info file always in text mode
    if (ISROOT) {
      filename_no_extension.append(".info");
      std::ofstream ofs;
      ofs.open(filename_no_extension, std::ios_base::out | std::ios::app);
      // write modes
      ofs << modes << std::endl;
      // dimension of modes
      for (int i = 0; i < modes; i++) {
        ofs << gsizes[i] << std::endl;
      }
      ofs.close();
    }
    MPI_Barrier(MPI_COMM_WORLD);
    // Now each of write to the file.
    // Create the datatype associated with this layout

    MPI_Datatype view;
    MPI_Type_create_subarray(modes, gsizes, lsizes, starts, MPI_ORDER_FORTRAN,
                             MPI_DOUBLE, &view);
    MPI_Type_commit(&view);

    // Open the file
    MPI_File fh;
    int ret =
        MPI_File_open(MPI_COMM_WORLD, filename.c_str(),
                      MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL, &fh);
    if (ISROOT && ret != MPI_SUCCESS) {
      DISTPRINTINFO("Error: Could not open file " << filename << std::endl);
    }

    // Set the view
    MPI_Offset disp = 0;
    MPI_File_set_view(fh, disp, MPI_DOUBLE, view, "native", MPI_INFO_NULL);

    // Write the file
    int count = local_tensor.numel();
    assert(count <= std::numeric_limits<int>::max());
    MPI_Status status;
    ret = MPI_File_write_all(fh, &local_tensor.m_data[0], count, MPI_DOUBLE,
                             &status);
    if (ret != MPI_SUCCESS) {
      DISTPRINTINFO("Error: Could not write file " << filename << std::endl);
    }
    // Close the file
    MPI_File_close(&fh);
    // Free the datatype
    MPI_Type_free(&view);
    // free the allocated memories
    delete[] gsizes;
    delete[] lsizes;
    delete[] starts;
  }

  /*
   * We need m,n,pr,pc only for rand matrices. If otherwise we are
   * expecting the file will hold all the details.
   * If we are loading by file name we dont need distio flag.
   *
   */
  void readInput(const std::string file_name, UVEC i_global_dims,
                 UVEC i_proc_grids, UWORD k = 0, double sparsity = 0) {
    // INFO << "readInput::" << file_name << "::" << distio << "::"
    //     << m << "::" << n << "::" << pr << "::" << pc
    //     << "::" << this->MPI_RANK << "::" << this->m_mpicomm.size() <<
    //     std::endl;
    this->m_global_dims = i_global_dims;
    std::string rand_prefix("rand_");
    if (!file_name.compare(0, rand_prefix.size(), rand_prefix)) {
      this->m_local_dims = arma::zeros<UVEC>(i_proc_grids.n_rows);
      UVEC start_rows = arma::zeros<UVEC>(i_proc_grids.n_rows);
      // Calculate tensor local dimensions
      for (unsigned int mode = 0; mode < this->m_local_dims.n_rows; mode++) {
        int slice_num = this->m_mpicomm.slice_num(mode);
        this->m_local_dims[mode] =
            itersplit(i_global_dims[mode], i_proc_grids[mode], slice_num);
        start_rows[mode] =
            startidx(i_global_dims[mode], i_proc_grids[mode], slice_num);
      }
      if (!file_name.compare("rand_uniform")) {
        // Tensor temp(i_global_dims / i_proc_grids);
        Tensor temp(this->m_local_dims, start_rows);
        swap(m_A, temp);
        // generate again. otherwise all processes will have
        // same input tensor because of the same seed.
        this->m_A.randu(449 * MPI_RANK + 677);
      } else if (!file_name.compare("rand_lowrank")) {
        randomLowRank(i_global_dims, this->m_local_dims, start_rows, k);
        this->m_A.set_idx(start_rows);
      }
    } else {
      read_dist_tensor(file_name);
    }
  }
  void write(const std::string &output_file_name, DistAUNTF *ntfsolver) {
    std::stringstream sw;
    for (unsigned int i = 0; i < ntfsolver->modes(); i++) {
      sw << output_file_name << "_mode" << i << "_" << MPI_SIZE;
      MAT factort;
      if (this->m_mpicomm.fiber_rank(i) == 0) {
        factort = arma::zeros<MAT>(ntfsolver->rank(), this->m_global_dims[i]);
      }
      // This is a convenience barrier
      MPI_Barrier(MPI_COMM_WORLD);
      ntfsolver->factor(i, factort.memptr());
      // if (isparticipating(i) && this->m_mpicomm.fiber_rank(i) == 0) {
      if (ISROOT) {
        PRINTROOT("Writing factor " << i << " to " << sw.str());
        MAT current_factor = factort.t();
        current_factor.save(sw.str(), arma::raw_ascii);
      }
      MPI_Barrier(MPI_COMM_WORLD);
      sw.clear();
      sw.str("");
    }
    sw << output_file_name << "_lambda"
       << "_" << MPI_SIZE;
    if (ISROOT) {
      ntfsolver->lambda().save(sw.str(), arma::raw_ascii);
    }
  }
  void writeRandInput() {}
  const Tensor &A() const { return m_A; }
  const NTFMPICommunicator &mpicomm() const { return m_mpicomm; }
  UVEC global_dims() const { return m_global_dims; }
};
}  // namespace planc

#endif  // DISTNTF_DISTNTFIO_HPP_


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

// Copyright (c) 2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2015, 2019.
// Modifications copyright (c) 2015-2019, Oracle and/or its affiliates.

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

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

#ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_SIDE_OF_INTERSECTION_HPP
#define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_SIDE_OF_INTERSECTION_HPP

#include <limits>

#include <boost/core/ignore_unused.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/make_unsigned.hpp>

#include <boost/geometry/arithmetic/determinant.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/assert.hpp>
#include <boost/geometry/core/coordinate_type.hpp>
#include <boost/geometry/algorithms/detail/assign_indexed_point.hpp>
#include <boost/geometry/strategies/cartesian/side_by_triangle.hpp>
#include <boost/geometry/util/math.hpp>

#ifdef BOOST_GEOMETRY_SIDE_OF_INTERSECTION_DEBUG
#include <boost/math/common_factor_ct.hpp>
#include <boost/math/common_factor_rt.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#endif

namespace boost { namespace geometry
{

namespace strategy { namespace side
{

namespace detail
{

// A tool for multiplication of integers avoiding overflow
// It's a temporary workaround until we can use Multiprecision
// The algorithm is based on Karatsuba algorithm
// see: http://en.wikipedia.org/wiki/Karatsuba_algorithm
template <typename T>
struct multiplicable_integral
{
    // Currently this tool can't be used with non-integral coordinate types.
    // Also side_of_intersection strategy sign_of_product() and sign_of_compare()
    // functions would have to be modified to properly support floating-point
    // types (comparisons and multiplication).
    BOOST_STATIC_ASSERT(boost::is_integral<T>::value);

    static const std::size_t bits = CHAR_BIT * sizeof(T);
    static const std::size_t half_bits = bits / 2;
    typedef typename boost::make_unsigned<T>::type unsigned_type;
    static const unsigned_type base = unsigned_type(1) << half_bits; // 2^half_bits

    int m_sign;
    unsigned_type m_ms;
    unsigned_type m_ls;

    multiplicable_integral(int sign, unsigned_type ms, unsigned_type ls)
        : m_sign(sign), m_ms(ms), m_ls(ls)
    {}

    explicit multiplicable_integral(T const& val)
    {
        unsigned_type val_u = val > 0 ?
                                unsigned_type(val)
                              : val == (std::numeric_limits<T>::min)() ?
                                  unsigned_type((std::numeric_limits<T>::max)()) + 1
                                : unsigned_type(-val);
        // MMLL -> S 00MM 00LL
        m_sign = math::sign(val);
        m_ms = val_u >> half_bits; // val_u / base
        m_ls = val_u - m_ms * base;
    }

    friend multiplicable_integral operator*(multiplicable_integral const& a,
                                            multiplicable_integral const& b)
    {
        // (S 00MM 00LL) * (S 00MM 00LL) -> (S Z2MM 00LL)
        unsigned_type z2 = a.m_ms * b.m_ms;
        unsigned_type z0 = a.m_ls * b.m_ls;
        unsigned_type z1 = (a.m_ms + a.m_ls) * (b.m_ms + b.m_ls) - z2 - z0;
        // z0 may be >= base so it must be normalized to allow comparison
        unsigned_type z0_ms = z0 >> half_bits; // z0 / base
        return multiplicable_integral(a.m_sign * b.m_sign,
                                      z2 * base + z1 + z0_ms,
                                      z0 - base * z0_ms);
    }

    friend bool operator<(multiplicable_integral const& a,
                          multiplicable_integral const& b)
    {
        if ( a.m_sign == b.m_sign )
        {
            bool u_less = a.m_ms < b.m_ms
                      || (a.m_ms == b.m_ms && a.m_ls < b.m_ls);
            return a.m_sign > 0 ? u_less : (! u_less);
        }
        else
        {
            return a.m_sign < b.m_sign;
        }
    }

    friend bool operator>(multiplicable_integral const& a,
                          multiplicable_integral const& b)
    {
        return b < a;
    }

#ifdef BOOST_GEOMETRY_SIDE_OF_INTERSECTION_DEBUG
    template <typename CmpVal>
    void check_value(CmpVal const& cmp_val) const
    {
        unsigned_type b = base; // a workaround for MinGW - undefined reference base
        CmpVal val = CmpVal(m_sign) * (CmpVal(m_ms) * CmpVal(b) + CmpVal(m_ls));
        BOOST_GEOMETRY_ASSERT(cmp_val == val);
    }
#endif // BOOST_GEOMETRY_SIDE_OF_INTERSECTION_DEBUG
};

} // namespace detail

// Calculates the side of the intersection-point (if any) of
// of segment a//b w.r.t. segment c
// This is calculated without (re)calculating the IP itself again and fully
// based on integer mathematics; there are no divisions
// It can be used for either integer (rescaled) points, and also for FP
class side_of_intersection
{
private :
    template <typename T, typename U>
    static inline
    int sign_of_product(T const& a, U const& b)
    {
        return a == 0 || b == 0 ? 0
            : a > 0 && b > 0 ? 1
            : a < 0 && b < 0 ? 1
            : -1;
    }

    template <typename T>
    static inline
    int sign_of_compare(T const& a, T const& b, T const& c, T const& d)
    {
        // Both a*b and c*d are positive
        // We have to judge if a*b > c*d

        using side::detail::multiplicable_integral;
        multiplicable_integral<T> ab = multiplicable_integral<T>(a)
                                     * multiplicable_integral<T>(b);
        multiplicable_integral<T> cd = multiplicable_integral<T>(c)
                                     * multiplicable_integral<T>(d);

        int result = ab > cd ? 1
                   : ab < cd ? -1
                   : 0
                   ;

#ifdef BOOST_GEOMETRY_SIDE_OF_INTERSECTION_DEBUG
        using namespace boost::multiprecision;
        cpp_int const lab = cpp_int(a) * cpp_int(b);
        cpp_int const lcd = cpp_int(c) * cpp_int(d);

        ab.check_value(lab);
        cd.check_value(lcd);

        int result2 = lab > lcd ? 1
                    : lab < lcd ? -1
                    : 0
                    ;
        BOOST_GEOMETRY_ASSERT(result == result2);
#endif

        return result;
    }

    template <typename T>
    static inline
    int sign_of_addition_of_two_products(T const& a, T const& b, T const& c, T const& d)
    {
        // sign of a*b+c*d, 1 if positive, -1 if negative, else 0
        int const ab = sign_of_product(a, b);
        int const cd = sign_of_product(c, d);
        if (ab == 0)
        {
            return cd;
        }
        if (cd == 0)
        {
            return ab;
        }

        if (ab == cd)
        {
            // Both positive or both negative
            return ab;
        }

        // One is positive, one is negative, both are non zero
        // If ab is positive, we have to judge if a*b > -c*d (then 1 because sum is positive)
        // If ab is negative, we have to judge if c*d > -a*b (idem)
        return ab == 1
            ? sign_of_compare(a, b, -c, d)
            : sign_of_compare(c, d, -a, b);
    }

public :

    // Calculates the side of the intersection-point (if any) of
    // of segment a//b w.r.t. segment c
    // This is calculated without (re)calculating the IP itself again and fully
    // based on integer mathematics
    template <typename T, typename Segment, typename Point>
    static inline T side_value(Segment const& a, Segment const& b,
                Segment const& c, Point const& fallback_point)
    {
        // The first point of the three segments is reused several times
        T const ax = get<0, 0>(a);
        T const ay = get<0, 1>(a);
        T const bx = get<0, 0>(b);
        T const by = get<0, 1>(b);
        T const cx = get<0, 0>(c);
        T const cy = get<0, 1>(c);

        T const dx_a = get<1, 0>(a) - ax;
        T const dy_a = get<1, 1>(a) - ay;

        T const dx_b = get<1, 0>(b) - bx;
        T const dy_b = get<1, 1>(b) - by;

        T const dx_c = get<1, 0>(c) - cx;
        T const dy_c = get<1, 1>(c) - cy;

        // Cramer's rule: d (see cart_intersect.hpp)
        T const d = geometry::detail::determinant<T>
                    (
                        dx_a, dy_a,
                        dx_b, dy_b
                    );

        T const zero = T();
        if (d == zero)
        {
            // There is no IP of a//b, they are collinear or parallel
            // Assuming they intersect (this method should be called for
            // segments known to intersect), they are collinear and overlap.
            // They have one or two intersection points - we don't know and
            // have to rely on the fallback intersection point

            Point c1, c2;
            geometry::detail::assign_point_from_index<0>(c, c1);
            geometry::detail::assign_point_from_index<1>(c, c2);
            return side_by_triangle<>::apply(c1, c2, fallback_point);
        }

        // Cramer's rule: da (see cart_intersect.hpp)
        T const da = geometry::detail::determinant<T>
                    (
                        dx_b,    dy_b,
                        ax - bx, ay - by
                    );

        // IP is at (ax + (da/d) * dx_a, ay + (da/d) * dy_a)
        // Side of IP is w.r.t. c is: determinant(dx_c, dy_c, ipx-cx, ipy-cy)
        // We replace ipx by expression above and multiply each term by d

#ifdef BOOST_GEOMETRY_SIDE_OF_INTERSECTION_DEBUG
        T const result1 = geometry::detail::determinant<T>
                    (
                        dx_c * d,                   dy_c * d,
                        d * (ax - cx) + dx_a * da,  d * (ay - cy) + dy_a * da
                    );

        // Note: result / (d * d)
        // is identical to the side_value of side_by_triangle
        // Therefore, the sign is always the same as that result, and the
        // resulting side (left,right,collinear) is the same

        // The first row we divide again by d because of determinant multiply rule
        T const result2 = d * geometry::detail::determinant<T>
                    (
                        dx_c,                   dy_c,
                        d * (ax - cx) + dx_a * da,  d * (ay - cy) + dy_a * da
                    );
        // Write out:
        T const result3 = d * (dx_c * (d * (ay - cy) + dy_a * da)
                             - dy_c * (d * (ax - cx) + dx_a * da));
        // Write out in braces:
        T const result4 = d * (dx_c * d * (ay - cy) + dx_c * dy_a * da
                             - dy_c * d * (ax - cx) - dy_c * dx_a * da);
        // Write in terms of d * XX + da * YY
        T const result5 = d * (d * (dx_c * (ay - cy) - dy_c * (ax - cx))
                             + da * (dx_c * dy_a - dy_c * dx_a));

        boost::ignore_unused(result1, result2, result3, result4, result5);
        //return result;
#endif

        // We consider the results separately
        // (in the end we only have to return the side-value 1,0 or -1)

        // To avoid multiplications we judge the product (easy, avoids *d)
        // and the sign of p*q+r*s (more elaborate)
        T const result = sign_of_product
            (
                d,
                sign_of_addition_of_two_products
                    (
                        d, dx_c * (ay - cy) - dy_c * (ax - cx),
                        da, dx_c * dy_a - dy_c * dx_a
                    )
                );
        return result;

    }

    template <typename Segment, typename Point>
    static inline int apply(Segment const& a, Segment const& b,
            Segment const& c,
            Point const& fallback_point)
    {
        typedef typename geometry::coordinate_type<Segment>::type coordinate_type;
        coordinate_type const s = side_value<coordinate_type>(a, b, c, fallback_point);
        coordinate_type const zero = coordinate_type();
        return math::equals(s, zero) ? 0
            : s > zero ? 1
            : -1;
    }

};

}} // namespace strategy::side

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_SIDE_OF_INTERSECTION_HPP