// Copyright (c) 2015-2019 The Bitcoin Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <pow/daa.h>

#include <chain.h>
#include <chainparams.h>
#include <config.h>

#include <test/util/setup_common.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(daa_tests, BasicTestingSetup)

static CBlockIndex GetBlockIndex(CBlockIndex *pindexPrev, int64_t nTimeInterval,
                                 uint32_t nBits) {
    CBlockIndex block;
    block.pprev = pindexPrev;
    block.nHeight = pindexPrev->nHeight + 1;
    block.nTime = pindexPrev->nTime + nTimeInterval;
    block.nBits = nBits;

    block.nChainWork = pindexPrev->nChainWork + GetBlockProof(block);
    return block;
}

BOOST_AUTO_TEST_CASE(daa_test) {
    DummyConfig config(CBaseChainParams::MAIN);

    std::vector<CBlockIndex> blocks(3000);

    const Consensus::Params &params = config.GetChainParams().GetConsensus();
    const arith_uint256 powLimit = UintToArith256(params.powLimit);
    uint32_t powLimitBits = powLimit.GetCompact();
    arith_uint256 currentPow = powLimit >> 4;
    uint32_t initialBits = currentPow.GetCompact();

    // Genesis block.
    blocks[0] = CBlockIndex();
    blocks[0].nHeight = 0;
    blocks[0].nTime = 1269211443;
    blocks[0].nBits = initialBits;

    blocks[0].nChainWork = GetBlockProof(blocks[0]);

    // Block counter.
    size_t i;

    // Pile up some blocks every 10 mins to establish some history.
    for (i = 1; i < 2050; i++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 600, initialBits);
    }

    CBlockHeader blkHeaderDummy;
    uint32_t nBits =
        GetNextDAAWorkRequired(&blocks[2049], &blkHeaderDummy, params);

    // Difficulty stays the same as long as we produce a block every 10 mins.
    for (size_t j = 0; j < 10; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 600, nBits);
        BOOST_CHECK_EQUAL(
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params), nBits);
    }

    // Make sure we skip over blocks that are out of wack. To do so, we produce
    // a block that is far in the future, and then produce a block with the
    // expected timestamp.
    blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
    BOOST_CHECK_EQUAL(
        GetNextDAAWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);
    blocks[i] = GetBlockIndex(&blocks[i - 1], 2 * 600 - 6000, nBits);
    BOOST_CHECK_EQUAL(
        GetNextDAAWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);

    // The system should continue unaffected by the block with a bogous
    // timestamps.
    for (size_t j = 0; j < 20; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 600, nBits);
        BOOST_CHECK_EQUAL(
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params), nBits);
    }

    // We start emitting blocks slightly faster. The first block has no impact.
    blocks[i] = GetBlockIndex(&blocks[i - 1], 550, nBits);
    BOOST_CHECK_EQUAL(
        GetNextDAAWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);

    // Now we should see difficulty increase slowly.
    for (size_t j = 0; j < 10; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 550, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Make sure that difficulty increases very slowly.
        BOOST_CHECK(nextTarget < currentTarget);
        BOOST_CHECK((currentTarget - nextTarget) < (currentTarget >> 10));

        nBits = nextBits;
    }

    // Check the actual value.
    BOOST_CHECK_EQUAL(nBits, 0x1c0fe7b1);

    // If we dramatically shorten block production, difficulty increases faster.
    for (size_t j = 0; j < 20; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 10, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Make sure that difficulty increases faster.
        BOOST_CHECK(nextTarget < currentTarget);
        BOOST_CHECK((currentTarget - nextTarget) < (currentTarget >> 4));

        nBits = nextBits;
    }

    // Check the actual value.
    BOOST_CHECK_EQUAL(nBits, 0x1c0db19f);

    // We start to emit blocks significantly slower. The first block has no
    // impact.
    blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
    nBits = GetNextDAAWorkRequired(&blocks[i++], &blkHeaderDummy, params);

    // Check the actual value.
    BOOST_CHECK_EQUAL(nBits, 0x1c0d9222);

    // If we dramatically slow down block production, difficulty decreases.
    for (size_t j = 0; j < 93; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Check the difficulty decreases.
        BOOST_CHECK(nextTarget <= powLimit);
        BOOST_CHECK(nextTarget > currentTarget);
        BOOST_CHECK((nextTarget - currentTarget) < (currentTarget >> 3));

        nBits = nextBits;
    }

    // Check the actual value.
    BOOST_CHECK_EQUAL(nBits, 0x1c2f13b9);

    // Due to the window of time being bounded, next block's difficulty actually
    // gets harder.
    blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
    nBits = GetNextDAAWorkRequired(&blocks[i++], &blkHeaderDummy, params);
    BOOST_CHECK_EQUAL(nBits, 0x1c2ee9bf);

    // And goes down again. It takes a while due to the window being bounded and
    // the skewed block causes 2 blocks to get out of the window.
    for (size_t j = 0; j < 192; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Check the difficulty decreases.
        BOOST_CHECK(nextTarget <= powLimit);
        BOOST_CHECK(nextTarget > currentTarget);
        BOOST_CHECK((nextTarget - currentTarget) < (currentTarget >> 3));

        nBits = nextBits;
    }

    // Check the actual value.
    BOOST_CHECK_EQUAL(nBits, 0x1d00ffff);

    // Once the difficulty reached the minimum allowed level, it doesn't get any
    // easier.
    for (size_t j = 0; j < 5; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 6000, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        // Check the difficulty stays constant.
        BOOST_CHECK_EQUAL(nextBits, powLimitBits);
        nBits = nextBits;
    }

    // We will now mine blocks much faster. For a while, the difficulty will
    // continue to go down.
    for (size_t j = 0; j < 130; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 10, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Check the difficulty decreases.
        BOOST_CHECK(nextTarget <= powLimit);
        BOOST_CHECK(nextTarget >= currentTarget);
        BOOST_CHECK((nextTarget - currentTarget) < (currentTarget >> 3));

        nBits = nextBits;
    }

    // Now the difficulty will go back up, and evetually we will trigger the
    // cliff code.
    for (size_t j = 0; j < 70; i++, j++) {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 10, nBits);
        const uint32_t nextBits =
            GetNextDAAWorkRequired(&blocks[i], &blkHeaderDummy, params);

        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Check the difficulty decreases.
        BOOST_CHECK(nextTarget < currentTarget);
        BOOST_CHECK((currentTarget - nextTarget) < (currentTarget >> 3));

        nBits = nextBits;
    }

    // Check the actual value.
    BOOST_CHECK_EQUAL(nBits, 0x1c4c068c);
}

BOOST_AUTO_TEST_SUITE_END()


