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

// Diverse arithmetic operations in the bls curve
// inspired by https://github.com/b-g-goodell/research-lab/blob/master/source-code/StringCT-java/src/how/monero/hodl/bulletproof/Bulletproof.java
// and https://github.com/monero-project/monero/blob/master/src/ringct/bulletproofs.cc

#include <boost/algorithm/string.hpp>

#include <blsct/bulletproofs.h>
#include <tinyformat.h>

bool BLSInitResult = bls::BLS::Init();

static std::vector<Scalar> VectorPowers(const Scalar &x, size_t n);
static std::vector<Scalar> VectorDup(const Scalar &x, size_t n);
static Scalar InnerProduct(const std::vector<Scalar> &a, const std::vector<Scalar> &b);

Scalar BulletproofsRangeproof::one;
Scalar BulletproofsRangeproof::two;

std::vector<bls::G1Element> BulletproofsRangeproof::Hi, BulletproofsRangeproof::Gi;
std::vector<Scalar> BulletproofsRangeproof::oneN;
std::vector<Scalar> BulletproofsRangeproof::twoN;
Scalar BulletproofsRangeproof::ip12;

boost::mutex BulletproofsRangeproof::init_mutex;

bls::G1Element BulletproofsRangeproof::G;
bls::G1Element BulletproofsRangeproof::H;

// Calculate base point
static bls::G1Element GetBaseG1Element(const bls::G1Element &base, size_t idx)
{
    static const std::string salt("bulletproof");
    std::vector<uint8_t> data =  base.Serialize();
    std::string toHash = HexStr(data) + salt + std::to_string(idx);

    CHashWriter ss(SER_GETHASH, 0);
    ss << toHash;
    uint256 hash = ss.GetHash();

    uint8_t dest[1];
    dest[0] = 0x0;

    bls::G1Element e = bls::G1Element::FromMessage(std::vector<unsigned char>(hash.begin(), hash.end()), dest, 1);

    CHECK_AND_ASSERT_THROW_MES(e != bls::G1Element::Infinity(), "Exponent is point at infinity");

    return e;
}

// Initialize bases and constants
bool BulletproofsRangeproof::Init()
{
    boost::lock_guard<boost::mutex> lock(BulletproofsRangeproof::init_mutex);

    static bool fInit = false;

    if (fInit)
        return true;

    BulletproofsRangeproof::one = 1;
    BulletproofsRangeproof::two = 2;

    BulletproofsRangeproof::G = bls::G1Element::Generator();
    BulletproofsRangeproof::H = GetBaseG1Element(BulletproofsRangeproof::G, 0);

    BulletproofsRangeproof::Hi.resize(maxMN);
    BulletproofsRangeproof::Gi.resize(maxMN);

    for (size_t i = 0; i < maxMN; ++i)
    {
        BulletproofsRangeproof::Hi[i] = GetBaseG1Element(BulletproofsRangeproof::H, i * 2 + 1);
        BulletproofsRangeproof::Gi[i] = GetBaseG1Element(BulletproofsRangeproof::H, i * 2 + 2);
    }

    BulletproofsRangeproof::oneN = VectorDup(BulletproofsRangeproof::one, maxN);
    BulletproofsRangeproof::twoN = VectorPowers(BulletproofsRangeproof::two, maxN);
    BulletproofsRangeproof::ip12 = InnerProduct(BulletproofsRangeproof::oneN, BulletproofsRangeproof::twoN);

    fInit = true;

    return true;
}

// Todo multi-exp optimization
bls::G1Element MultiExp(std::vector<MultiexpData> multiexp_data)
{
    bls::G1Element result;

    for (size_t i = 0; i < multiexp_data.size(); i++)
    {
        if (i == 0)
            result = multiexp_data[i].base * multiexp_data[i].exp.bn;
        else
        {
            bls::G1Element temp = bls::G1Element(multiexp_data[i].base * multiexp_data[i].exp.bn);
            result = result + temp;
        }
    }

    return result;
}

/* Given two Scalar arrays, construct a vector commitment */
static bls::G1Element VectorCommitment(const std::vector<Scalar> &a, const std::vector<Scalar> &b)
{
    CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
    CHECK_AND_ASSERT_THROW_MES(a.size() <= maxMN, "Incompatible sizes of a and maxN");

    std::vector<MultiexpData> multiexp_data;
    for (size_t i = 0; i < a.size(); ++i)
    {
        multiexp_data.push_back({BulletproofsRangeproof::Gi[i], a[i]});
        multiexp_data.push_back({BulletproofsRangeproof::Hi[i], b[i]});
    }
    return MultiExp(multiexp_data);
}

/* Given a Scalar x, construct a vector of powers [x^0, x^1, ..., x^n] */
static std::vector<Scalar> VectorPowers(const Scalar &x, size_t n)
{
    std::vector<Scalar> res(n);

    if (n == 0)
        return res;

    res[0] = 1;

    if (n == 1)
        return res;

    res[1] = x;

    for (size_t i = 2; i < n; ++i)
    {
        res[i] = res[i-1] * x;
    }

    return res;
}

static Scalar VectorPowerSum(const Scalar &x, size_t n)
{
    std::vector<Scalar> res = VectorPowers(x, n);
    Scalar ret;
    ret = 0;

    for (auto& it: res)
    {
        ret = ret + it;
    }

    return ret;
}

/* Create a vector from copies of a single value */
static std::vector<Scalar> VectorDup(const Scalar &x, size_t n)
{
    std::vector<Scalar> ret(n);

    for (auto i=0;i<n;i++)
    {
        ret[i] = x;
    }

    return ret;
}

/* Given two Scalar arrays, construct the inner product */
static Scalar InnerProduct(const std::vector<Scalar> &a, const std::vector<Scalar> &b)
{
    CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");

    Scalar res;

    for (size_t i = 0; i < a.size(); ++i)
    {
        if (i == 0)
            res = (a[i] * b[i]);
        else
            res = res + (a[i] * b[i]);
    }
    return res;
}

static std::vector<Scalar> VectorProduct(const std::vector<Scalar> &a, const std::vector<Scalar> &b)
{
    CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");

    std::vector<Scalar> res(a.size());

    for (size_t i = 0; i < a.size(); ++i)
    {
        res[i] = (a[i] * b[i]);
    }
    return res;
}

/* Subtract a number from all the elements of a vectors */
static std::vector<Scalar> VectorSubtract(const std::vector<Scalar>& a, const Scalar& b)
{

    std::vector<Scalar> ret(a.size());

    for (unsigned int i = 0; i < a.size(); i++)
    {
        ret[i] = (a[i] - b);
    }

    return ret;
}

/* Given two scalar arrays, construct the Hadamard product */
std::vector<Scalar> Hadamard(const std::vector<Scalar>& a, const std::vector<Scalar>& b)
{
    if (a.size() != b.size())
        throw std::runtime_error("Hadamard(): a and b should be of the same size");

    std::vector<Scalar> ret(a.size());

    for (unsigned int i = 0; i < a.size(); i++)
    {
        ret[i] = (a[i] * b[i]);
    }

    return ret;
}

/* Add a number to all the elements of a vectors */
static std::vector<Scalar> VectorAdd(const std::vector<Scalar>& a, const Scalar& b)
{

    std::vector<Scalar> ret(a.size());

    for (unsigned int i = 0; i < a.size(); i++)
    {
        ret[i] = (a[i] + b);
    }

    return ret;
}

static std::vector<Scalar> VectorAdd(const std::vector<Scalar>& a, const std::vector<Scalar>& b)
{
    CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");

    std::vector<Scalar> ret(a.size());

    for (unsigned int i = 0; i < a.size(); i++)
    {
        ret[i] = (a[i] + b[i]);
    }

    return ret;
}

/* Multiply all the elements of a vector by a number */
static std::vector<Scalar> VectorScalar(const std::vector<Scalar>& a, const Scalar& x)
{
    std::vector<Scalar> ret(a.size());

    for (unsigned int i = 0; i < a.size(); i++)
    {
        ret[i] = (a[i] * x);
    }

    return ret;
}

/* Compute the slice of a scalar vector */
static std::vector<Scalar> VectorSlice(const std::vector<Scalar>& a, unsigned int start, unsigned int stop)
{
    if (start > a.size() || stop > a.size())
        throw std::runtime_error("VectorSlice(): wrong start or stop point");

    std::vector<Scalar> ret(stop-start);

    for (unsigned int i = start; i < stop; i++)
    {
        ret[i-start] = a[i];
    }

    return ret;
}

static std::vector<bls::G1Element> HadamardFold(const std::vector<bls::G1Element> &vec, const std::vector<Scalar> *scale, const Scalar &a, const Scalar &b)
{
    if(!((vec.size() & 1) == 0))
        throw std::runtime_error("HadamardFold(): vector argument size is not even");

    const size_t sz = vec.size() / 2;
    std::vector<bls::G1Element> out(sz);

    for (size_t n = 0; n < sz; ++n)
    {
        bls::G1Element c0 = vec[n];
        bls::G1Element c1 = vec[sz + n];
        Scalar sa, sb;
        if (scale) sa = a*(*scale)[n]; else sa = a;
        if (scale) sb = b*(*scale)[sz + n]; else sb = b;
        bls::G1Element l = c0*sa.bn;
        bls::G1Element r = c1*sb.bn;
        out[n] = l + r;
    }

    return out;
}

std::vector<Scalar> VectorInvert(const std::vector<Scalar>& x)
{
    std::vector<Scalar> ret(x.size());

    for (size_t i = 0; i < ret.size(); i++)
    {
        ret[i] = x[i].Invert();
    }

    return ret;
}

bls::G1Element CrossVectorExponent(size_t size, const std::vector<bls::G1Element> &A, size_t Ao, const std::vector<bls::G1Element> &B, size_t Bo, const std::vector<Scalar> &a, size_t ao, const std::vector<Scalar> &b, size_t bo, const std::vector<Scalar> *scale, const bls::G1Element *extra_point, const Scalar *extra_scalar)
{
    if (!(size + Ao <= A.size()))
        throw std::runtime_error("CrossVectorExponent(): Incompatible size for A");

    if (!(size + Bo <= B.size()))
        throw std::runtime_error("CrossVectorExponent(): Incompatible size for B");

    if (!(size + ao <= a.size()))
        throw std::runtime_error("CrossVectorExponent(): Incompatible size for a");

    if (!(size + bo <= b.size()))
        throw std::runtime_error("CrossVectorExponent(): Incompatible size for b");

    if (!(size <= maxMN))
        throw std::runtime_error("CrossVectorExponent(): Size is too large");

    if (!(!scale || size == scale->size() / 2))
        throw std::runtime_error("CrossVectorExponent(): Incompatible size for scale");

    if (!(!!extra_point == !!extra_scalar))
        throw std::runtime_error("CrossVectorExponent(): Only one of extra base/exp present");

    std::vector<MultiexpData> multiexp_data;
    multiexp_data.resize(size*2 + (!!extra_point));
    for (size_t i = 0; i < size; ++i)
    {
        multiexp_data[i*2].exp = a[ao+i];
        multiexp_data[i*2].base = A[Ao+i];
        multiexp_data[i*2+1].exp = b[bo+i];

        if (scale)
            multiexp_data[i*2+1].exp =  multiexp_data[i*2+1].exp * (*scale)[Bo+i];

        multiexp_data[i*2+1].base = B[Bo+i];
    }
    if (extra_point)
    {
        multiexp_data.back().exp = *extra_scalar;
        multiexp_data.back().base = *extra_point;
    }

    return MultiExp(multiexp_data);
}

void BulletproofsRangeproof::Prove(std::vector<Scalar> v, bls::G1Element nonce, const std::vector<uint8_t>& message)
{
    if (pow(2, BulletproofsRangeproof::logN) > maxN)
        throw std::runtime_error("BulletproofsRangeproof::Prove(): logN value is too high");

    if (message.size() > maxMessageSize)
        throw std::runtime_error("BulletproofsRangeproof::Prove(): message size is too big");

    if (v.empty() || v.size() > maxM)
        throw std::runtime_error("BulletproofsRangeproof::Prove(): Invalid vector size");

    CHECK_AND_ASSERT_THROW_MES(!v.empty(), "sv is empty");

    Init();

    const size_t N = 1<<BulletproofsRangeproof::logN;

    size_t M, logM;
    for (logM = 0; (M = 1<<logM) <= maxM && M < v.size(); ++logM);
    CHECK_AND_ASSERT_THROW_MES(M <= maxM, "sv is too large");
    const size_t logMN = logM + BulletproofsRangeproof::logN;
    const size_t MN = M * N;

    // V is a vector with commitments in the form g2^v g^gamma
    this->V.resize(v.size());

    std::vector<Scalar> gamma;
    gamma.resize(v.size());

    for (auto i = 0; i < gamma.size(); i++)
    {
        gamma[i] =  HashG1Element(nonce, 100+i);
    }

    // This hash is updated for Fiat-Shamir throughout the proof
    CHashWriter hasher(0,0);

    for (unsigned int j = 0; j < v.size(); j++)
    {
        bls::G1Element gammaElement = G*gamma[j].bn;
        bls::G1Element valueElement = H*v[j].bn;
        this->V[j] = gammaElement + valueElement;
        hasher << this->V[j];
    }

    // PAPER LINES 41-42
    // Value to be obfuscated is encoded in binary in aL
    // aR is aL-1
    std::vector<Scalar> aL(MN), aR(MN);

    for (size_t j = 0; j < M; ++j)
    {
        for (size_t i = 0; i < N; ++i)
        {
            if (j < v.size() && v[j].GetBit(i) == 1)
            {
                aL[j*N+i] = 1;
                aR[j*N+i] = 0;
            }
            else
            {
                aL[j*N+i] = 0;
                aR[j*N+i] = 1;
                aR[j*N+i] = aR[j*N+i].Negate();
            }
        }
    }

try_again:
    // PAPER LINES 43-44
    // Commitment to aL and aR (obfuscated with alpha)
    Scalar alpha;
    std::vector<unsigned char> firstMessage;
    firstMessage = message.size() > 23 ? std::vector<unsigned char>(message.begin(), message.begin()+23) : message;
    Scalar sM = firstMessage;
    sM = sM << 8*8;

    alpha = HashG1Element(nonce, 1);
    alpha = alpha + (v[0] | sM);

    this->A = VectorCommitment(aL, aR);
    {
    bls::G1Element alphaElement = G*alpha.bn;
    this->A = this->A + alphaElement;
    }

    // PAPER LINES 45-47
    // Commitment to blinding sL and sR (obfuscated with rho)
    std::vector<Scalar> sL(MN);
    std::vector<Scalar> sR(MN);

    for (unsigned int i = 0; i < MN; i++)
    {
        sL[i] = Scalar::Rand();
        sR[i] = Scalar::Rand();
    }

    Scalar rho;
    rho = HashG1Element(nonce, 2);

    this->S = VectorCommitment(sL, sR);
    {
    bls::G1Element rhoElement = G*rho.bn;
    this->S = this->S + rhoElement;
    }

    // PAPER LINES 48-50
    hasher << this->A;
    hasher << this->S;

    Scalar y;
    y = hasher.GetHash();

    if (y == 0)
        goto try_again;

    hasher << y;

    Scalar z;
    z = hasher.GetHash();

    if (z == 0)
        goto try_again;

    // Polynomial construction by coefficients
    // PAPER LINE AFTER 50
    std::vector<Scalar> l0;
    std::vector<Scalar> l1;
    std::vector<Scalar> r0;
    std::vector<Scalar> r1;

    // l(x) = (aL - z 1^n) + sL X
    l0 = VectorSubtract(aL, z);

    // l(1) is (aL - z 1^n) + sL, but this is reduced to sL
    l1 = sL;

    // This computes the ugly sum/concatenation from page 19
    // Calculation of r(0) and r(1)
    std::vector<Scalar> zerosTwos(MN);
    std::vector<Scalar> zpow(M+2);

    zpow = VectorPowers(z, M+2);

    for (unsigned int j = 0; j < M; ++j)
    {
        for (unsigned int i = 0; i < N; ++i)
        {
            CHECK_AND_ASSERT_THROW_MES(j+2 < zpow.size(), "invalid zpow index");
            CHECK_AND_ASSERT_THROW_MES(i < twoN.size(), "invalid twoN index");
            zerosTwos[j*N+i] = zpow[j+2] * BulletproofsRangeproof::twoN[i];
        }
    }

    std::vector<Scalar> yMN = VectorPowers(y, MN);
    r0 = VectorAdd(Hadamard(VectorAdd(aR, z), yMN), zerosTwos);
    r1 = Hadamard(yMN, sR);

    // Polynomial construction before PAPER LINE 51
    Scalar t1 = InnerProduct(l0, r1) + InnerProduct(l1, r0);
    Scalar t2 = InnerProduct(l1, r1);

    // PAPER LINES 52-53
    Scalar tau1 = HashG1Element(nonce, 3);
    Scalar tau2 = HashG1Element(nonce, 4);
    std::vector<unsigned char> secondMessage = message.size() > 23 ? std::vector<unsigned char>(message.begin()+23, message.end()) : std::vector<unsigned char>();
    Scalar sM2 = secondMessage;
    tau1 = tau1 + sM2;

    {
    bls::G1Element t1Element = H*t1.bn;
    bls::G1Element t2Element = H*t2.bn;
    bls::G1Element tau1Element = G*tau1.bn;
    bls::G1Element tau2Element = G*tau2.bn;

    this->T1 = t1Element + tau1Element;
    this->T2 = t2Element + tau2Element;
    }

    // PAPER LINES 54-56
    hasher << z;
    hasher << this->T1;
    hasher << this->T2;

    Scalar x;
    x = hasher.GetHash();

    if (x == 0)
        goto try_again;

    // PAPER LINES 58-59
    std::vector<Scalar> l = VectorAdd(l0, VectorScalar(l1, x));
    std::vector<Scalar> r = VectorAdd(r0, VectorScalar(r1, x));

    // PAPER LINE 60
    this->t = InnerProduct(l, r);

    // TEST
    Scalar test_t;
    Scalar t0 = InnerProduct(l0, r0);
    test_t = ((t0 + (t1*x)) + (t2*x*x));
    if (!(test_t==this->t))
        throw std::runtime_error("BulletproofsRangeproof::Prove(): L60 Invalid test");

    // PAPER LINES 61-62
    this->taux = tau1 * x;
    this->taux = this->taux + (tau2 * x*x);

    for (unsigned int j = 1; j <= M; j++) // note this starts from 1
        this->taux = (this->taux + (zpow[j+1]*(gamma[j-1])));

    this->mu = ((x * rho) + alpha);

    // PAPER LINE 63
    hasher << x;
    hasher << this->taux;
    hasher << this->mu;
    hasher << this->t;

    Scalar x_ip;
    x_ip = hasher.GetHash();

    if (x_ip == 0)
        goto try_again;

    // These are used in the inner product rounds
    unsigned int nprime = MN;

    std::vector<bls::G1Element> gprime(nprime);
    std::vector<bls::G1Element> hprime(nprime);
    std::vector<Scalar> aprime(nprime);
    std::vector<Scalar> bprime(nprime);

    Scalar yinv = y.Invert();

    std::vector<Scalar> yinvpow(nprime);

    yinvpow[0] = 1;
    yinvpow[1] = yinv;

    for (unsigned int i = 0; i < nprime; i++)
    {
        gprime[i] = BulletproofsRangeproof::Gi[i];
        hprime[i] = BulletproofsRangeproof::Hi[i];

        if(i > 1)
            yinvpow[i] = yinvpow[i-1] * yinv;

        aprime[i] = l[i];
        bprime[i] = r[i];
    }

    this->L.resize(logMN);
    this->R.resize(logMN);

    unsigned int round = 0;

    std::vector<Scalar> w(logMN);

    std::vector<Scalar>* scale = &yinvpow;

    Scalar tmp;

    while (nprime > 1)
    {
        // PAPER LINE 20
        nprime /= 2;

        // PAPER LINES 21-22
        Scalar cL = InnerProduct(VectorSlice(aprime, 0, nprime),
                                 VectorSlice(bprime, nprime, bprime.size()));

        Scalar cR = InnerProduct(VectorSlice(aprime, nprime, aprime.size()),
                                 VectorSlice(bprime, 0, nprime));

        // PAPER LINES 23-24
        tmp = cL * x_ip;
        this->L[round] = CrossVectorExponent(nprime, gprime, nprime, hprime, 0, aprime, 0, bprime, nprime, scale, &H, &tmp);
        tmp = cR * x_ip;
        this->R[round] = CrossVectorExponent(nprime, gprime, 0, hprime, nprime, aprime, nprime, bprime, 0, scale, &H, &tmp);

        // PAPER LINES 25-27
        hasher << this->L[round];
        hasher << this->R[round];

        w[round] = hasher.GetHash();

        if (w[round] == 0)
            goto try_again;

        Scalar winv = w[round].Invert();

        // PAPER LINES 29-31
        if (nprime > 1)
        {
            gprime = HadamardFold(gprime, NULL, winv, w[round]);
            hprime = HadamardFold(hprime, scale, w[round], winv);
        }

        // PAPER LINES 33-34
        aprime = VectorAdd(VectorScalar(VectorSlice(aprime, 0, nprime), w[round]),
                           VectorScalar(VectorSlice(aprime, nprime, aprime.size()), winv));

        bprime = VectorAdd(VectorScalar(VectorSlice(bprime, 0, nprime), winv),
                           VectorScalar(VectorSlice(bprime, nprime, bprime.size()), w[round]));

        scale = NULL;

        round += 1;
    }

    this->a = aprime[0];
    this->b = bprime[0];
}