//////////////////////////////////////////////////////////////////////////////
///   Copyright 2003 and onward LASMEA UMR 6602 CNRS/U.B.P Clermont-Ferrand
///   Copyright 2009 and onward LRI    UMR 8623 CNRS/Univ Paris Sud XI
///
///          Distributed under the Boost Software License, Version 1.0
///                 See accompanying file LICENSE.txt or copy at
///                     http://www.boost.org/LICENSE_1_0.txt
//////////////////////////////////////////////////////////////////////////////
#ifndef NT2_TOOLBOX_ARITHMETIC_FUNCTION_SIMD_COMMON_RDIVIDE_HPP_INCLUDED
#define NT2_TOOLBOX_ARITHMETIC_FUNCTION_SIMD_COMMON_RDIVIDE_HPP_INCLUDED
#include <nt2/sdk/meta/upgrade.hpp>
#include <nt2/sdk/constant/digits.hpp>
#include <boost/fusion/tuple.hpp>
#include <nt2/sdk/meta/strip.hpp>
#include <nt2/include/functions/is_eqz.hpp>
#include <nt2/include/functions/group.hpp>
#include <nt2/include/functions/split.hpp>
#include <nt2/include/functions/toint.hpp>
#include <nt2/include/functions/tofloat.hpp>

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is arithmetic_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<arithmetic_<A0>,X>))
                          ((simd_<arithmetic_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::arithmetic_, X> ,
                            tag::simd_<tag::arithmetic_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      const A0 iseqza1 = is_eqz(a1);
      return (a0-(iseqza1&a0))/(a1+(iseqza1&One<A0>()));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is int8_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<int8_<A0>,X>))
                          ((simd_<int8_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::int8_, X> ,
                            tag::simd_<tag::int8_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      typedef typename meta::scalar_of<A0>::type              stype;
      typedef typename meta::upgrade<stype>::type             itype;
      typedef simd::native<itype, X>                 ivtype;
      ivtype a0l, a0h, a1l, a1h;
      boost::fusion::tie(a0l, a0h) = split(a0);
      boost::fusion::tie(a1l, a1h) = split(a1);
      return simd::native_cast<A0>(group(nt2::rdivide(a0l, a1l),
                               nt2::rdivide(a0h, a1h) ));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is int16_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<int16_<A0>,X>))
                          ((simd_<int16_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::int16_, X> ,
                            tag::simd_<tag::int16_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      typedef typename meta::scalar_of<A0>::type             stype;
      typedef typename meta::upgrade<stype>::type            itype;
      typedef simd::native<itype,X>                 ivtype;
      ivtype a0l, a0h, a1l, a1h;
      boost::fusion::tie(a0l, a0h) = split(a0);
      boost::fusion::tie(a1l, a1h) = split(a1);
//       std::cout << type_id < ivtype > () << std::endl;
//       std::cout << a1l << std::endl;
//       std::cout << a1h << std::endl;
//       std::cout << a0l << std::endl;
//       std::cout << a0h << std::endl;
      return sel(is_eqz(a1),
		 Zero<A0>(),
		 simd::native_cast<A0>(group(toint(tofloat(a0l)/tofloat(a1l)),
					     toint(tofloat(a0h)/tofloat(a1h)))));
    }
  };
} }

/////////////////////////////////////////////////////////////////////////////
// Implementation when type A0 is real_
/////////////////////////////////////////////////////////////////////////////
NT2_REGISTER_DISPATCH(tag::rdivide_, tag::cpu_,
                          (A0)(X),
                          ((simd_<real_<A0>,X>))
                          ((simd_<real_<A0>,X>))
                         );

namespace nt2 { namespace ext
{
  template<class X, class Dummy>
  struct call<tag::rdivide_(tag::simd_<tag::real_, X> ,
                            tag::simd_<tag::real_, X> ),
              tag::cpu_, Dummy> : callable
  {
    template<class Sig> struct result;
    template<class This,class A0>
    struct result<This(A0,A0)>
      : meta::strip<A0>{};//

    NT2_FUNCTOR_CALL(2)
    {
      return b_or(b_and(is_eqz(a0), is_eqz(a1)), a0/a1);
    }
  };
} }

#endif
// modified by jt the 04/01/2011


#include "Xfa.hpp"
#include "ast.hpp"
#include "RelationMaker.hpp"

#include <wali/util/DisjointSets.hpp>

#include <boost/lexical_cast.hpp>

#include <iostream>
#include <vector>
#include <sstream>

namespace details
{
extern
void
interleave_all_fdds();

extern
void
print_bdd_variable_order(std::ostream & os);

}

namespace wali {
    namespace xfa {

        struct ReadTransitionException {
            sem_elem_t back_weight, bit1_weight, init_weight;

            ReadTransitionException(sem_elem_t init, sem_elem_t back, sem_elem_t bit1)
                : back_weight(back)
                , bit1_weight(bit1)
                , init_weight(init)
            {}
        };

        struct WeightedTransition {
            State source;
            Symbol symbol;
            State target;
            sem_elem_t weight;

            WeightedTransition(State src, Symbol sym, State tgt, sem_elem_t w)
                : source(src), symbol(sym), target(tgt), weight(w)
            {
                assert (weight.get_ptr());
            }
        };

        typedef std::vector<WeightedTransition> TransList;

        std::string
        var_name(int id,
                 std::string const & domain_var_name_prefix)
        {
            std::stringstream os;
            os << domain_var_name_prefix
               << "var"
               << id;
            return os.str();
        }

        typedef wali::util::DisjointSets<std::string> StringSets;

        std::vector<std::map<std::string, int> >
        disjoint_sets_to_var_order(StringSets const & sets,
                                   int fdd_size)
        {
            std::vector<std::map<std::string, int> > answer;
            for (auto outer_iter = sets.begin(); outer_iter != sets.end(); ++outer_iter) {
                answer.push_back(std::map<std::string, int>());
                for (auto inner_iter = outer_iter->begin();
                     inner_iter != outer_iter->end(); ++inner_iter)
                {
                    answer.back()[*inner_iter] = fdd_size;
                }
            }
            return answer;
        }

        std::vector<std::map<std::string, int> >
        get_vars(XfaAst const & ast,
                      int fdd_size,
                      std::string const & prefix)
        {
            StringSets sets;

            std::cerr << "Registering variables with size " << fdd_size << "\n";
            std::set<std::string> registered;
            for (auto ast_trans = ast.transitions.begin(); ast_trans != ast.transitions.end(); ++ast_trans) {
                ast::ActionList & acts = (*ast_trans)->actions;
                for (auto act_it = acts.begin(); act_it != acts.end(); ++act_it) {
                    auto & act = **act_it;
                    if (act.action_type == "fire"
                        || act.action_type == "alert"
                        || act.action_type == "reject")
                    {
                        continue;
                    }
                    assert (act.action_type == "ctr2");
                    if (registered.find(var_name(act.operand_id, prefix)) == registered.end()) {
                        std::cerr << "    " << var_name(act.operand_id, prefix) << "\n";
                        registered.insert(var_name(act.operand_id, prefix));
                    }

                    auto cmd = *act.command;
                    if (cmd.name == "testnectr2") {
                        assert(cmd.arguments.size() == 1u);
                        assert(cmd.consequent);
                        assert(!cmd.alternative);
                        assert(cmd.consequent->action_type == "reject");

                        int rhs_id = boost::lexical_cast<int>(cmd.arguments[0]);
                        std::string lhs_name = var_name(act.operand_id, prefix);
                        std::string rhs_name = var_name(rhs_id, prefix);

                        sets.merge_sets(lhs_name, rhs_name);
                    }
                }
            }

            std::cerr << "Here is the set of variables: " << sets.to_string();

            std::vector<std::map<std::string, int> > vars = disjoint_sets_to_var_order(sets, fdd_size);
            vars.push_back(std::map<std::string, int>());

            //voc.addIntVar("left_current_state", ast.states.size());
            vars.back()[prefix + "current_state"] = static_cast<int>(ast.states.size()) * 2;
            std::cout << "Adding " << prefix << "current_state with size " << vars.back()[prefix + "current_state"];

            return vars;

            //voc.setIntVars(vars);
            //details::interleave_all_fdds();
            //::details::print_bdd_variable_order(std::cout);
        }