struct proof_data_t
{
    Scalar x, y, z, x_ip;
    std::vector<Scalar> w;
    std::vector<bls::G1Element> V;
    size_t logM, inv_offset;
};

bool VerifyBulletproof(const std::vector<std::pair<int, BulletproofsRangeproof>>& proofs, std::vector<RangeproofEncodedData>& vData, const std::vector<bls::G1Element>& nonces, const bool &fOnlyRecover)
{
    bool fRecover = false;

    if (nonces.size() == proofs.size())
        fRecover = true;

    if (pow(2, BulletproofsRangeproof::logN) > maxN)
        throw std::runtime_error("BulletproofsRangeproof::Prove(): logN value is too high");

    BulletproofsRangeproof::Init();

    unsigned int N = 1 << BulletproofsRangeproof::logN;

    size_t max_length = 0;
    size_t nV = 0;

    std::vector<proof_data_t> proof_data;

    size_t inv_offset = 0, j = 0;
    std::vector<Scalar> to_invert;

    vData.clear();

    for (auto& p: proofs)
    {
        BulletproofsRangeproof proof = p.second;
        if (!(proof.V.size() >= 1 && proof.L.size() == proof.R.size() &&
              proof.L.size() > 0))
            return false;

        max_length = std::max(max_length, proof.L.size());
        nV += proof.V.size();

        proof_data.resize(proof_data.size() + 1);
        proof_data_t &pd = proof_data.back();
        pd.V = proof.V;

        CHashWriter hasher(0,0);

        hasher << pd.V[0];

        for (unsigned int j = 1; j < pd.V.size(); j++)
            hasher << pd.V[j];

        hasher << proof.A;
        hasher << proof.S;

        pd.y = hasher.GetHash();

        hasher << pd.y;

        pd.z = hasher.GetHash();

        hasher << pd.z;
        hasher << proof.T1;
        hasher << proof.T2;

        pd.x = hasher.GetHash();

        hasher << pd.x;
        hasher << proof.taux;
        hasher << proof.mu;
        hasher << proof.t;

        pd.x_ip = hasher.GetHash();

        size_t M;
        for (pd.logM = 0; (M = 1<<pd.logM) <= maxM && M < pd.V.size(); ++pd.logM);

        const size_t rounds = pd.logM+BulletproofsRangeproof::logN;

        pd.w.resize(rounds);
        for (size_t i = 0; i < rounds; ++i)
        {
            hasher << proof.L[i];
            hasher << proof.R[i];

            pd.w[i] = hasher.GetHash();
        }

        pd.inv_offset = inv_offset;
        for (size_t i = 0; i < rounds; ++i)
        {
            to_invert.push_back(pd.w[i]);
        }
        to_invert.push_back(pd.y);
        inv_offset += rounds + 1;

        if (fRecover)
        {
            Scalar alpha = HashG1Element(nonces[j], 1);
            Scalar rho = HashG1Element(nonces[j], 2);
            Scalar tau1 = HashG1Element(nonces[j], 3);
            Scalar tau2 = HashG1Element(nonces[j], 4);
            Scalar gamma = HashG1Element(nonces[j], 100);
            Scalar excess = (proof.mu - rho*pd.x) - alpha;
            Scalar amount = (excess & Scalar(0xFFFFFFFFFFFFFFFF));

            RangeproofEncodedData data;
            data.index = p.first;
            data.amount = amount.GetInt64();

            std::vector<unsigned char> vMsg = (excess>>8*8).GetVch();
            std::vector<unsigned char> vMsgTrimmed(0);

            bool fFoundNonZero = false;

            for (auto&it: vMsg)
            {
                if (it != '\0')
                    fFoundNonZero = true;
                if (fFoundNonZero)
                    vMsgTrimmed.push_back(it);
            }

            data.gamma = gamma;
            data.valid = true;

            Scalar excessMsg2 = ((proof.taux - (tau2*pd.x*pd.x) - (pd.z*pd.z*gamma)) * pd.x.Invert()) - tau1;

            std::vector<unsigned char> vMsg2 = excessMsg2.GetVch();
            std::vector<unsigned char> vMsg2Trimmed(0);

            fFoundNonZero = false;

            for (auto&it: vMsg2)
            {
                if (it != '\0')
                    fFoundNonZero = true;
                if (fFoundNonZero)
                    vMsg2Trimmed.push_back(it);
            }

            data.message = std::string(vMsgTrimmed.begin(), vMsgTrimmed.end()) + std::string(vMsg2Trimmed.begin(), vMsg2Trimmed.end());

            {
            bls::G1Element gammaElement = BulletproofsRangeproof::G*gamma.bn;
            bls::G1Element valueElement = BulletproofsRangeproof::H*amount.bn;
            bool fIsMine = ((gammaElement + valueElement) == pd.V[0]);

            if (fIsMine)
                vData.push_back(data);
            }

            j++;
        }
    }

    if (fOnlyRecover)
        return true;

    size_t maxMN = 1u << max_length;

    std::vector<Scalar> inverses(to_invert.size());

    inverses = VectorInvert(to_invert);

    Scalar z1 = 0;
    Scalar z3 = 0;

    std::vector<Scalar> z4(maxMN, 0);
    std::vector<Scalar> z5(maxMN, 0);

    Scalar y0 = 0;
    Scalar y1 = 0;

    Scalar tmp;

    int proof_data_index = 0;

    std::vector<MultiexpData> multiexpdata;

    multiexpdata.reserve(nV + (2 * (10/*logM*/ + BulletproofsRangeproof::logN) + 4) * proofs.size() + 2 * maxMN);
    multiexpdata.resize(2 * maxMN);

    for (auto& p: proofs)
    {
        BulletproofsRangeproof proof = p.second;

        const proof_data_t &pd = proof_data[proof_data_index++];

        if (proof.L.size() != BulletproofsRangeproof::logN+pd.logM)
            return false;

        const size_t M = 1 << pd.logM;
        const size_t MN = M*N;

        Scalar weight_y = Scalar::Rand();
        Scalar weight_z = Scalar::Rand();

        y0 = y0 - (proof.taux * weight_y);

        std::vector<Scalar> zpow = VectorPowers(pd.z, M+3);

        Scalar k;
        Scalar ip1y = VectorPowerSum(pd.y, MN);

        k = (zpow[2]*ip1y).Negate();

        for (size_t j = 1; j <= M; ++j)
        {
            k = k - (zpow[j+2]*BulletproofsRangeproof::ip12);
        }

        tmp = k + (pd.z*ip1y);

        tmp = (proof.t - tmp);

        y1 = y1 + (tmp * weight_y);

        for (size_t j = 0; j < pd.V.size(); j++)
        {
            tmp = zpow[j+2] * weight_y;
            multiexpdata.push_back({pd.V[j], tmp});
        }

        tmp = pd.x * weight_y;

        multiexpdata.push_back({proof.T1, tmp});

        tmp = pd.x * pd.x * weight_y;

        multiexpdata.push_back({proof.T2, tmp});
        multiexpdata.push_back({proof.A, weight_z});

        tmp = pd.x * weight_z;

        multiexpdata.push_back({proof.S, tmp});

        const size_t rounds = pd.logM+BulletproofsRangeproof::logN;

        Scalar yinvpow = 1;
        Scalar ypow = 1;

        const Scalar *winv = &inverses[pd.inv_offset];
        const Scalar yinv = inverses[pd.inv_offset + rounds];

        std::vector<Scalar> w_cache(1<<rounds, 1);
        w_cache[0] = winv[0];
        w_cache[1] = pd.w[0];

        for (size_t j = 1; j < rounds; ++j)
        {
            const size_t sl = 1<<(j+1);
            for (size_t s = sl; s-- > 0; --s)
            {
                w_cache[s] = w_cache[s/2] * pd.w[j];
                w_cache[s-1] = w_cache[s/2] * winv[j];
            }
        }

        for (size_t i = 0; i < MN; ++i)
        {
            Scalar g_scalar = proof.a;
            Scalar h_scalar;

            if (i == 0)
                h_scalar = proof.b;
            else
                h_scalar = proof.b * yinvpow;

            g_scalar = g_scalar * w_cache[i];
            h_scalar = h_scalar * w_cache[(~i) & (MN-1)];

            g_scalar = g_scalar + pd.z;

            tmp = zpow[2+i/N] * BulletproofsRangeproof::twoN[i%N];

            if (i == 0)
            {
                tmp = tmp + pd.z;
                h_scalar = h_scalar - tmp;
            }
            else
            {
                tmp = tmp + (pd.z*ypow);
                h_scalar = h_scalar - (tmp * yinvpow);
            }

            z4[i] = z4[i] - (g_scalar * weight_z);
            z5[i] = z5[i] - (h_scalar * weight_z);

            if (i == 0)
            {
                yinvpow = yinv;
                ypow = pd.y;
            }
            else if (i != MN-1)
            {
                yinvpow = yinvpow * yinv;
                ypow = ypow * pd.y;
            }
        }

        z1 = z1 + (proof.mu * weight_z);

        for (size_t i = 0; i < rounds; ++i)
        {
            tmp = pd.w[i] * pd.w[i] * weight_z;

            multiexpdata.push_back({proof.L[i], tmp});

            tmp = winv[i] * winv[i] * weight_z;

            multiexpdata.push_back({proof.R[i], tmp});
        }

        tmp = proof.t - (proof.a*proof.b);
        tmp = tmp * pd.x_ip;
        z3 = z3 + (tmp * weight_z);
    }

    tmp = y0 - z1;

    multiexpdata.push_back({BulletproofsRangeproof::G, tmp});

    tmp = z3 - y1;

    multiexpdata.push_back({BulletproofsRangeproof::H, tmp});

    for (size_t i = 0; i < maxMN; ++i)
    {
        multiexpdata[i * 2] = {BulletproofsRangeproof::Gi[i], z4[i]};
        multiexpdata[i * 2 + 1] = {BulletproofsRangeproof::Hi[i], z5[i]};
    }

    bls::G1Element mexp = MultiExp(multiexpdata);

    return mexp == bls::G1Element::Infinity();
}


#ifndef BODY_HPP
#define BODY_HPP

#include <skelly_sim.hpp>

#include <Eigen/LU>
#include <kernels.hpp>
#include <params.hpp>

class Periphery;
class SphericalBody;

/// Class for "small" bodies such as MTOCs
class Body {
  public:
    int n_nodes_; ///< Number of nodes representing the body surface

    Eigen::Vector3d position_;       ///< Instantaneous lab frame position of body, usually the centroid
    Eigen::Quaterniond orientation_; ///< Instantaneous orientation of body
    Eigen::Quaterniond orientation_ref_ = {1.0, 0.0, 0.0, 0.0}; ///< Reference orientation of body
    Eigen::Vector3d velocity_;                                  ///<  Net instantaneous lab frame velocity of body
    Eigen::Vector3d angular_velocity_;         ///< Net instantaneous lab frame angular velocity of body
    Eigen::Matrix<double, 6, 1> force_torque_; ///< Net force+torque vector [fx,fy,fz,tx,ty,tz] about centroid
    Eigen::VectorXd RHS_;                      ///< Current 'right-hand-side' for matrix formulation of solver

    Eigen::MatrixXd ex_; ///< [ 3 x num_nodes ] Singularity subtraction vector along x
    Eigen::MatrixXd ey_; ///< [ 3 x num_nodes ] Singularity subtraction vector along y
    Eigen::MatrixXd ez_; ///< [ 3 x num_nodes ] Singularity subtraction vector along z

    Eigen::MatrixXd K_; ///< [ 3*num_nodes x 6 ] matrix that helps translate body info to nodes

    Eigen::MatrixXd node_positions_;     ///< [ 3 x n_nodes ] node positions in lab frame
    Eigen::MatrixXd node_positions_ref_; ///< [ 3 x n_nodes ] node positions in reference 'body' frame
    Eigen::MatrixXd node_normals_;       ///< [ 3 x n_nodes ] node normals in lab frame
    Eigen::MatrixXd node_normals_ref_;   ///< [ 3 x n_nodes ] node normals in reference 'body' frame
    Eigen::VectorXd node_weights_;       ///< [ n_nodes ] far field quadrature weights for nodes

    /// [ 3 x n_nucleation_sites ] nucleation site positions in reference 'body' frame
    Eigen::MatrixXd nucleation_sites_ref_;
    /// [ 3 x n_nucleation_sites ] nucleation site positions in lab frame
    Eigen::MatrixXd nucleation_sites_;

    /// [3] vector of constant external force on body in lab frame
    Eigen::Vector3d external_force_{0.0, 0.0, 0.0};

    Eigen::MatrixXd A_;                         ///< Matrix representation of body for solver
    Eigen::PartialPivLU<Eigen::MatrixXd> A_LU_; ///< LU decomposition of A_ for preconditioner

    Body(const toml::value &body_table, const Params &params);
    Body() = default; ///< default constructor...

    /// Return reference to body COM position
    const Eigen::Vector3d &get_position() const { return position_; };
    void update_RHS(MatrixRef &v_on_body);
    void update_cache_variables(double eta);
    void update_K_matrix();
    void update_preconditioner(double eta);
    void update_singularity_subtraction_vecs(double eta);
    void load_precompute_data(const std::string &input_file);
    void move(const Eigen::Vector3d &new_pos, const Eigen::Quaterniond &new_orientation);

    /// @brief Make a copy of this instance
    virtual std::unique_ptr<Body> clone() const { return std::make_unique<Body>(*this); }

    /// @brief dummy method to be overriden by derived classes
    virtual bool check_collision(const Periphery &periphery, double threshold) const {
        // FIXME: there is probably a way to make our objects abstract base classes, but it makes the containers weep if
        // you make this a pure virtual function, so instead we just throw an error.
        throw std::runtime_error("Collision undefined on base Body class\n");
    };

    /// @brief dummy method to be overriden by derived classes
    virtual bool check_collision(const Body &body, double threshold) const {
        throw std::runtime_error("Collision undefined on base Body class\n");
    };

    /// @brief dummy method to be overriden by derived classes
    virtual bool check_collision(const SphericalBody &body, double threshold) const {
        throw std::runtime_error("Collision undefined on base Body class\n");
    };

    /// @brief Serialize body automatically with msgpack macros
    MSGPACK_DEFINE_MAP(position_, orientation_);

    /// For structures with fixed size Eigen::Vector types, this ensures alignment if the
    /// structure is allocated via `new`
    EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};

/// @brief Container for multiple generic Body objects
class BodyContainer {
  private:
    int world_rank_ = 0; ///< MPI world rank
    int world_size_;     ///< MPI world size

  public:
    /// Vector of body pointers
    std::vector<std::unique_ptr<Body>> bodies;

    /// Pointer to FMM stresslet kernel (Stokes)
    std::shared_ptr<kernels::FMM<stkfmm::Stk3DFMM>> stresslet_kernel_;
    /// Pointer to FMM oseen kernel (PVel)
    std::shared_ptr<kernels::FMM<stkfmm::Stk3DFMM>> oseen_kernel_;

    /// @brief Empty container constructor to avoid initialization list complications.
    BodyContainer() = default;
    BodyContainer(toml::array &body_tables, Params &params);

    // FIXME: remove redundant code in =/copy
    /// @brief Copy constructor...
    BodyContainer(const BodyContainer &orig) {
        for (auto &body : orig.bodies) {
            bodies.push_back(body->clone());
        }
        world_rank_ = orig.world_rank_;
        world_size_ = orig.world_size_;
        stresslet_kernel_ = orig.stresslet_kernel_;
        oseen_kernel_ = orig.oseen_kernel_;
    };

    /// @brief Assignment operator...
    BodyContainer &operator=(const BodyContainer orig) {
        bodies.clear();
        for (auto &body : orig.bodies) {
            bodies.push_back(body->clone());
        }
        world_rank_ = orig.world_rank_;
        world_size_ = orig.world_size_;
        stresslet_kernel_ = orig.stresslet_kernel_;
        oseen_kernel_ = orig.oseen_kernel_;
        return *this;
    }

    /// @brief Get total number of nodes associated with body
    ///
    /// See BodyContainer::get_local_solution_size for notes about how this is distributed
    int get_local_node_count() const {
        if (world_rank_ != 0)
            return 0;

        int tot = 0;
        for (const auto &body : bodies)
            tot += body->n_nodes_;
        return tot;
    }

    /// @brief Get the size of all local bodies contribution to the matrix problem solution
    ///
    /// Since there aren't many bodies, and there is no easy way to synchronize them across processes, the rank 0
    /// process handles all components of the solution.
    int get_local_solution_size() const {
        return (world_rank_ == 0) ? 3 * get_local_node_count() + 6 * bodies.size() : 0;
    }

    void update_RHS(MatrixRef &v_on_body);
    Eigen::VectorXd get_RHS() const;
    Eigen::MatrixXd get_global_node_positions() const;
    Eigen::MatrixXd get_local_node_positions() const;
    Eigen::MatrixXd get_local_node_normals() const;

    /// @brief Get body center position
    /// @param[in] override_distributed if 'true', get center position regardless of MPI rank, otherwise only rank 0
    /// @return [3 x n_bodies] vector of center positions
    Eigen::MatrixXd get_center_positions(bool override_distributed) const {
        if (world_rank_ != 0 && !override_distributed)
            return Eigen::MatrixXd(3, 0);

        Eigen::MatrixXd centers(3, bodies.size());
        for (size_t i = 0; i < bodies.size(); ++i) {
            centers.block(0, i, 3, 1) = bodies[i]->position_;
        }

        return centers;
    };

    std::pair<Eigen::MatrixXd, Eigen::MatrixXd> unpack_solution_vector(VectorRef &x) const;

    Eigen::MatrixXd get_local_center_positions() const { return get_center_positions(false); };
    Eigen::MatrixXd get_global_center_positions() const { return get_center_positions(true); };
    Eigen::VectorXd matvec(MatrixRef &v_bodies, MatrixRef &body_densities, MatrixRef &body_velocities) const;
    Eigen::VectorXd apply_preconditioner(VectorRef &X) const;

    Eigen::MatrixXd flow(MatrixRef &r_trg, MatrixRef &densities, MatrixRef &force_torque_bodies, double eta) const;

    /// @brief Update cache variables for each Body. @see Body::update_cache_variables
    void update_cache_variables(double eta) {
        for (auto &body : bodies)
            body->update_cache_variables(eta);
    }