        sem_elem_t
        get_relation(RelationMaker const & maker,
                     ast::Action const & act,
                     std::string const & prefix)
        {
            using namespace wali::domains::binrel;

            if (act.action_type == "fire"
                || act.action_type == "alert")
            {
                std::cerr << "    fire or alert\n";
                // Hmmm.
                abort();
                return sem_elem_t();
            }

            assert(act.command);
            auto cmd = *act.command;

            if (cmd.name == "read") {
                sem_elem_t times2, plus1, init;
                std::string varname = var_name(act.operand_id, prefix);
                {
                    init = maker.initialize_variable_to_val(varname, 0);
                }
                {
                    times2 = maker.multiply_variable_by_two(varname);
                }
                {
                    plus1 = maker.increment_variable(varname);
                }
                throw ReadTransitionException(init, times2, plus1);
            }

            if (cmd.name == "reset") {
                assert(cmd.arguments.size() == 1u);
                int val = boost::lexical_cast<int>(cmd.arguments[0]);

                return maker.initialize_variable_to_val(var_name(act.operand_id, prefix),
                                                        val);
            }

            if (cmd.name == "incr") {
                return maker.increment_variable(var_name(act.operand_id, prefix));
            }

            if (cmd.name == "testnectr2") {
                assert(cmd.arguments.size() == 1u);
                assert(cmd.consequent);
                assert(!cmd.alternative);
                assert(cmd.consequent->action_type == "reject");

                int rhs_id = boost::lexical_cast<int>(cmd.arguments[0]);
                std::string lhs_name = var_name(act.operand_id, prefix);
                std::string rhs_name = var_name(rhs_id, prefix);

                return maker.assume_equality(lhs_name, rhs_name);
            }

            assert(false);
        }

        TransList
        get_transitions(RelationMaker const & maker,
                        ast::Transition const & trans,
                        std::string const & prefix)
        {
            State source = getState(trans.source);
            State dest = getState(trans.dest);
            Symbol eps(wali::WALI_EPSILON);

            using wali::domains::binrel::BinRel;
            sem_elem_t rel;

            TransList ret;

            try {
                if (trans.actions.size() == 0u) {
                    rel = maker.one();
                    //rel->is_effectively_one = true;
                }
                else if (trans.actions.size() == 1u) {
                    rel = get_relation(maker, *trans.actions[0], prefix);
                }
                else {
                    assert(false);
                }

                auto const & syms = trans.symbols;
                for (auto sym = syms.begin(); sym != syms.end(); ++sym) {
                    auto name = dynamic_cast<ast::Name*>(sym->get());
                    assert(name);
                    if (name->name == "epsilon") {
                        ret.push_back(WeightedTransition(source, eps, dest, rel));
                    }
                    else {
                        ret.push_back(WeightedTransition(source, getSymbol(name->name), dest, rel));
                    }
                }
            }
            catch (ReadTransitionException & e) {
                auto const & syms = trans.symbols;
                for (auto sym = syms.begin(); sym != syms.end(); ++sym) {
                    auto name = dynamic_cast<ast::Name*>(sym->get());
                    assert(name->name != "epsilon");

                    // source ---> intermediate_name ---> dest
                    //                               <---
                    std::stringstream intermediate_name;
                    intermediate_name << trans.source << "__" << name->name;
                    std::stringstream bit0_name, bit1_name;;
                    bit0_name << name->name << "__bit_is_0";
                    bit1_name << name->name << "__bit_is_1";
                    Symbol startbits = getSymbol("__startbits");
                    Symbol bit0 = getSymbol(bit0_name.str());
                    Symbol bit1 = getSymbol(bit1_name.str());
                    State intermediate = getState(intermediate_name.str());

                    // source --> intermediate has identity weight, symbol '__startbits'
                    assert (e.init_weight.get_ptr());
                    ret.push_back(WeightedTransition(source, startbits, intermediate, e.init_weight));

                    // intermediate --> dest has two transitions:
                    //     '__bit_0' has identity weight
                    //     '__bit_1' has +1 weight
                    ret.push_back(WeightedTransition(intermediate, bit0, dest, maker.one()));
                    ret.push_back(WeightedTransition(intermediate, bit1, dest, e.bit1_weight));

                    // dest --> intermediate has epsilon and weight *2
                    ret.push_back(WeightedTransition(dest, eps, intermediate, e.back_weight));
                }
            }

            return ret;
        }

        Xfa
        from_parser_ast(RelationMaker const & maker,
                        XfaAst const & ast,
                        int UNUSED_PARAMETER(fdd_size),
                        std::string const & domain_var_name_prefix)
        {
            using namespace wali::domains::binrel;
            Symbol eps(wali::WALI_EPSILON);

            Xfa ret;

            for (auto ast_state = ast.states.begin(); ast_state != ast.states.end(); ++ast_state) {
                State state = getState((*ast_state)->name);
                ret.addState(state, maker.zero());
            }

            for (auto ast_trans = ast.transitions.begin(); ast_trans != ast.transitions.end(); ++ast_trans) {
                TransList transs = get_transitions(maker, **ast_trans, domain_var_name_prefix);
                for (auto trans = transs.begin(); trans != transs.end(); ++trans) {
                    ret.addTrans(trans->source, trans->symbol, trans->target, trans->weight);
                }
            }

            ret.setInitialState(getState(ast.start_state));

            return ret;
        }
    }
}

#include "BddRelationMaker.hpp"