    /// @brief Get copy of a given nucleation site
    ///
    /// @param[in] i_body index of relevant body
    /// @param[in] j_site index of nucleation site
    Eigen::Vector3d get_nucleation_site(int i_body, int j_site) const {
        return bodies[i_body]->nucleation_sites_.col(j_site);
    };

    /// @brief get reference to ith Body without inspecting internal BodyContainer::bodies
    ///
    /// @param[in] i index of body in container
    /// @return Reference to ith Body in bodies
    const Body &at(size_t i) const { return *bodies.at(i); };

    /// @brief return number of bodies relevant for local calculations
    size_t get_local_count() const { return (world_rank_ == 0) ? bodies.size() : 0; };
    /// @brief return number of bodies relevant for global calculations
    size_t get_global_count() const { return bodies.size(); };

    /// @brief Return total number of body nodes across all MPI ranks
    size_t get_global_node_count() const {
        size_t n_nodes = 0;
        for (const auto &body : bodies)
            n_nodes += body->n_nodes_;
        return n_nodes;
    }

    /// @brief Return total number of body nucleation sites across all MPI ranks
    size_t get_global_site_count() const {
        size_t n_sites = 0;
        for (const auto &body : bodies)
            n_sites += body->nucleation_sites_.cols();
        return n_sites;
    }

    /// @brief msgpack serialization routine
    MSGPACK_DEFINE(bodies);
};

/// @brief Spherical Body...
class SphericalBody : public Body {
  public:
    /// @brief Construct spherical body. @see Body
    /// @param[in] body_table Parsed TOML body table. Must have 'radius' key defined.
    /// @param[in] params Initialized Params object
    SphericalBody(const toml::value &body_table, const Params &params) : Body(body_table, params) {
        radius_ = toml::find_or<double>(body_table, "radius", 0.0);
    };

    /// Duplicate SphericalBody object
    std::unique_ptr<Body> clone() const override { return std::make_unique<SphericalBody>(*this); };
    double radius_; ///< Radius of body

    bool check_collision(const Periphery &periphery, double threshold) const override;
    bool check_collision(const Body &body, double threshold) const override;
    bool check_collision(const SphericalBody &body, double threshold) const override;
};

#endif


//
//  ADMMDDTimeStepper.hpp
//  DOT
//
//  Created by Minchen Li on 7/17/18.
//

#ifndef ADMMDDTimeStepper_hpp
#define ADMMDDTimeStepper_hpp

#include "Optimizer.hpp"

#include "Timer.hpp"

#include <Eigen/Cholesky>

namespace DOT {

    template<int dim>
    class ADMMDDTimeStepper : public Optimizer<dim>
    {
        typedef Optimizer<dim> Base;

    protected:
        std::vector<Eigen::VectorXi> elemList_subdomain;
        std::vector<Mesh<dim>> mesh_subdomain;
        std::vector<std::map<int, int>> globalVIToLocal_subdomain;
        std::vector<std::vector<int>> localVIToGlobal_subdomain;
        std::vector<std::map<int, int>> globalTriIToLocal_subdomain;
        std::vector<std::map<int, int>> globalVIToDual_subdomain;
        std::vector<std::vector<int>> dualIndIToLocal_subdomain; //TODO: use it to simplify index querying
        std::vector<std::vector<int>> dualIndIToShared_subdomain;
        std::vector<std::vector<int>> interElemGlobalI_sbd;
        std::vector<Eigen::MatrixXd> xHat_subdomain;
        std::vector<double> tol_subdomain;
        std::vector<int> localIterCount_sbd;
        int dualDim;
        std::vector<Eigen::MatrixXd> u_subdomain, du_subdomain, dz_subdomain, y_subdomain;
        std::vector<Eigen::MatrixXd> weights_subdomain;
        std::vector<std::map<std::pair<int, int>, double>> weightMtr_subdomain;
        std::vector<std::map<std::pair<int, int>, double>> weightMtrFixed_subdomain;
        Eigen::MatrixXd weightSum;
        std::vector<bool> isSharedVert;
        std::map<int, int> globalVIToShared;
        Eigen::VectorXi sharedVerts;

        Eigen::SparseMatrix<double> consensusMtr;
        std::vector<double*> CM_elemPtr;
        LinSysSolver<Eigen::VectorXi, Eigen::VectorXd>* consensusSolver;
        Eigen::VectorXd solvedSharedVerts;

        std::vector<bool> m_isUpdateElemHessian;
        std::vector<LinSysSolver<Eigen::VectorXi, Eigen::VectorXd>*> linSysSolver_subdomain;
        std::vector<std::vector<AutoFlipSVD<Eigen::Matrix<double, dim, dim>>>> svd_subdomain;
        std::vector<std::vector<Eigen::Matrix<double, dim, dim>>> F_subdomain, F_subdomain_last, U_sbd, V_sbd;
        std::vector<std::vector<Eigen::Matrix<double, dim, 1>>> Sigma_sbd;
        bool useDense = false;
        std::vector<Eigen::MatrixXd> hessian_sbd;
        std::vector<Eigen::LDLT<Eigen::MatrixXd>> denseSolver_sbd;

        double sqn_g;
        Eigen::MatrixXd resultVk; //TODO: only need the interface verts

        std::vector<Timer> timer_sbd;
        Timer timer_sum;

    public:
        ADMMDDTimeStepper(const Mesh<dim>& p_data0,
                          const std::vector<Energy<dim>*>& p_energyTerms,
                          const std::vector<double>& p_energyParams,
                          bool p_mute = false,
                          const Config& animConfig = Config());
        virtual ~ADMMDDTimeStepper(void);

    public:
        virtual void precompute(void);

        virtual void getFaceFieldForVis(Eigen::VectorXd& field);
        virtual void getSharedVerts(Eigen::VectorXi& sharedVerts) const;

        virtual void writeMeshToFile(const std::string& filePath_pre) const;

        virtual const Eigen::MatrixXd& getDenseMatrix(int sbdI) const;
        virtual void getGradient(int sbdI, Eigen::VectorXd& gradient);
        virtual LinSysSolver<Eigen::VectorXi, Eigen::VectorXd>* getSparseSolver(int sbdI);

    protected:
        virtual bool fullyImplicit(void);

    protected:
        void initPrimal(int option); // 0: last timestep; 1: explicit Euler; 2: xHat; 3: Symplectic Euler; 4: uniformly accelerated motion approximation; 5: Jacobi
        void initDual(void);
        void initWeights(bool overwriteSbdH);
        void initWeights_fast(bool overwriteSbdH);
        void initConsensusSolver(void);
        void updateConsensusSolver(void);

        void subdomainSolve(int localMaxIter = 100,
                            int localMinIter = 0,
                            bool updateH = true,
                            bool linesearch = true); // local solve
        void checkRes(void);
        void boundaryConsensusSolve(double relaxParam = 1.0); // global solve
        void dualSolve(double stepSize = 1.0, double relaxParam = 1.0);

        void uToY(void);
        void yToU(void);

        void computeError(const Eigen::VectorXd& residual,
                          double& err_in, double& err_b) const;

        // subdomain energy computation
        void computeEnergyVal_subdomain(int subdomainI, bool redoSVD, double& Ei);
        void computeGradient_subdomain(int subdomainI, bool redoSVD, Eigen::VectorXd& g);
        void computeHessianProxy_subdomain(int subdomainI, bool redoSVD,
                                           bool augmentLag);

        void extract(const Eigen::VectorXd& src_g,
                     int sbdI, Eigen::VectorXd& dst_l) const;
        void fill(int sbdI, const Eigen::VectorXd& src_l,
                  Eigen::VectorXd& dst_g) const;
    };
}

#endif /* ADMMDDTimeStepper_hpp */


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

#include <kernel.hpp>
#include <edfsched.hpp>
#include <fifosched.hpp>
#include <jtrace.hpp>
#include <texttrace.hpp>
#include <rttask.hpp>
#include <grubserver.hpp>
#include <load.hpp>
#include <taskstat.hpp>

#include <boost/program_options.hpp>

#include "sim_structs.hpp"

using namespace MetaSim;
using namespace RTSim;

using namespace std;

#define N_HIGH_TASKS    4
#define N_LOW_TASKS     4

double highUtil;
double lowHighRatio_Min;
double lowHighRatio_Max;
int High_Min_Period;
int High_Max_Period;
int Low_Min_Period;
int Low_Max_Period;
int period_gcd;
int NRep;
double RStep;

int SimLength;

bool SingleServer = false;
string base_name;

bool ConstantUtil = false;