/*!
@file
Defines `boost::hana::List::mcd`.

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

#ifndef BOOST_HANA_LIST_MCD_HPP
#define BOOST_HANA_LIST_MCD_HPP

#include <boost/hana/list/list.hpp>

#include <boost/hana/applicative/mcd.hpp>
#include <boost/hana/comparable/equal_mcd.hpp>
#include <boost/hana/core/convert.hpp>
#include <boost/hana/core/is_a.hpp>
#include <boost/hana/detail/variadic/foldr.hpp>
#include <boost/hana/foldable/foldable.hpp>
#include <boost/hana/functor/fmap_mcd.hpp>
#include <boost/hana/group/group.hpp>
#include <boost/hana/integral.hpp>
#include <boost/hana/iterable/iterable.hpp>
#include <boost/hana/logical/logical.hpp>
#include <boost/hana/maybe.hpp>
#include <boost/hana/monad/flatten_mcd.hpp>
#include <boost/hana/orderable/orderable.hpp>
#include <boost/hana/pair.hpp>
#include <boost/hana/searchable/mcd.hpp>
#include <boost/hana/traversable/traverse_mcd.hpp>

namespace boost { namespace hana {
    //! Minimal complete definition: `Iterable`, `Foldable`, `cons`, and `nil`
    template <typename T>
    struct List::mcd {
        template <typename Xs, typename Ys>
        static constexpr auto concat_impl(Xs xs, Ys ys)
        { return foldr(xs, ys, cons); }

        template <typename Xs, typename Pred>
        static constexpr auto filter_impl(Xs xs, Pred pred) {
            auto go = [=](auto x, auto xs) {
                return eval_if(pred(x),
                    [=](auto _) { return _(cons)(x, xs); },
                    [=](auto _) { return xs; }
                );
            };
            return foldr(xs, nil<T>, go);
        }

        template <typename Pred, typename Xs>
        static constexpr auto group_by_impl(Pred pred, Xs xs_) {
            return eval_if(is_empty(xs_),
                [](auto) { return nil<T>; },
                [=](auto _) {
                    auto x = _(head)(xs_);
                    auto xs = _(tail)(xs_);
                    auto ys_zs = span(xs, [=](auto y) { return pred(x, y); });
                    auto ys = first(ys_zs);
                    auto zs = second(ys_zs);
                    return cons(cons(x, ys), group_by_impl(pred, zs));
                }
            );
        }

        template <typename Xs>
        static constexpr auto group_impl(Xs xs) {
            return group_by(equal, xs);
        }

        template <typename Xs>
        static constexpr auto init_impl(Xs xs) {
            return eval_if(is_empty(tail(xs)),
                [](auto _) { return nil<T>; },
                [=](auto _) { return cons(_(head)(xs), init_impl(_(tail)(xs))); }
            );
        }

        template <typename ...Xs>
        static constexpr auto into_impl(Xs ...xs) {
            return detail::variadic::foldr(cons, nil<T>, xs...);
        }

        template <typename Xs, typename Pred>
        static constexpr auto partition_impl(Xs xs, Pred pred) {
            auto go = [=](auto parts, auto x) {
                return eval_if(pred(x),
                    [=](auto _) { return pair(_(snoc)(first(parts), x), second(parts)); },
                    [=](auto _) { return pair(first(parts), _(snoc)(second(parts), x)); }
                );
            };
            return foldl(xs, pair(nil<T>, nil<T>), go);
        }

    private:
        template <typename X, typename Xs>
        static constexpr auto insertions(X x, Xs l) {
            return eval_if(is_empty(l),
                [=](auto _) { return lift<T>(lift<T>(x)); },
                [=](auto _) {
                    auto y = _(head)(l);
                    auto ys = _(tail)(l);
                    return cons(
                        _(cons)(x, l),
                        fmap([=](auto zs) { return cons(y, zs); }, insertions(x, ys))
                    );
                }
            );
        }

    public:
        template <typename Xs>
        static constexpr auto permutations_impl(Xs xs) {
            return eval_if(is_empty(xs),
                [](auto _) { return lift<T>(nil<T>); },
                [=](auto _) {
                    return flatten(fmap(
                        [=](auto ys) { return insertions(_(head)(xs), ys); },
                        permutations_impl(_(tail)(xs))
                    ));
                }
            );
        }

        template <typename Xs>
        static constexpr auto reverse_impl(Xs xs) {
            return foldl(xs, nil<T>, [](auto xs, auto x) {
                return cons(x, xs);
            });
        }

        template <typename Xs, typename S, typename F>
        static constexpr auto scanl_impl(Xs xs, S s, F f) {
            return eval_if(is_empty(xs),
                [=](auto _) { return lift<T>(s); },
                [=](auto _) {
                    return cons(s, scanl_impl(_(tail)(xs), f(s, _(head)(xs)), f));
                }
            );
        }

        template <typename Xs, typename F>
        static constexpr auto scanl1_impl(Xs xs, F f) {
            return eval_if(is_empty(xs),
                [](auto _) { return nil<T>; },
                [=](auto _) { return scanl(_(tail)(xs), _(head)(xs), f); }
            );
        }

        template <typename Xs, typename S, typename F>
        static constexpr auto scanr_impl(Xs xs, S s, F f) {
            return eval_if(is_empty(xs),
                [=](auto _) { return lift<T>(s); },
                [=](auto _) {
                    auto rest = scanr_impl(_(tail)(xs), s, f);
                    return cons(f(_(head)(xs), head(rest)), rest);
                }
            );
        }

        template <typename Lst, typename F>
        static constexpr auto scanr1_impl(Lst lst, F f) {
            return eval_if(is_empty(lst),
                [](auto _) { return nil<T>; },
                [=](auto _) {
                    auto x = _(head)(lst);
                    auto xs = _(tail)(lst);
                    return eval_if(is_empty(xs),
                        [=](auto _) { return lift<T>(x); },
                        [=](auto _) {
                            auto rest = scanr1_impl(xs, _(f));
                            return cons(f(x, head(rest)), rest);
                        }
                    );
                }
            );
        }

        template <typename Xs, typename X>
        static constexpr auto snoc_impl(Xs xs, X x) {
            return foldr(xs, lift<T>(x), cons);
        }

        template <typename Xs>
        static constexpr auto sort_impl(Xs xs) {
            return sort_by(less, xs);
        }

        template <typename Pred, typename Xs>
        static constexpr auto sort_by_impl(Pred pred, Xs xs) {
            return eval_if(is_empty(xs),
                [=](auto _) { return xs; },
                [=](auto _) {
                    return eval_if(is_empty(_(tail)(xs)),
                        [=](auto _) { return xs; },
                        [=](auto _) {
                            auto pivot = _(head)(xs);
                            auto rest = _(tail)(xs);
                            auto parts = partition(rest, [=](auto x) { return pred(x, pivot); });
                            return concat(
                                sort_by_impl(pred, first(parts)),
                                cons(pivot, sort_by_impl(pred, second(parts)))
                            );
                        }
                    );
                }
            );
        }

        template <typename Xs, typename Pred>
        static constexpr auto span_impl(Xs xs, Pred pred) {
            return eval_if(is_empty(xs),
                [=](auto _) { return pair(nil<T>, nil<T>); },
                [=](auto _) {
                    auto x = _(head)(xs);
                    auto xs_ = _(tail)(xs);
                    return eval_if(pred(x),
                        [=](auto _) {
                            auto ys_zs = span_impl(xs_, _(pred));
                            auto ys = first(ys_zs);
                            auto zs = second(ys_zs);
                            return pair(cons(x, ys), zs);
                        },
                        [=](auto _) {
                            return pair(nil<T>, xs);
                        }
                    );
                }
            );
        }

        template <typename N, typename Xs>
        static constexpr auto take_impl(N n, Xs xs) {
            return eval_if(or_(is_empty(xs), equal(n, int_<0>)),
                [](auto _) { return nil<T>; },
                [=](auto _) {
                    return cons(_(head)(xs), take_impl(minus(n, int_<1>), _(tail)(xs)));
                }
            );
        }

        template <typename Pred, typename Xs>
        static constexpr auto take_until_impl(Pred pred, Xs xs) {
            return take_while([=](auto x) { return not_(pred(x)); }, xs);
        }

        template <typename Pred, typename Xs>
        static constexpr auto take_while_impl(Pred pred, Xs xs) {
            return eval_if(is_empty(xs),
                [=](auto _) { return nil<T>; },
                [=](auto _) {
                    return eval_if(pred(_(head)(xs)),
                        [=](auto _) {
                            return cons(
                                _(head)(xs),
                                take_while_impl(pred, _(tail)(xs))
                            );
                        },
                        [=](auto _) { return nil<T>; }
                    );
                }
            );
        }

        template <typename F, typename Init>
        static constexpr auto unfoldr_impl(F f, Init init) {
            auto g = [=](auto a_b) {
                return cons(first(a_b), unfoldr_impl(f, second(a_b)));
            };
            return maybe(nil<T>, g, f(init));
        }

        template <typename F, typename Init>
        static constexpr auto unfoldl_impl(F f, Init init) {
            auto g = [=](auto b_a) {
                return snoc(unfoldl_impl(f, first(b_a)), second(b_a));
            };
            return maybe(nil<T>, g, f(init));
        }

        template <typename Xs>
        static constexpr auto unzip_impl(Xs xs)
        { return unpack(xs, zip); }

        template <typename ...Xss>
        static constexpr auto zip_impl(Xss ...xss)
        { return zip_with(into<T>, xss...); }

        template <typename F, typename ...Xss>
        static constexpr auto zip_with_impl(F f, Xss ...xss) {
            return eval_if(any_of(into<T>(is_empty(xss)...)),
                [](auto _) { return nil<T>; },
                [=](auto _) {
                    return cons(
                        f(_(head)(xss)...),
                        zip_with_impl(f, _(tail)(xss)...)
                    );
                }
            );
        }
    };

    //! @details
    //! `List`s implement `fmap` as the mapping of a function over each
    //! element of the list. This is somewhat equivalent to `std::transform`.
    //! Mapping a function over an empty list returns an empty list and never
    //! applies the function.
    //!
    //! ### Example 1
    //! @snippet example/list/functor/fmap.cpp main
    //!
    //! ### Example 2
    //! @snippet example/type_list/functor/fmap.cpp main
    template <typename T>
    struct Functor::instance<T, when<is_a<List, T>()>>
        : Functor::fmap_mcd
    {
        template <typename F, typename Xs>
        static constexpr auto fmap_impl(F f, Xs xs) {
            return foldr(xs, nil<T>, [=](auto x, auto xs) {
                return cons(f(x), xs);
            });
        }
    };

    //! `Applicative` instance for instances of the `List` type class.
    //!
    //! A value can be lifted into a singleton list with `lift`. `ap(fs, xs)`
    //! applies each function in the list `fs` to each value in the list `xs`,
    //! and returns a list containing all the results.
    //!
    //! ### Example
    //! @snippet example/list/applicative.cpp main
    template <typename T>
    struct Applicative::instance<T, when<is_a<List, T>()>>
        : Applicative::mcd
    {
        template <typename X>
        static constexpr auto lift_impl(X x)
        { return cons(x, nil<T>); }

        template <typename Fs, typename Xs>
        static constexpr auto ap_impl(Fs fs, Xs xs)
        { return bind(fs, [=](auto f) { return fmap(f, xs); }); }
    };

    //! `Monad` instance for instances of the `List` type class.
    //!
    //! A function returning a list of results can be mapped over all the
    //! elements of a list and have all the results concatenated using `bind`.
    //! Also, a list of lists can be flattened one level with `flatten`.
    //!
    //! ### Example
    //! @snippet example/list/monad.cpp main
    template <typename T>
    struct Monad::instance<T, when<is_a<List, T>()>>
        : Monad::flatten_mcd<T>
    {
        template <typename Xss>
        static constexpr auto flatten_impl(Xss xss)
        { return foldl(xss, nil<T>, concat); }
    };

    //! `Traversable` instance for `List` instances.
    //!
    //! ### Example
    //! @snippet example/list/traversable/traverse.cpp main
    //!
    //! ### Example
    //! @snippet example/list/traversable/sequence.cpp main
    template <typename T>
    struct Traversable::instance<T, when<is_a<List, T>()>>
        : Traversable::traverse_mcd
    {
        template <typename A, typename F, typename Xs>
        static constexpr auto traverse_impl(F f, Xs xs) {
            auto curried_cons = [](auto x) {
                return [=](auto xs) { return cons(x, xs); };
            };
            return foldr(xs, lift<A>(nil<T>), [=](auto x, auto ys) {
                return ap(fmap(curried_cons, f(x)), ys);
            });
        }
    };

    //! @details
    //! Two `List`s are equal if and only if they contain the same number
    //! of elements and their elements at any given index are equal.
    //!
    //! ### Example
    //! @snippet example/list/comparable.cpp main
    template <typename T, typename U>
    struct Comparable::instance<T, U, when<
        is_a<List, T>() && is_a<List, U>()
    >> : Comparable::equal_mcd
    {
        template <typename Xs, typename Ys>
        static constexpr auto equal_impl(Xs xs, Ys ys) {
            return eval_if(or_(is_empty(xs), is_empty(ys)),
                [=](auto _) {
                    return and_(_(is_empty)(xs), _(is_empty)(ys));
                },
                [=](auto _) {
                    return and_(equal(_(head)(xs), _(head)(ys)),
                                equal_impl(_(tail)(xs), _(tail)(ys)));
                }
            );
        }
    };

    //! @details
    //! A `List` can be searched by doing a linear search in the elements,
    //! with the keys and values being both the elements in the list.
    //!
    //! @todo
    //! Technically, this can be implemented in `Iterable`. Should it?
    //!
    //! ### Example
    //! @snippet example/list/searchable/find.cpp main
    template <typename T>
    struct Searchable::instance<T, when<is_a<List, T>()>>
        : Searchable::mcd
    {
        template <typename Xs, typename Pred>
        static constexpr auto find_impl(Xs xs, Pred pred) {
            auto e = drop_until(pred, xs);
            return eval_if(is_empty(e),
                [](auto) { return nothing; },
                [=](auto _) { return just(_(head)(e)); }
            );
        }

        template <typename Xs, typename Pred>
        static constexpr auto any_impl(Xs xs, Pred pred) {
            return eval_if(is_empty(xs),
                [](auto _) { return false_; },
                [=](auto _) {
                    return eval_if(pred(_(head)(xs)),
                        [=](auto _) { return true_; },
                        [=](auto _) { return any_impl(_(tail)(xs), pred); }
                    );
                }
            );
        }
    };

    //! Converts a `Foldable` to a `List`.
    template <typename L, typename T>
    struct convert<L, T, detail::std::enable_if_t<
        is_a<List, L>() && is_a<Foldable, T>()
    >> {
        template <typename Xs>
        static constexpr auto apply(Xs xs)
        { return foldr(xs, nil<L>, cons); }
    };
}} // end namespace boost::hana

#endif // !BOOST_HANA_LIST_MCD_HPP


/*
 * This file is part of the DUDS project. It is subject to the BSD-style
 * license terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://www.somewhere.org/somepath/license.html.
 * No part of DUDS, including this file, may be copied, modified, propagated,
 * or distributed except according to the terms contained in the LICENSE file.
 *
 * Copyright (C) 2018  Jeff Jackowski
 */