int main(int argc, char*argv[])
{
    namespace po = boost::program_options;

    po::options_description desc("Options");
    desc.add_options()
	("help", "Print help messages")
	("high_u", po::value<double>(&highUtil)->default_value(.5), "Min utilization of HI-tasks")
	("rstep", po::value<double>(&RStep)->default_value(.05), "Step of ratio")
	("ratio_min", po::value<double>(&lowHighRatio_Min)->default_value(.75), "Min Ratio between utilization in Low Critical mode and High Critical mode of HI-tasks")
	("ratio_max", po::value<double>(&lowHighRatio_Max)->default_value(.75), "Max Ratio between utilization in Low Critical mode and High Critical mode of HI-tasks")
	("high_min_p", po::value<int>(&High_Min_Period)->required(), "Minimum period of High-Tasks")
	("high_max_p", po::value<int>(&High_Max_Period)->required(), "Maximum period of High-Tasks")
	("low_min_p", po::value<int>(&Low_Min_Period)->required(), "Minimum period of Low-Tasks")
	("low_max_p", po::value<int>(&Low_Max_Period)->required(), "Maximum period of Low-Tasks")
	("gcd", po::value<int>(&period_gcd)->default_value(100), "GCD of periods")
	("rep", po::value<int>(&NRep)->default_value(10), "Number of repetitions")
	("constant", po::value<bool>(&ConstantUtil)->default_value(false), "Constant utilization of low tasks")
	("single", po::value<bool>(&SingleServer)->default_value(false), "True is all LO-tasks go in a single server")
	("len", po::value<int>(&SimLength)->default_value(10000000), "Lenght of each simulation")
	("output", po::value<string>(&base_name)->default_value("st_"), "base name of the output files");

    po::variables_map vm;
    try
    {
	po::store(po::parse_command_line(argc, argv, desc),
		  vm); // can throw

	/** --help option
	 */
	if ( vm.count("help")  )
	{
	    std::cout << "Basic Command Line Parameter App" << std::endl
		      << desc << std::endl;
	    return 0;
	}

	po::notify(vm); // throws on error, so do after help in case
	// there are any problems
    } catch(po::error& e) {
	std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
	std::cerr << desc << std::endl;
	return -1;
    } catch(std::exception& e) {
	std::cerr << "Unhandled Exception reached the top of main: "
		  << e.what() << ", application will now exit" << std::endl;
	return -2;
    }

    try {
	double u_step = .05;
	int i = 0;
	for (double ratio=lowHighRatio_Min ; ratio < lowHighRatio_Max; ratio+=RStep) {
	    cout << "Ratio: " << ratio << endl;
	    // create the scheduler and the kernel
	    Stats mystats;
	    for (int k=0; k<NRep; k++) {
		MySim sim(highUtil, ratio);

		//preparing the HIGH CRITICAL TASKS
		RandomTaskSetFactory ts(N_HIGH_TASKS, highUtil * ratio,
					new ConstIATGen(High_Min_Period, High_Max_Period, High_Min_Period, period_gcd),
					new ConstCTGen,
					new DlineEquPeriodDTGen);

		vector<Grub *> servers;
		for (unsigned j=0; j<ts.size(); j++) {
		    ts.getTask(j)->setRelDline(ts.getMaxIAT(j));
		    cout << "Created task: ("
			 << ts.getMaxCT(j) << " , " << ts.getMaxIAT(j) << ","
			 << ts.getTask(j)->getRelDline() << ")" <<  endl;
		    sim.create_high_server(*ts.getTask(j));
		}

		// generating the LOW CRITICALITY task set
		// I generate an utilization which is
		double lu;
		if (ConstantUtil) lu = .99 - highUtil * lowHighRatio_Max;
		else lu = .99 - highUtil * ratio;

		RandomTaskSetFactory ts2(N_LOW_TASKS, lu,
					 new ConstIATGen(Low_Min_Period, Low_Max_Period, Low_Min_Period, period_gcd),
					 new ConstCTGen,
					 new DlineEquPeriodDTGen);
		for (unsigned j=0; j<ts2.size(); j++) {
		    ts2.getTask(j)->setRelDline(ts2.getMaxIAT(j));
		    cout << "Created task: (" <<
			ts2.getMaxCT(j) << " , " <<
			ts2.getMaxIAT(j) << "," <<
			ts2.getTask(j)->getRelDline() << ")" <<  endl;
		    // create a server for a LOW CRITICALITY TASK
		    // I have to allocate less bandwidth.
		    // The actual available bandwidth is  (1 - HighUtil)
		    // I need to allocate (1 - HighUtil) / (1 - HighLowUtil)

		    if (SingleServer) {
			sim.create_single_server(*ts2.getTask(j), period_gcd);
		    } else {
			sim.create_low_server(*ts2.getTask(j), lu);
		    }

		    mystats.miss_count.attachToTask(ts2.getTask(j));
		    mystats.miss_perc.attachToTask(ts2.getTask(j));
		    mystats.tard.attachToTask(ts2.getTask(j));
		}
		cout << "DONE! Now start simulation" << endl;
		int n = 1;
		if (k == 0)  n = -NRep;
		else if (k == NRep-1) n = 0;
		else n = -1;
		SIMUL.run(SimLength, n);
	    }
	    mystats.dump(ratio, base_name);
	}
    } catch (BaseExc &e) {
        cout << e.what() << endl;
    } catch (parse_util::ParseExc &e2) {
        cout << e2.what() << endl;
    }
}


#include <Eigen/Dense>
#include <memory>

#include "catch.hpp"

#include "nodal_properties.h"

//! \brief Check NodalProperties struct
TEST_CASE("NodalProperties is checked", "[nodal_properties]") {
  // Number of nodes
  const unsigned nnodes = 3;
  // Number of materials
  const unsigned nmaterials = 2;
  // Tolerance
  const double tolerance = 1.E-7;

  // Check scalar property creation
  SECTION("Create scalar properties") {
    // Declare nodal properties
    mpm::NodalProperties nodal_properties;

    // Define properties to be created
    std::string property1 = "masses";
    std::string property2 = "areas";

    // Define dimension (1D for scalar)
    const unsigned dim = 1;

    // Check property creation
    REQUIRE(
        nodal_properties.create_property(property1, nnodes * dim, nmaterials));
    REQUIRE(
        nodal_properties.create_property(property2, nnodes * dim, nmaterials));

    // Check size of matrix of property data
    REQUIRE(nodal_properties.properties_.at(property1).rows() == 3);
    REQUIRE(nodal_properties.properties_.at(property1).cols() == 2);
    REQUIRE(nodal_properties.properties_.at(property1).size() == 6);
    REQUIRE(nodal_properties.properties_.at(property2).rows() == 3);
    REQUIRE(nodal_properties.properties_.at(property2).cols() == 2);
    REQUIRE(nodal_properties.properties_.at(property2).size() == 6);

    // Check if values in nodal properties are equal to 0.0
    for (int i = 0; i < nnodes; ++i) {
      for (int j = 0; j < nmaterials; ++j) {
        for (int k = 0; k < dim; ++k) {
          REQUIRE(nodal_properties.property(property1, i, j, dim)(k, 0) ==
                  Approx(0.0).epsilon(tolerance));
          REQUIRE(nodal_properties.property(property2, i, j, dim)(k, 0) ==
                  Approx(0.0).epsilon(tolerance));
        }
      }
    }

    // clang-format off
    Eigen::Matrix<double,3,2> data1;
    data1 << 4.2, 3.1,
             0.5, 1.7,
             6.6, 8.2;
    Eigen::Matrix<double,3,2> data2;
    data2 << 4.1, 3.2,
             6.8, 5.1,
             9.1, 2.0;
    // clang-format on

    // Update values in the property data matrix and check update
    for (int i = 0; i < nnodes; ++i) {
      for (int j = 0; j < nmaterials; ++j) {
        REQUIRE_NOTHROW(nodal_properties.assign_property(
            property1, i, j, data1.block<dim, 1>(i * dim, j), dim));
        REQUIRE_NOTHROW(nodal_properties.assign_property(
            property2, i, j, data2.block<dim, 1>(i * dim, j), dim));
      }
    }

    // Check values in matrix of property data
    for (int i = 0; i < nnodes; ++i) {
      for (int j = 0; j < nmaterials; ++j) {
        for (int k = 0; k < dim; ++k) {
          REQUIRE(nodal_properties.property(property1, i, j, dim)(k, 0) ==
                  Approx(data1(i * dim + k, j)).epsilon(tolerance));
          REQUIRE(nodal_properties.property(property2, i, j, dim)(k, 0) ==
                  Approx(data2(i * dim + k, j)).epsilon(tolerance));
        }
      }
    }
  }

  // Check vector property creation
  SECTION("Create vector properties") {
    // Declare nodal properties
    mpm::NodalProperties nodal_properties;

    // Define properties to be created
    std::string property1 = "velocities";
    std::string property2 = "momenta";

    // Define dimension (2D)
    const unsigned dim = 2;

    // Check property creation
    REQUIRE(
        nodal_properties.create_property(property1, nnodes * dim, nmaterials));
    REQUIRE(
        nodal_properties.create_property(property2, nnodes * dim, nmaterials));

    // Check size of matrix of property data
    REQUIRE(nodal_properties.properties_.at(property1).rows() == 6);
    REQUIRE(nodal_properties.properties_.at(property1).cols() == 2);
    REQUIRE(nodal_properties.properties_.at(property1).size() == 12);
    REQUIRE(nodal_properties.properties_.at(property2).rows() == 6);
    REQUIRE(nodal_properties.properties_.at(property2).cols() == 2);
    REQUIRE(nodal_properties.properties_.at(property2).size() == 12);

    // Check if values in nodal properties are equal to 0.0
    for (int i = 0; i < nnodes; ++i) {
      for (int j = 0; j < nmaterials; ++j) {
        for (int k = 0; k < dim; ++k) {
          REQUIRE(nodal_properties.property(property1, i, j, dim)(k, 0) ==
                  Approx(0.0).epsilon(tolerance));
          REQUIRE(nodal_properties.property(property2, i, j, dim)(k, 0) ==
                  Approx(0.0).epsilon(tolerance));
        }
      }
    }

    // clang-format off
    Eigen::Matrix<double,6,2> data1;
    data1 << 4.2, 3.1,
             1.0, 1.7, // end of first node
             2.2, 8.2,
             3.5, 2.5, // end of second node
             9.7, 1.1,
             6.4, 0.7; // end of third node
    Eigen::Matrix<double,6,2> data2;
    data2 << 4.1, 3.2,
             7.7, 5.1, // end first node
             2.4, 2.0,
             2.5, 4.3, // end of second node
             9.8, 8.3,
             6.1, 0.1; // end of third node
    // clang-format on

    // Update values in the property data matrix and check update
    for (int i = 0; i < nnodes; ++i) {
      for (int j = 0; j < nmaterials; ++j) {
        REQUIRE_NOTHROW(nodal_properties.assign_property(
            property1, i, j, data1.block<dim, 1>(i * dim, j), dim));
        REQUIRE_NOTHROW(nodal_properties.assign_property(
            property2, i, j, data2.block<dim, 1>(i * dim, j), dim));
      }
    }

    // Check values in matrix of property data
    for (int i = 0; i < nnodes; ++i) {
      for (int j = 0; j < nmaterials; ++j) {
        for (int k = 0; k < dim; ++k) {
          REQUIRE(nodal_properties.property(property1, i, j, dim)(k, 0) ==
                  Approx(data1(i * dim + k, j)).epsilon(tolerance));
          REQUIRE(nodal_properties.property(property2, i, j, dim)(k, 0) ==
                  Approx(data2(i * dim + k, j)).epsilon(tolerance));
        }
      }
    }
  }
}