#include <duds/hardware/devices/instruments/AMG88xx.hpp>
#include <duds/hardware/interface/linux/DevI2c.hpp>
#include <iostream>
#include <thread>
#include <iomanip>
#include <assert.h>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/program_options.hpp>

constexpr int valw = 8;
bool quit = false;

template<typename InputIterator, typename OutputIterator, typename RatioType>
void ExponentialMovingAverage(
	OutputIterator result,
	InputIterator prev,
	InputIterator samp,
	RatioType fracNew,
	std::size_t items
) {
	for (; items; --items, ++result, ++prev, ++samp) {
		*result = *samp * fracNew + *prev * ((RatioType)1 - fracNew);
	}
}

const char tind[7] = { ' ', '.', ',', 'x', '!', 'X', '#' };

void runtest(duds::hardware::devices::instruments::AMG88xx &meter, bool tenFps)
try {
	double img[8][8], emavg[8][8];
	unsigned int frame = 0;
	std::cout.precision(2);
	std::cout << std::fixed;
	meter.start();
	std::this_thread::sleep_for(std::chrono::seconds(1));
	meter.sample();
	std::copy_n(&(meter.image()[0][0]), 64, &(emavg[0][0]));
	do {
		if (tenFps) {
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
		} else {
			std::this_thread::sleep_for(std::chrono::seconds(1));
		}
		meter.sample();
		std::copy_n(&(meter.image()[0][0]), 64, &(img[0][0]));
		ExponentialMovingAverage(
			&(emavg[0][0]),
			&(emavg[0][0]),
			&(img[0][0]),
			0.35,
			64
		);
		double *pixel = &(emavg[0][0]);
		double mt = 640.0, Mt = 0;
		for (int i = 64; i > 0; --i, ++pixel) {
			mt = std::min(mt, *pixel);
			Mt = std::max(Mt, *pixel);
		}
		double rng = Mt - mt;
		if ((mt > 273.14) && (mt < 273.16)) {
			meter.start();
		}
		std::cout << "Frame " << frame++ << ", thermistor at " <<
		meter.temperature().value - 273.15 << "C, floor = " << mt - 273.15 <<
		"C, max +" << rng;
		rng = std::max(rng, 5.0);
		for (int x = 0; x < 8; ++x) {
			std::cout << "\n\t";
			for (int y = 0; y < 8; ++y) {
				std::cout << std::setw(6) << emavg[x][y] - 273.15 << ' ';
			}
			std::cout << "   ";
			for (int y = 0; y < 8; ++y) {
				int spot = ((emavg[x][y] - mt) / rng) * 7.0;
				spot = std::min(spot, 6);
				// early attempt at possible human body heat detection
				if ((emavg[x][y] > 306.0) && (emavg[x][y] <= 308.5)) {
					std::cout << 'h' << tind[spot];
				} else if ((emavg[x][y] > 309.0) && (emavg[x][y] <= 311.0)) {
					std::cout << 'H' << tind[spot];
				} else {
					std::cout << tind[spot] << tind[spot];
				}
			}
		}
		std::cout << std::endl;
	} while (!quit);
} catch (...) {
	std::cerr << "Program failed in runtest(): " <<
	boost::current_exception_diagnostic_information() << std::endl;
}