/*
 * slavcheva_optimizer.cpp
 *
 *  Created on: Jan 11, 2019
 *      Author: Gregory Kramida
 *   Copyright: 2019 Gregory Kramida
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
//stdlib
#include <iostream>

//libraries
#include <Eigen/Eigen>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>

//local
#include "eigen_numpy.hpp"
#include "../nonrigid_optimization/slavcheva/sobolev_optimizer2d.hpp"
#include "../nonrigid_optimization/field_warping.hpp"
#include "../nonrigid_optimization/slavcheva/data_term.hpp"
#include "../telemetry/warp_delta_statistics.hpp"
#include "../telemetry/convergence_report.hpp"

namespace bp = boost::python;
namespace nro_s = nonrigid_optimization::slavcheva;
namespace nro = nonrigid_optimization;

struct NullDeleter {
	void operator()(const void*) {
	}
};

typedef nro_s::SobolevOptimizer2d::SobolevParameters SobolevParameters;
typedef nro_s::Optimizer2d::SharedParameters SharedParameters;

boost::shared_ptr<SobolevParameters> get_shared_sobolev_parameters() {
	return boost::shared_ptr<SobolevParameters>(
			&SobolevParameters::get_instance(), NullDeleter());
}

boost::shared_ptr<SharedParameters> get_shared_shared_parameters() {
	return boost::shared_ptr<SharedParameters>(&SharedParameters::get_instance(), NullDeleter());
}

BOOST_PYTHON_FUNCTION_OVERLOADS(resample_overloads, nonrigid_optimization::py_warp_field_advanced, 4, 8)
BOOST_PYTHON_FUNCTION_OVERLOADS(resample_warp_unchanged_overloads, nonrigid_optimization::py_warp_field_advanced_no_warp_change,
		4,8)

namespace python_export {

namespace slavcheva {
void export_auxiliary_functions() {
	bp::def("data_term_at_location", nro_s::py_data_term_at_location);

	bp::def("warp_field_advanced", nro::py_warp_field_advanced,
			resample_overloads(
					bp::args("warped_live_field", "canonical_field", "warp_field_u", "warp_field_v", "band_union_only",
							"known_values_only", "substitute_original", "truncation_float_threshold"),
					" resample (warp) a 2D scalar field using a 2D vector field, such that new value at (x,y)"
							" is interpolated bilinearly from the original scalar field (warped_live_field) at the location "
							"(x+u, y+v), where"
							" the vector (u,v) is located at (x,y) within the warp field (i.e. u = warp_field_u[y,x]).\n"
							" :param band_union_only when set to True, skips processing locations for which both"
							" warped_live_field and canonical_field contain the values +1 or -1 (truncated sdf values)")
					);
	bp::def("warp_field_advanced_no_warp_change", nro::py_warp_field_advanced_no_warp_change,
			resample_warp_unchanged_overloads(
					bp::args("warped_live_field", "canonical_field", "warp_field_u", "warp_field_v", "band_union_only",
							"known_values_only", "substitute_original", "truncation_float_threshold"),
					" resample (warp) a 2D scalar field using a 2D vector field, such that new value at (x,y)"
							" is interpolated bilinearly from the original scalar field (warped_live_field) at the location "
							"(x+u, y+v), where"
							" the vector (u,v) is located at (x,y) within the warp field (i.e. u = warp_field_u[y,x]).\n"
							" :param band_union_only when set to True, skips processing locations for which both"
							" warped_live_field and canonical_field contain the values +1 or -1 (truncated sdf values)")
					);
}
void export_setting_singletons() {
	bp::class_<SobolevParameters, boost::shared_ptr<SobolevParameters>, boost::noncopyable>(
			"SobolevParameters", bp::no_init)
			.def("get_instance", &get_shared_sobolev_parameters)
			.staticmethod("get_instance")
			//.def_readwrite("sobolev_kernel", &SobolevParameters::sobolev_kernel)
			.def("get_sobolev_kernel", &SobolevParameters::get_sobolev_kernel)
			.def("set_sobolev_kernel", &SobolevParameters::set_sobolev_kernel)
			.def_readwrite("smoothing_term_weight", &SobolevParameters::smoothing_term_weight);

	bp::class_<SharedParameters, boost::shared_ptr<SharedParameters>, boost::noncopyable>(
			"SharedParameters", bp::no_init)
			.def("get_instance", &get_shared_shared_parameters)
			.staticmethod("get_instance")

	.def_readwrite("gradient_descent_rate", &SharedParameters::gradient_descent_rate)

	//termination condition parameters
	.def_readwrite("maximum_warp_length_lower_threshold",
			&SharedParameters::maximum_warp_length_lower_threshold)
			.def_readwrite("maximum_warp_length_upper_threshold",
			&SharedParameters::maximum_warp_length_upper_threshold)
			.def_readwrite("maximum_iteration_count", &SharedParameters::maximum_iteration_count)
			.def_readwrite("minimum_iteration_count", &SharedParameters::minimum_iteration_count)

	//logging
	.def_readwrite("enable_focus_spot_analytics", &SharedParameters::enable_focus_spot_analytics)
			.def_readwrite("enable_convergence_reporting",
			&SharedParameters::enable_convergence_reporting)
			.def_readwrite("enable_live_sdf_progression_logging",
			&SharedParameters::enable_live_sdf_progression_logging)
			.def_readwrite("enable_gradient_logging", &SharedParameters::enable_gradient_logging)
			.def_readwrite("enable_gradient_component_logging", &SharedParameters::enable_gradient_component_logging)
			.def_readwrite("enable_warp_statistics_logging", &SharedParameters::enable_warp_statistics_logging)

	//focus spot
	//TODO: math::Vector<X> C++-->numpy converter is needed for this
	.def_readwrite("focus_spot", &SharedParameters::focus_spot);
}

void export_algorithms() {
	bp::class_<nro_s::SobolevOptimizer2d>("SobolevOptimizer2d", bp::init<>())
			.def("optimize", &nro_s::SobolevOptimizer2d::optimize)
			.def("get_convergence_report", &nro_s::SobolevOptimizer2d::get_convergence_report)
			.def("get_warp_statistics_as_matrix", &nro_s::SobolevOptimizer2d::get_warp_statistics_as_matrix)
			;

}
} // namespace slavcheva
} // namespace python_export


//  Copyright (c) 2006-2009, Bernhard Reiter
//
//  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 LIBS_TREE_TEST_FAKE_BINARY_TREE_HPP
#define LIBS_TREE_TEST_FAKE_BINARY_TREE_HPP

#include <boost/tree/cursor_facade.hpp>
#include <boost/tree/cursor_adaptor.hpp>

#include <vector>

#include "test_data.hpp"

template <class T, class HT>
class fake_binary_cursor;

template <class T>
struct fake_root_tracking_binary_cursor;

using boost::tree::descending_vertical_traversal_tag;
using boost::tree::ascending_vertical_traversal_tag;

template <class T, class VT = descending_vertical_traversal_tag>
struct fake_binary_tree;

/// See http://en.wikipedia.org/wiki/Binary_Tree#Methods_for_storing_binary_trees
template <class T>
struct fake_binary_tree<T, descending_vertical_traversal_tag> {
    typedef std::vector<T> children_type;
    typedef typename children_type::size_type size_type;
    typedef typename children_type::value_type value_type;
    typedef typename children_type::difference_type difference_type;
    typedef typename children_type::pointer pointer;
    typedef typename children_type::reference reference;

    typedef fake_binary_cursor<T, descending_vertical_traversal_tag> descending_cursor;
    typedef fake_binary_cursor<T/* const*/, descending_vertical_traversal_tag> const_descending_cursor; //FIXME

    typedef descending_cursor cursor;
    typedef const_descending_cursor const_cursor;

protected:
    typedef fake_binary_cursor<T, ascending_vertical_traversal_tag> ascending_cursor;
    typedef fake_binary_cursor<T, ascending_vertical_traversal_tag> const_ascending_cursor; //FIXME

    typedef fake_root_tracking_binary_cursor<T> root_tracking_cursor;
    typedef fake_root_tracking_binary_cursor<T> const_root_tracking_cursor; //FIXME

public:
    fake_binary_tree(size_type s = 0) : m_data(s)
    { }

    descending_cursor descending_root()
    {
        return descending_cursor(*this, 0);
    }

    descending_cursor root()
    {
        return descending_cursor(*this, 0);
    }

    const_descending_cursor root() const
    {
        return const_descending_cursor(*this, 0);
    }

protected:
    ascending_cursor ascending_root()
    {
        return ascending_cursor(*this, 0);
    }

    const_ascending_cursor ascending_root() const
    {
        return const_ascending_cursor(*this, 0);
    }

    root_tracking_cursor root_tracking_root()
    {
        return root_tracking_cursor(*this, 0);
    }

    const_root_tracking_cursor root_tracking_root() const
    {
        return const_root_tracking_cursor(*this, 0);
    }

public:
    descending_cursor insert(descending_cursor c, value_type const& v)
    {
        if (c.m_pos >= m_data.size())
            m_data.resize(c.m_pos + 1);
        else {
            value_type tmp;
            size_type s = c.m_pos;

            while (m_data[s]) {
                tmp = m_data[s];
                s = 2*s + 1 + c.index();

                if (s >= m_data.size()) {
                    m_data.resize(s + 1);
                    m_data[s] = tmp;
                    break;
                }
                m_data[s] = tmp;
            }
        }

        m_data[c.m_pos] = v;
        return c;
    }

//    ascending_cursor insert(ascending_cursor c, value_type const& v)
//    {
//        m_data[c.m_pos] = v;
//        return c;
//    }
//
//    root_tracking_cursor insert(root_tracking_cursor c, value_type const& v)
//    {
//        m_data[c.m_pos] = v;
//        return c;
//    }

    children_type m_data;
};

template <class T>
struct fake_binary_tree<T, ascending_vertical_traversal_tag>
: fake_binary_tree<T, descending_vertical_traversal_tag> {
    typedef typename fake_binary_tree<T>::size_type size_type;
    typedef typename fake_binary_tree<T>::ascending_cursor cursor;
    typedef typename fake_binary_tree<T>::const_ascending_cursor const_cursor;

    fake_binary_tree(size_type s = 0)
    : fake_binary_tree<T, descending_vertical_traversal_tag>(s) { }

    fake_binary_tree(fake_binary_tree<T, descending_vertical_traversal_tag> const& f)
    : fake_binary_tree<T, descending_vertical_traversal_tag>(f) { }

    cursor root()
    {
        return fake_binary_tree<T, descending_vertical_traversal_tag>::ascending_root();
    }

    const_cursor root() const
    {
        return fake_binary_tree<T, descending_vertical_traversal_tag>::ascending_root();
    }
};

template <class T>
struct fake_root_tracking_binary_tree
: fake_binary_tree<T, descending_vertical_traversal_tag> {
    typedef typename fake_binary_tree<T>::size_type size_type;
    typedef typename fake_binary_tree<T>::root_tracking_cursor cursor;
    typedef typename fake_binary_tree<T>::const_root_tracking_cursor const_cursor;

    fake_root_tracking_binary_tree(size_type s = 0)
    : fake_binary_tree<T, descending_vertical_traversal_tag>(s) { }

    fake_root_tracking_binary_tree(fake_binary_tree<T, descending_vertical_traversal_tag> const& f)
    : fake_binary_tree<T, descending_vertical_traversal_tag>(f) { }

    cursor root()
    {
        return fake_binary_tree<T, descending_vertical_traversal_tag>::root_tracking_root();
    }

    const_cursor root() const
    {
        return fake_binary_tree<T, descending_vertical_traversal_tag>::root_tracking_root();
    }
};

template <class T, class VT>
bool operator==(fake_binary_tree<T, VT> const& x, fake_binary_tree<T, VT> const& y)
{
    return (x.m_data == y.m_data);
}

template <class T, class VT>
bool operator!=(fake_binary_tree<T, VT> const& x, fake_binary_tree<T, VT> const& y)
{
    return !(x == y);
}

// This should be easily extensible to nary by replacing the
// factor 2 by n in dereference, left, right and idx.
template <class T>
class fake_binary_cursor<T, descending_vertical_traversal_tag>
: public boost::tree::cursor_facade<
        fake_binary_cursor<T, descending_vertical_traversal_tag>
      , T
      , boost::bidirectional_traversal_tag
      , boost::tree::descending_vertical_traversal_tag
    >
{
public:
    typedef fake_binary_cursor<T, descending_vertical_traversal_tag> cursor;
    typedef fake_binary_cursor<T/* const*/, descending_vertical_traversal_tag> const_cursor;

    typedef typename fake_binary_cursor<T, descending_vertical_traversal_tag>::cursor_facade_::size_type size_type;
    typedef size_type subtree_size_type;

    fake_binary_cursor()
    : m_tree(0), m_pos(0) {}

    explicit fake_binary_cursor(fake_binary_tree<T>& t, size_type p = 0)
    : m_tree(&t), m_pos(p) {}

    fake_binary_cursor(fake_binary_cursor<T, descending_vertical_traversal_tag> const& other)
    : m_tree(other.m_tree), m_pos(other.m_pos) {}

    fake_binary_cursor<T, descending_vertical_traversal_tag>& operator=(fake_binary_cursor<T, descending_vertical_traversal_tag> const& other)
    {
        m_tree = other.m_tree;
        m_pos = other.m_pos;
        return *this;
    }

    fake_binary_tree<T>* m_tree;
    typename fake_binary_tree<T>::size_type m_pos;

private:
    friend class boost::iterator_core_access;
    friend class boost::tree::cursor_core_access;

    static const
    typename fake_binary_cursor<T, descending_vertical_traversal_tag>::cursor_facade_::value_type def_val
    = typename fake_binary_cursor<T, descending_vertical_traversal_tag>::cursor_facade_::value_type();

    typename fake_binary_cursor<T, descending_vertical_traversal_tag>::cursor_facade_::reference
    dereference() const
    {
        return m_tree->m_data[m_pos];
    }

    bool equal(fake_binary_cursor<T, descending_vertical_traversal_tag> const& other) const
    {
        return (this->m_tree == other.m_tree)
            && (this->m_pos == other.m_pos);
    }

    void increment()
    {
        ++m_pos;
    }

    void decrement()
    {
        --m_pos;
    }

    void left()
    {
        m_pos *= 2;
        ++m_pos;
    }

    void right()
    {
        ++m_pos;
        m_pos *= 2;
    }

    bool const empty_() const
    {
        if (m_pos >= m_tree->m_data.size())
            return true;
        if (m_pos == 0)
            return false;
        return (m_tree->m_data[m_pos] == def_val);
    }

    size_type const idx() const
    {
        return (m_pos + 1) % 2;
    }
};

template <class T>
typename fake_binary_cursor<T, descending_vertical_traversal_tag>::size_type
index(fake_binary_cursor<T, descending_vertical_traversal_tag> const& cur)
{
    return cur.index();
}

template <class T>
struct fake_binary_cursor<T, ascending_vertical_traversal_tag>
: public boost::tree::cursor_adaptor<fake_binary_cursor<T, ascending_vertical_traversal_tag>
                                   , fake_binary_cursor<T, descending_vertical_traversal_tag>
                                   , boost::use_default
                                   , boost::use_default
                                   , boost::tree::ascending_vertical_traversal_tag >
{
    typedef fake_binary_cursor<T, ascending_vertical_traversal_tag> cursor;
    typedef fake_binary_cursor<T const, ascending_vertical_traversal_tag> const_cursor;

    fake_binary_cursor(fake_binary_tree<T>& t
                                    , typename fake_binary_tree<T>::size_type p)
    : fake_binary_cursor::cursor_adaptor_(fake_binary_cursor<T, descending_vertical_traversal_tag>(t, p)) {}

private:
    friend class boost::iterator_core_access;
    friend class boost::tree::cursor_core_access;

    void up()
    {
        --this->base_reference().m_pos;
        this->base_reference().m_pos /= 2;
    }

public:
    bool is_root() const
    {
        return this->base_reference().m_pos == 0;
    }
};

template <class T>
struct fake_root_tracking_binary_cursor
: public boost::tree::cursor_adaptor<fake_root_tracking_binary_cursor<T>
                                   , fake_binary_cursor<T, ascending_vertical_traversal_tag>
                                    >
{
    typedef fake_root_tracking_binary_cursor<T> cursor;
    typedef fake_root_tracking_binary_cursor<T const> const_cursor;

    fake_root_tracking_binary_cursor(fake_binary_tree<T>& t
                                    , typename fake_binary_tree<T>::size_type p)
    : fake_root_tracking_binary_cursor::cursor_adaptor_(fake_binary_cursor<T, ascending_vertical_traversal_tag>(t, p)) {}

private:
    friend class boost::iterator_core_access;
    friend class boost::tree::cursor_core_access;

public:
    bool is_root() const
    {
        return this->base_reference().is_root();
    }
};

fake_binary_tree<int> generate_fake_binary_tree()
{
    test_data_set mpo;
    mock_cursor_data(mpo);

    typedef test_data_set::nth_index<0>::type container_type;
    const container_type& order_index = mpo.get<0>();

    container_type::const_iterator ci = order_index.begin();
    container_type::const_iterator cie = order_index.end();

    fake_binary_tree<int> mt(57);

    for(;ci!=cie;++ci) {
        if (ci->pos_level >= mt.m_data.size())
            mt.m_data.resize(ci->pos_level + 1);
        mt.m_data[ci->pos_level] = ci->val;
    }

    return mt;
}

template <class T = int>
struct fake_binary_tree_fixture {
    fake_binary_tree_fixture() : fbt1(generate_fake_binary_tree()), fbt2(57) {}

    template <class Cursor>
    static void validate_test_dataset1_tree(Cursor cur)
    {
        BOOST_CHECK_EQUAL(*cur, 8);
        BOOST_CHECK_EQUAL(*cur.begin(), 3);
        BOOST_CHECK_EQUAL(*cur.begin().begin(), 1);  //Leaf
        BOOST_CHECK_EQUAL(*cur.begin().end(), 6);
        BOOST_CHECK_EQUAL(*cur.begin().end().begin(), 4); //Leaf
        BOOST_CHECK_EQUAL(*cur.begin().end().end(), 7); //Leaf
        BOOST_CHECK_EQUAL(*cur.end(), 10);
        BOOST_CHECK_EQUAL(*cur.end().end(), 14);
        BOOST_CHECK_EQUAL(*cur.end().end().begin(), 13);
        BOOST_CHECK_EQUAL(*cur.end().end().begin().begin(), 11);
        BOOST_CHECK_EQUAL(*cur.end().end().begin().begin().end(), 12); //Leaf
    }

    template <class Cursor>
    static void validate_test_dataset1_minus_1_tree(Cursor cur)
    {
        BOOST_CHECK_EQUAL(*cur, 7);
        BOOST_CHECK_EQUAL(*cur.begin(), 2);
        BOOST_CHECK_EQUAL(*cur.begin().begin(), 0);  //Leaf
        BOOST_CHECK_EQUAL(*cur.begin().end(), 5);
        BOOST_CHECK_EQUAL(*cur.begin().end().begin(), 3); //Leaf
        BOOST_CHECK_EQUAL(*cur.begin().end().end(), 6); //Leaf

        BOOST_CHECK_EQUAL(*cur.end(), 9);
        BOOST_CHECK_EQUAL(*cur.end().end(), 13);
        BOOST_CHECK_EQUAL(*cur.end().end().begin(), 12);
        BOOST_CHECK_EQUAL(*cur.end().end().begin().begin(), 10);
        BOOST_CHECK_EQUAL(*cur.end().end().begin().begin().end(), 11); //Leaf
    }

    fake_binary_tree<T> fbt1, fbt2;
};

//template <class T = int>
//struct fake_binary_tree_with_list_fixture
//: public fake_binary_tree_fixture<T>
//, public test_with_list_fixture {
//     fake_binary_tree_with_list_fixture()
//     : fake_binary_tree_fixture<T>()
//     , test_with_list_fixture() {}
//};

#endif // LIBS_TREE_TEST_FAKE_BINARY_TREE_HPP