int main(int argc, char *argv[])
try {
	std::string i2cpath;
	int i2caddr;
	bool tenFps = false;
	{ // option parsing
		boost::program_options::options_description optdesc(
			"Options for AMG88xx test"
		);
		optdesc.add_options()
			( // help info
				"help,h",
				"Show this help message"
			)
			( // the I2C device file to use
				"i2cdev,i",
				boost::program_options::value<std::string>(&i2cpath)->
					default_value("/dev/i2c-1"),
				"Specify I2C device file"
			)
			( // the I2C device address to use
				"i2caddr,a",
				boost::program_options::value<int>(&i2caddr)->
					default_value(0x69),
				"Specify Smbus device address"
			)
			( // 10fps
				"ten,t",
				"Run at 10 FPS"
			)
		;
		boost::program_options::variables_map vm;
		boost::program_options::store(
			boost::program_options::parse_command_line(argc, argv, optdesc),
			vm
		);
		boost::program_options::notify(vm);
		if (vm.count("help")) {
			std::cout << "Test program for AMG88xx thermal camera\n" <<
			argv[0] << " [options]\n" << optdesc << std::endl;
			return 0;
		}
		if (vm.count("ten")) {
			tenFps = true;
		}
	}
	std::unique_ptr<duds::hardware::interface::I2c> i2c(
		new duds::hardware::interface::linux::DevI2c(i2cpath, i2caddr)
	);
	duds::hardware::devices::instruments::AMG88xx meter(i2c);
	if (tenFps) {
		meter.tenFps();
	} else {
		meter.oneFps();
	}
	std::this_thread::sleep_for(std::chrono::milliseconds(2));
	std::thread doit(runtest, std::ref(meter), tenFps);
	std::cin.get();
	quit = true;
	doit.join();
} catch (...) {
	std::cerr << "Program failed in main(): " <<
	boost::current_exception_diagnostic_information() << std::endl;
	return 1;
}


//////////////////////////////////////////////////////////////////////////////
///   Copyright 2003 and onward LASMEA UMR 6602 CNRS/U.B.P Clermont-Ferrand
///   Copyright 2009 and onward LRI    UMR 8623 CNRS/Univ Paris Sud XI
///
///          Distributed under the Boost Software License, Version 1.0
///                 See accompanying file LICENSE.txt or copy at
///                     http://www.boost.org/LICENSE_1_0.txt
//////////////////////////////////////////////////////////////////////////////
#ifndef NT2_TOOLBOX_BOOST_MATH_FUNCTION_IBETA_INVB_HPP_INCLUDED
#define NT2_TOOLBOX_BOOST_MATH_FUNCTION_IBETA_INVB_HPP_INCLUDED
#include <nt2/include/simd.hpp>
#include <nt2/include/functor.hpp>
#include <boost/math/special_functions/beta.hpp>

namespace nt2 { namespace boost_math { namespace tag
  {
    struct ibeta_invb_ {};
  }
  NT2_FUNCTION_IMPLEMENTATION(boost_math::tag::ibeta_invb_, ibeta_invb, 3)
  } }

#include <nt2/toolbox/boost_math/function/scalar/ibeta_invb.hpp>
// #include NT2_BOOST_MATH_INCLUDE(ibeta_invb.hpp)

#endif

// modified by jt the 29/12/2010


// Copyright (c) 2015-2018 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#ifndef coalescent_model_hpp
#define coalescent_model_hpp

#include <vector>
#include <unordered_map>
#include <functional>
#include <utility>
#include <functional>
#include <iterator>
#include <algorithm>
#include <cstddef>
#include <tuple>
#include <cassert>

#include <boost/functional/hash.hpp>
#include <boost/optional.hpp>

#include "core/types/haplotype.hpp"
#include "core/types/variant.hpp"
#include "indel_mutation_model.hpp"

namespace octopus {

class CoalescentModel
{
public:
    using LogProbability = double;

    struct Parameters
    {
        double snp_heterozygosity   = 0.001;
        double indel_heterozygosity = 0.0001;
    };

    enum class CachingStrategy { none, value, address };

    CoalescentModel() = delete;

    CoalescentModel(Haplotype reference,
                    Parameters parameters,
                    std::size_t num_haplotyes_hint = 1024,
                    CachingStrategy caching = CachingStrategy::value);

    CoalescentModel(const CoalescentModel&)            = default;
    CoalescentModel& operator=(const CoalescentModel&) = default;
    CoalescentModel(CoalescentModel&&)                 = default;
    CoalescentModel& operator=(CoalescentModel&&)      = default;

    ~CoalescentModel() = default;

    void set_reference(Haplotype reference);

    void prime(std::vector<Haplotype> haplotypes);
    void unprime() noexcept;
    bool is_primed() const noexcept;

    // ln p(haplotype(s))
    LogProbability evaluate(const Haplotype& haplotype) const;
    template <typename Container> double evaluate(const Container& haplotypes) const;
    LogProbability evaluate(const std::vector<unsigned>& haplotype_indices) const;

private:
    using VariantReference = std::reference_wrapper<const Variant>;
    using SiteCountTuple = std::tuple<unsigned, unsigned, unsigned>;
    using SiteCountIndelTuple = std::tuple<unsigned, unsigned, unsigned, double>;

    struct SiteCountTupleHash
    {
        std::size_t operator()(const SiteCountIndelTuple& t) const noexcept
        {
            return boost::hash_value(t);
        }
    };

    Haplotype reference_;
    IndelMutationModel::ContextIndelModel indel_heterozygosity_model_;
    Parameters params_;
    std::vector<Haplotype> haplotypes_;
    CachingStrategy caching_;

    mutable std::vector<VariantReference> site_buffer1_, site_buffer2_;
    mutable std::unordered_map<Haplotype, std::vector<Variant>> difference_value_cache_;
    mutable std::unordered_map<const Haplotype*, std::vector<Variant>> difference_address_cache_;
    mutable std::vector<boost::optional<std::vector<Variant>>> index_cache_;
    mutable std::vector<bool> index_flag_buffer_;
    mutable std::vector<std::vector<boost::optional<LogProbability>>> k_indel_zero_result_cache_;
    mutable std::unordered_map<SiteCountIndelTuple, LogProbability, SiteCountTupleHash> k_indel_pos_result_cache_;

    LogProbability evaluate(const SiteCountTuple& t) const;
    LogProbability evaluate(unsigned k_snp, unsigned n) const;
    LogProbability evaluate(unsigned k_snp, unsigned k_indel, unsigned n) const;

    void fill_site_buffer(const Haplotype& haplotype) const;
    template <typename Container> void fill_site_buffer(const Container& haplotypes) const;
    void fill_site_buffer(const std::vector<unsigned>& haplotype_indices) const;
    void fill_site_buffer_uncached(const Haplotype& haplotype) const;
    void fill_site_buffer_from_value_cache(const Haplotype& haplotype) const;
    void fill_site_buffer_from_address_cache(const Haplotype& haplotype) const;

    SiteCountTuple count_segregating_sites(const Haplotype& haplotype) const;
    template <typename Container> SiteCountTuple count_segregating_sites(const Container& haplotypes) const;
    SiteCountTuple count_segregating_sites_in_buffer(unsigned num_haplotypes) const;
    double calculate_buffered_indel_heterozygosity() const;
    double calculate_heterozygosity(const Variant& indel) const;
};

template <typename Container>
CoalescentModel::LogProbability CoalescentModel::evaluate(const Container& haplotypes) const
{
    return evaluate(count_segregating_sites(haplotypes));
}

// private methods

template <typename Container>
void CoalescentModel::fill_site_buffer(const Container& haplotypes) const
{
    assert(site_buffer2_.empty());
    site_buffer1_.clear();
    for (const Haplotype& haplotype : haplotypes) {
        switch (caching_) {
            case CachingStrategy::address: fill_site_buffer_from_address_cache(haplotype); break;
            case CachingStrategy::value: fill_site_buffer_from_value_cache(haplotype); break;
            default: fill_site_buffer_uncached(haplotype);
        }
        site_buffer1_ = std::move(site_buffer2_);
        site_buffer2_.clear();
    }
}

namespace detail {

template <typename Container>
auto size(const Container& haplotypes) noexcept
{
    // Use this because Genotype template does not have a size member method (uses ploidy instead).
    return std::distance(std::cbegin(haplotypes), std::cend(haplotypes));
}

} // namespace detail

template <typename Container>
CoalescentModel::SiteCountTuple
CoalescentModel::count_segregating_sites(const Container& haplotypes) const
{
    fill_site_buffer(haplotypes);
    return count_segregating_sites_in_buffer(detail::size(haplotypes));
}

struct CoalescentProbabilityGreater
{
    CoalescentProbabilityGreater(CoalescentModel model);

    bool operator()(const Haplotype& lhs, const Haplotype& rhs) const;

private:
    CoalescentModel model_;
    mutable std::vector<Haplotype> buffer_;
    mutable std::unordered_map<Haplotype, CoalescentModel::LogProbability, std::hash<Haplotype>, HaveSameAlleles> cache_;
};

} // namespace octopus

#endif


#include <cstring>
#include <fstream>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <cstring>

#include <NTL/ZZX.h>
#include <NTL/vector.h>

#include "FHE.h"
#include "timing.h"
#include "EncryptedArray.h"

// https://www.linuxquestions.org/questions/programming-9/c-list-files-in-directory-379323/
// get Dir file names
int getdir(string dir, vector <string> &files) {
    DIR *dp;
    struct dirent *dirp;
    if ((dp = opendir(dir.c_str())) == NULL) {
        cout << "Error(" << errno << ") opening " << dir << endl;
        return errno;
    }

    int checkIndex = 0;
    while ((dirp = readdir(dp)) != NULL) {
        if(checkIndex++ < 2) continue;
        string fileName = string(dirp->d_name);
        files.push_back(string(dirp->d_name));
    }
    closedir(dp);
    return 0;
}

int main(int argc, char *argv[]) {
    ArgMapping amap;

    long numberOfCandidates = 4;
    string owner = "owner";
    string directoryPath = "data";

    amap.arg("n", numberOfCandidates, "number of candidates");
    amap.arg("o", owner, "owner's address");
    amap.arg("dir", directoryPath, "data directory Path");
    amap.parse(argc, argv);

    // setting directory path with Ctxt files
    string directoryPathWithCtxt = directoryPath + "/result/" + owner;

    // files name vector list
    vector<string> fileNames = vector<string>();

    // set resultFile
    const string resultFilePath = directoryPath + "/result/" + owner + ".txt";
    ofstream resultFile(resultFilePath.c_str(), ios::binary);
    assert(resultFile.is_open());

    // get Ctxt files
    getdir(directoryPathWithCtxt, fileNames);
    const long sizeOfCtxtFile = fileNames.size();
    if(sizeOfCtxtFile == 0) {
        string res = "[";
        for (int i = 0; i < numberOfCandidates; i++) {
            res.append(to_string(i));
            if(i != numberOfCandidates-1) res.append(",");
        }
        res.append("]");

        resultFile << res;
        return 0;
    }

    // get public key
    const string secretKeyBinaryFilePath = directoryPath + "/secretKey/" + owner + ".bin";
    ifstream secretBinFile(secretKeyBinaryFilePath.c_str(), ios::binary);
    assert(secretBinFile.is_open());

    // make stream of Ctxt Files
    fstream CtxtFiles[sizeOfCtxtFile];
    for (long i = 0;i < sizeOfCtxtFile;i++) {
        string fileName = directoryPathWithCtxt + "/" + fileNames[i];
        CtxtFiles[i] = fstream(fileName.c_str(), fstream::in);
        assert(CtxtFiles[i].is_open());
    }

    // Read context in secret bin file
    std::unique_ptr <FHEcontext> context = buildContextFromBinary(secretBinFile);
    readContextBinary(secretBinFile, *context);

    // Read in SecKey and PubKey.
    // Got to insert pubKey into seckey obj first.
    std::unique_ptr <FHESecKey> secKey(new FHESecKey(*context));
    FHEPubKey *pubKey = (FHEPubKey *) secKey.get();

    // read publicKey
    readPubKeyBinary(secretBinFile, *pubKey);
    readSecKeyBinary(secretBinFile, *secKey);

    // close secreyKey file
    secretBinFile.close();

    cout << "read PublicKey & SecretKey successful.\n" << flush;

    // ready to add two Ctxts
    // get Ctxt to file
    Ctxt resultCtxt(*pubKey), secondCtxt(*pubKey), tempCtxt(*pubKey);

    // fill 1 to tempCtxt
    Vec <ZZ> tempPoly;
    tempPoly.SetLength(numberOfCandidates);

    // set poly
    for (long i = 0; i < numberOfCandidates; i++) tempPoly[i] = 1;

    // encrypt poly
    pubKey->Encrypt(tempCtxt, to_ZZX(tempPoly));

    // add files
    ZZX tempCtxtZZX;
    ostringstream cTxtStream;

    CtxtFiles[0] >> resultCtxt;
    cTxtStream << resultCtxt;
    secKey->Decrypt(tempCtxtZZX, resultCtxt);

    cout << "\n" << fileNames[0] << endl;
    cout << "CypherText: " << cTxtStream.str().substr(0, 20)
        << " ... " << cTxtStream.str().substr(cTxtStream.str().size() - 63, 50) << endl;
    cout << "PlainText: " << tempCtxtZZX << endl;
    cout << "\n+\n" << endl;

    for (long i = 1; i < sizeOfCtxtFile; i++) {
        CtxtFiles[i] >> secondCtxt;
        resultCtxt += secondCtxt;
        cTxtStream << secondCtxt;
        secKey->Decrypt(tempCtxtZZX, secondCtxt);

        cout << fileNames[i] << endl;
        cout << "CypherText: " << cTxtStream.str().substr(0, 20)
             << " ..." << cTxtStream.str().substr(cTxtStream.str().size() - 63, 50) << endl;
        cout << "PlainText:" << tempCtxtZZX << endl;
        if(i != sizeOfCtxtFile-1) cout << "\n+\n" << endl;
    }
    cout << "\n=\n" << endl;

    // save result
    ZZX ptSum;
    cTxtStream << resultCtxt;
    secKey->Decrypt(ptSum, resultCtxt);

    cout << " [ tally result ]" << endl;
    cout << "CypherText: " << cTxtStream.str().substr(0, 20)
         << " ..." << cTxtStream.str().substr(cTxtStream.str().size() - 63, 50) << endl;
    cout << "PlainText: " << ptSum << endl;

    // last elements +1
    resultCtxt += tempCtxt;
    secKey->Decrypt(ptSum, resultCtxt);

    // save result to file
    resultFile << ptSum;
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020-2021 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#define BOOST_TEST_MODULE merkle_tree_components_test

#include <boost/test/unit_test.hpp>

#include <nil/crypto3/algebra/curves/bls12.hpp>
#include <nil/crypto3/algebra/curves/mnt4.hpp>
#include <nil/crypto3/algebra/curves/mnt6.hpp>

#include <nil/crypto3/zk/components/hashes/sha256/sha256_component.hpp>
#include <nil/crypto3/zk/components/merkle_tree/merkle_tree_check_read_component.hpp>
#include <nil/crypto3/zk/components/merkle_tree/merkle_tree_check_update_components.hpp>

using namespace nil::crypto3;
using namespace nil::crypto3::zk;
using namespace nil::crypto3::algebra;

template<typename FieldType, typename Hash>
void test_merkle_tree_check_update_component() {
    /* prepare test */
    const std::size_t digest_len = Hash::get_digest_len();

    const std::size_t tree_depth = 16;
    std::vector<merkle_authentication_node> prev_path(tree_depth);

    std::vector<bool> prev_load_hash(digest_len);
    std::generate(prev_load_hash.begin(), prev_load_hash.end(), [&]() { return std::rand() % 2; });
    std::vector<bool> prev_store_hash(digest_len);
    std::generate(prev_store_hash.begin(), prev_store_hash.end(), [&]() { return std::rand() % 2; });

    std::vector<bool> loaded_leaf = prev_load_hash;
    std::vector<bool> stored_leaf = prev_store_hash;

    std::vector<bool> address_bits;

    std::size_t address = 0;
    for (long level = tree_depth - 1; level >= 0; --level) {
        const bool computed_is_right = (std::rand() % 2);
        address |= (computed_is_right ? 1ul << (tree_depth - 1 - level) : 0);
        address_bits.push_back(computed_is_right);
        std::vector<bool> other(digest_len);
        std::generate(other.begin(), other.end(), [&]() { return std::rand() % 2; });

        std::vector<bool> load_block = prev_load_hash;
        load_block.insert(computed_is_right ? load_block.begin() : load_block.end(), other.begin(), other.end());
        std::vector<bool> store_block = prev_store_hash;
        store_block.insert(computed_is_right ? store_block.begin() : store_block.end(), other.begin(), other.end());

        std::vector<bool> load_h = Hash::get_hash(load_block);
        std::vector<bool> store_h = Hash::get_hash(store_block);

        prev_path[level] = other;

        prev_load_hash = load_h;
        prev_store_hash = store_h;
    }

    std::vector<bool> load_root = prev_load_hash;
    std::vector<bool> store_root = prev_store_hash;

    /* execute the test */
    components::blueprint<FieldType> bp;
    components::blueprint_variable_vector<FieldType> address_bits_va;
    address_bits_va.allocate(bp, tree_depth);
    digest_variable<FieldType> prev_leaf_digest(bp, digest_len);
    digest_variable<FieldType> prev_root_digest(bp, digest_len);
    merkle_authentication_path_variable<FieldType, Hash> prev_path_var(bp, tree_depth);
    digest_variable<FieldType> next_leaf_digest(bp, digest_len);
    digest_variable<FieldType> next_root_digest(bp, digest_len);
    merkle_authentication_path_variable<FieldType, Hash> next_path_var(bp, tree_depth);
    merkle_tree_check_update_components<FieldType, Hash> mls(bp, tree_depth, address_bits_va, prev_leaf_digest,
                                                             prev_root_digest, prev_path_var, next_leaf_digest,
                                                             next_root_digest, next_path_var, components::blueprint_variable<FieldType>(0));

    prev_path_var.generate_r1cs_constraints();
    mls.generate_r1cs_constraints();

    address_bits_va.fill_with_bits(bp, address_bits);
    assert(address_bits_va.get_field_element_from_bits(bp).as_ulong() == address);
    prev_leaf_digest.generate_r1cs_witness(loaded_leaf);
    prev_path_var.generate_r1cs_witness(address, prev_path);
    next_leaf_digest.generate_r1cs_witness(stored_leaf);
    address_bits_va.fill_with_bits(bp, address_bits);
    mls.generate_r1cs_witness();

    /* make sure that update check will check for the right things */
    prev_leaf_digest.generate_r1cs_witness(loaded_leaf);
    next_leaf_digest.generate_r1cs_witness(stored_leaf);
    prev_root_digest.generate_r1cs_witness(load_root);
    next_root_digest.generate_r1cs_witness(store_root);
    address_bits_va.fill_with_bits(bp, address_bits);
    assert(bp.is_satisfied());

    const std::size_t num_constraints = bp.num_constraints();
    const std::size_t expected_constraints =
        merkle_tree_check_update_components<FieldType, Hash>::expected_constraints(tree_depth);
    assert(num_constraints == expected_constraints);
}

template<typename FieldType, typename Hash>
void test_merkle_tree_check_read_component() {
    /* prepare test */
    const std::size_t digest_len = Hash::get_digest_len();
    const std::size_t tree_depth = 16;
    std::vector<merkle_authentication_node> path(tree_depth);

    std::vector<bool> prev_hash(digest_len);
    std::generate(prev_hash.begin(), prev_hash.end(), [&]() { return std::rand() % 2; });
    std::vector<bool> leaf = prev_hash;

    std::vector<bool> address_bits;

    std::size_t address = 0;
    for (long level = tree_depth - 1; level >= 0; --level) {
        const bool computed_is_right = (std::rand() % 2);
        address |= (computed_is_right ? 1ul << (tree_depth - 1 - level) : 0);
        address_bits.push_back(computed_is_right);
        std::vector<bool> other(digest_len);
        std::generate(other.begin(), other.end(), [&]() { return std::rand() % 2; });

        std::vector<bool> block = prev_hash;
        block.insert(computed_is_right ? block.begin() : block.end(), other.begin(), other.end());
        std::vector<bool> h = Hash::get_hash(block);

        path[level] = other;

        prev_hash = h;
    }
    std::vector<bool> root = prev_hash;

    /* execute test */
    components::blueprint<FieldType> bp;
    components::blueprint_variable_vector<FieldType> address_bits_va;
    address_bits_va.allocate(bp, tree_depth);
    digest_variable<FieldType> leaf_digest(bp, digest_len);
    digest_variable<FieldType> root_digest(bp, digest_len);
    merkle_authentication_path_variable<FieldType, Hash> path_var(bp, tree_depth);
    merkle_tree_check_read_component<FieldType, Hash> ml(bp, tree_depth, address_bits_va, leaf_digest, root_digest,
                                                         path_var, components::blueprint_variable<FieldType>(0));

    path_var.generate_r1cs_constraints();
    ml.generate_r1cs_constraints();

    address_bits_va.fill_with_bits(bp, address_bits);
    assert(address_bits_va.get_field_element_from_bits(bp).as_ulong() == address);
    leaf_digest.generate_r1cs_witness(leaf);
    path_var.generate_r1cs_witness(address, path);
    ml.generate_r1cs_witness();

    /* make sure that read checker didn't accidentally overwrite anything */
    address_bits_va.fill_with_bits(bp, address_bits);
    leaf_digest.generate_r1cs_witness(leaf);
    root_digest.generate_r1cs_witness(root);
    assert(bp.is_satisfied());

    const std::size_t num_constraints = bp.num_constraints();
    const std::size_t expected_constraints =
        merkle_tree_check_read_component<FieldType, Hash>::expected_constraints(tree_depth);
    assert(num_constraints == expected_constraints);
}

template<typename CurveType>
void test_all_merkle_tree_components() {
    typedef typename CurveType::scalar_field_type scalar_field_type;

    // for now all CRH components are knapsack CRH's; can be easily extended
    // later to more expressive selector types.
    using crh_with_field_out_component = knapsack_crh_with_field_out_component<scalar_field_type>;
    using crh_with_bit_out_component = knapsack_crh_with_bit_out_component<scalar_field_type>;

    test_merkle_tree_check_read_component<scalar_field_type, crh_with_bit_out_component>();
    test_merkle_tree_check_read_component<scalar_field_type, sha256_two_to_one_hash_component<scalar_field_type>>();

    test_merkle_tree_check_update_component<scalar_field_type, crh_with_bit_out_component>();
    test_merkle_tree_check_update_component<scalar_field_type, sha256_two_to_one_hash_component<scalar_field_type>>();
}

BOOST_AUTO_TEST_SUITE(merkle_tree_components_test_suite)

BOOST_AUTO_TEST_CASE(merkle_tree_components_bls12_381_test) {

    test_all_merkle_tree_components<curves::bls12<381>>();

}

BOOST_AUTO_TEST_CASE(merkle_tree_components_mnt4_test) {

    test_all_merkle_tree_components<curves::mnt4<398>>();

}

BOOST_AUTO_TEST_CASE(merkle_tree_components_mnt6_test) {

    test_all_merkle_tree_components<curves::mnt6<298>>();

}

BOOST_AUTO_TEST_SUITE_END()