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

#include "pow.h"
#include "chain.h"
#include "chainparams.h"
#include "random.h"
#include "test/test_bitcoin.h"
#include "util.h"

#include <boost/test/unit_test.hpp>

using namespace std;

BOOST_FIXTURE_TEST_SUITE(pow_tests, BasicTestingSetup)

/* Test calculation of next difficulty target with no constraints applying */
BOOST_AUTO_TEST_CASE(get_next_work)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

    int64_t nLastRetargetTime = 1261130161; // Block #30240
    CBlockIndex pindexLast;
    pindexLast.nHeight = 32255;
    pindexLast.nTime = 1262152739; // Block #32255
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1d00d86a);
}

/* Test the constraint on the upper bound for next work */
BOOST_AUTO_TEST_CASE(get_next_work_pow_limit)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

    int64_t nLastRetargetTime = 1231006505; // Block #0
    CBlockIndex pindexLast;
    pindexLast.nHeight = 2015;
    pindexLast.nTime = 1233061996; // Block #2015
    pindexLast.nBits = 0x1d00ffff;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1d00ffff);
}

/* Test the constraint on the lower bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_lower_limit_actual)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

    int64_t nLastRetargetTime = 1279008237; // Block #66528
    CBlockIndex pindexLast;
    pindexLast.nHeight = 68543;
    pindexLast.nTime = 1279297671; // Block #68543
    pindexLast.nBits = 0x1c05a3f4;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1c0168fd);
}

/* Test the constraint on the upper bound for actual time taken */
BOOST_AUTO_TEST_CASE(get_next_work_upper_limit_actual)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

    int64_t nLastRetargetTime = 1263163443; // NOTE: Not an actual block time
    CBlockIndex pindexLast;
    pindexLast.nHeight = 46367;
    pindexLast.nTime = 1269211443; // Block #46367
    pindexLast.nBits = 0x1c387f6f;
    BOOST_CHECK_EQUAL(CalculateNextWorkRequired(&pindexLast, nLastRetargetTime, params), 0x1d00e1fd);
}

BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

    std::vector<CBlockIndex> blocks(10000);
    for (int i = 0; i < 10000; i++)
    {
        blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
        blocks[i].nHeight = i;
        blocks[i].nTime = 1269211443 + i * params.nPowTargetSpacing;
        blocks[i].nBits = 0x207fffff; /* target 0x7fffff000... */
        blocks[i].nChainWork = i ? blocks[i - 1].nChainWork + GetBlockProof(blocks[i - 1]) : arith_uint256(0);
    }

    for (int j = 0; j < 1000; j++)
    {
        CBlockIndex *p1 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p2 = &blocks[InsecureRandRange(10000)];
        CBlockIndex *p3 = &blocks[InsecureRandRange(10000)];

        int64_t tdiff = GetBlockProofEquivalentTime(*p1, *p2, *p3, params);
        BOOST_CHECK_EQUAL(tdiff, p1->GetBlockTime() - p2->GetBlockTime());
    }
}

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.BuildSkip();
    block.nChainWork = pindexPrev->nChainWork + GetBlockProof(block);
    return block;
}

BOOST_AUTO_TEST_CASE(retargeting_test)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

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

    const arith_uint256 powLimit = UintToArith256(params.powLimit);
    arith_uint256 currentPow = powLimit >> 1;
    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]);
    // Pile up some blocks.
    for (size_t i = 1; i < 100; i++)
    {
        blocks[i] = GetBlockIndex(&blocks[i - 1], params.nPowTargetSpacing, initialBits);
    }

    CBlockHeader blkHeaderDummy;

    // We start getting 2h blocks time. For the first 5 blocks, it doesn't
    // matter as the MTP is not affected. For the next 5 block, MTP difference
    // increases but stays below 12h.
    for (size_t i = 100; i < 110; i++)
    {
        blocks[i] = GetBlockIndex(&blocks[i - 1], 2 * 3600, initialBits);
        BOOST_CHECK_EQUAL(GetNextWorkRequired(&blocks[i], &blkHeaderDummy, params), initialBits);
    }

    // Now we expect the difficulty to decrease.
    blocks[110] = GetBlockIndex(&blocks[109], 2 * 3600, initialBits);
    currentPow.SetCompact(currentPow.GetCompact());
    currentPow += (currentPow >> 2);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&blocks[110], &blkHeaderDummy, params), currentPow.GetCompact());

    // As we continue with 2h blocks, difficulty continue to decrease.
    blocks[111] = GetBlockIndex(&blocks[110], 2 * 3600, currentPow.GetCompact());
    currentPow.SetCompact(currentPow.GetCompact());
    currentPow += (currentPow >> 2);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&blocks[111], &blkHeaderDummy, params), currentPow.GetCompact());

    // We decrease again.
    blocks[112] = GetBlockIndex(&blocks[111], 2 * 3600, currentPow.GetCompact());
    currentPow.SetCompact(currentPow.GetCompact());
    currentPow += (currentPow >> 2);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&blocks[112], &blkHeaderDummy, params), currentPow.GetCompact());

    // We check that we do not go below the minimal difficulty.
    blocks[113] = GetBlockIndex(&blocks[112], 2 * 3600, currentPow.GetCompact());
    currentPow.SetCompact(currentPow.GetCompact());
    currentPow += (currentPow >> 2);
    BOOST_CHECK(powLimit.GetCompact() != currentPow.GetCompact());
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&blocks[113], &blkHeaderDummy, params), powLimit.GetCompact());

    // Once we reached the minimal difficulty, we stick with it.
    blocks[114] = GetBlockIndex(&blocks[113], 2 * 3600, powLimit.GetCompact());
    BOOST_CHECK(powLimit.GetCompact() != currentPow.GetCompact());
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&blocks[114], &blkHeaderDummy, params), powLimit.GetCompact());
}

BOOST_AUTO_TEST_CASE(cash_difficulty_test)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

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

    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 = GetNextCashWorkRequired(&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(GetNextCashWorkRequired(&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(GetNextCashWorkRequired(&blocks[i++], &blkHeaderDummy, params), nBits);
    blocks[i] = GetBlockIndex(&blocks[i - 1], 2 * 600 - 6000, nBits);
    BOOST_CHECK_EQUAL(GetNextCashWorkRequired(&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(GetNextCashWorkRequired(&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(GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&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 = GetNextCashWorkRequired(&blocks[i], &blkHeaderDummy, params);

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

// clang-format off
double TargetFromBits(const uint32_t nBits) { return (nBits & 0xff'ff'ff) * pow(256, (nBits >> 24) - 3); }
// clang-format-on
double GetASERTApproximationError(const CBlockIndex *pindexPrev,
    const uint32_t finalBits,
    const CBlockIndex *pindexAnchorBlock)
{
    const int64_t nHeightDiff = pindexPrev->nHeight - pindexAnchorBlock->nHeight;
    const int64_t nTimeDiff = pindexPrev->GetBlockTime() - pindexAnchorBlock->pprev->GetBlockTime();
    const uint32_t initialBits = pindexAnchorBlock->nBits;

    BOOST_CHECK(nHeightDiff >= 0);
    double dInitialPow = TargetFromBits(initialBits);
    double dFinalPow = TargetFromBits(finalBits);

    double dExponent = double(nTimeDiff - (nHeightDiff + 1) * 600) / double(2 * 24 * 3600);
    double dTarget = dInitialPow * pow(2, dExponent);

    return (dFinalPow - dTarget) / dTarget;
}

BOOST_AUTO_TEST_CASE(asert_difficulty_test)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();

    std::vector<CBlockIndex> blocks(3000 + 2 * 24 * 3600);
    const arith_uint256 powLimit = UintToArith256(params.powLimit);
    arith_uint256 currentPow = powLimit >> 3;
    uint32_t initialBits = currentPow.GetCompact();
    double dMaxErr = 0.0001166792656486;

    // Genesis block, and parent of ASERT anchor block in this test case.
    blocks[0] = CBlockIndex();
    blocks[0].nHeight = 0;
    blocks[0].nTime = 1269211443;
    // The pre-anchor block's nBits should never be used, so we set it to a nonsense value in order to
    // trigger an error if it is ever accessed
    blocks[0].nBits = 0x0dedbeef;

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

    // Block counter.
    size_t i = 1;

    // ASERT anchor block. We give this one a solvetime of 150 seconds to ensure that
    // the solvetime between the pre-anchor and the anchor blocks is actually used.
    blocks[1] = GetBlockIndex(&blocks[0], 150, initialBits);
    // The nBits for the next block should not be equal to the anchor block's nBits
    CBlockHeader blkHeaderDummy;
    uint32_t nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr);
    BOOST_CHECK(nBits != initialBits);

    // If we add another block at 1050 seconds, we should return to the anchor block's nBits
    blocks[i] = GetBlockIndex(&blocks[i - 1], 1050, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(nBits == initialBits);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr);

    currentPow = arith_uint256().SetCompact(nBits);
    // Before we do anything else, check that timestamps *before* the anchor block work fine.
    // Jumping 2 days into the past will give a timestamp before the achnor, and should halve the target
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 - 172800, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    currentPow = arith_uint256().SetCompact(nBits);
    // Because nBits truncates target, we don't end up with exactly 1/2 the target
    BOOST_CHECK(currentPow <= arith_uint256().SetCompact(initialBits) / 2);
    BOOST_CHECK(currentPow >= arith_uint256().SetCompact(initialBits - 1) / 2);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr);

    // Jumping forward 2 days should return the target to the initial value
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 + 172800, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    currentPow = arith_uint256().SetCompact(nBits);
    BOOST_CHECK(nBits == initialBits);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr);

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

    nBits = GetNextASERTWorkRequired(&blocks[i - 1], &blkHeaderDummy, params, &blocks[1]);

    BOOST_CHECK_EQUAL(nBits, initialBits);

    // 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(GetNextASERTWorkRequired(&blocks[i], &blkHeaderDummy, params, &blocks[1]), nBits);
    }

    // If we add a two blocks whose solvetimes together add up to 1200s,
    // then the next block's target should be the same as the one before these blocks
    // (at this point, equal to initialBits).
    blocks[i] = GetBlockIndex(&blocks[i - 1], 300, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    blocks[i] = GetBlockIndex(&blocks[i - 1], 900, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    BOOST_CHECK_EQUAL(nBits, initialBits);
    BOOST_CHECK(nBits != blocks[i - 1].nBits);

    // Same in reverse - this time slower block first, followed by faster block.
    blocks[i] = GetBlockIndex(&blocks[i - 1], 900, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    blocks[i] = GetBlockIndex(&blocks[i - 1], 300, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    BOOST_CHECK_EQUAL(nBits, initialBits);
    BOOST_CHECK(nBits != blocks[i - 1].nBits);

    // Jumping forward 2 days should double the target (halve the difficulty)
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 + 2 * 24 * 3600, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    currentPow = arith_uint256().SetCompact(nBits) / 2;
    BOOST_CHECK_EQUAL(currentPow.GetCompact(), initialBits);

    // Jumping backward 2 days should bring target back to where we started
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 - 2 * 24 * 3600, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    BOOST_CHECK_EQUAL(nBits, initialBits);

    // Jumping backward 2 days should halve the target (double the difficulty)
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 - 2 * 24 * 3600, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    currentPow = arith_uint256().SetCompact(nBits);
    // Because nBits truncates target, we don't end up with exactly 1/2 the target
    BOOST_CHECK(currentPow <= arith_uint256().SetCompact(initialBits) / 2);
    BOOST_CHECK(currentPow >= arith_uint256().SetCompact(initialBits - 1) / 2);

    // And forward again
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 + 2 * 24 * 3600, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    BOOST_CHECK_EQUAL(nBits, initialBits);
    blocks[i] = GetBlockIndex(&blocks[i - 1], 600 + 2 * 24 * 3600, nBits);
    nBits = GetNextASERTWorkRequired(&blocks[i++], &blkHeaderDummy, params, &blocks[1]);
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[1])) < dMaxErr); // absolute
    BOOST_CHECK(fabs(GetASERTApproximationError(&blocks[i - 1], nBits, &blocks[i - 2])) < dMaxErr); // relative
    currentPow = arith_uint256().SetCompact(nBits) / 2;
    BOOST_CHECK_EQUAL(currentPow.GetCompact(), initialBits);

    // Iterate over the entire -2*24*3600..+2*24*3600 range to check that our integer approximation:
    //   1. Should be monotonic
    //   2. Should change target at least once every 8 seconds (worst-case: 15-bit precision on nBits)
    //   3. Should never change target by more than XXXX per 1-second step
    //   4. Never exceeds dMaxError in absolute error vs a double float calculation
    //   5. Has almost exactly the dMax and dMin errors we expect for the formula
    double dMin = 0;
    double dMax = 0;
    double dErr;
    double dRelMin = 0;
    double dRelMax = 0;
    double dRelErr;
    double dMaxStep = 0;
    uint32_t nBitsRingBuffer[8];
    double dStep = 0;
    blocks[i] = GetBlockIndex(&blocks[i - 1], -2 * 24 * 3600 - 30, nBits);
    for (size_t j = 0; j < 4 * 24 * 3600 + 660; j++)
    {
        blocks[i].nTime++;
        nBits = GetNextASERTWorkRequired(&blocks[i], &blkHeaderDummy, params, &blocks[1]);

        if (j > 8)
        {
            // 1: Monotonic
            BOOST_CHECK(arith_uint256().SetCompact(nBits) >= arith_uint256().SetCompact(nBitsRingBuffer[(j - 1) % 8]));
            // 2: Changes at least once every 8 seconds (worst case: nBits = 1d008000 to 1d008001)
            BOOST_CHECK(arith_uint256().SetCompact(nBits) > arith_uint256().SetCompact(nBitsRingBuffer[j % 8]));
            // 3: Check 1-sec step size
            dStep = (TargetFromBits(nBits) - TargetFromBits(nBitsRingBuffer[(j - 1) % 8])) / TargetFromBits(nBits);
            if (dStep > dMaxStep)
                dMaxStep = dStep;
            BOOST_CHECK(dStep < 0.0000314812106363); // from nBits = 1d008000 to 1d008001
        }
        nBitsRingBuffer[j % 8] = nBits;

        // 4 and 5: check error vs double precision float calculation
        dErr = GetASERTApproximationError(&blocks[i], nBits, &blocks[1]);
        dRelErr = GetASERTApproximationError(&blocks[i], nBits, &blocks[i - 1]);
        if (dErr < dMin)
            dMin = dErr;
        if (dErr > dMax)
            dMax = dErr;
        if (dRelErr < dRelMin)
            dRelMin = dRelErr;
        if (dRelErr > dRelMax)
            dRelMax = dRelErr;
        BOOST_CHECK_MESSAGE(
            fabs(dErr) < dMaxErr, strprintf("solveTime: %d\tStep size: %.8f%%\tdErr: %.8f%%\tnBits: %0x\n",
                                      int64_t(blocks[i].nTime) - blocks[i - 1].nTime, dStep * 100, dErr * 100, nBits));
        BOOST_CHECK_MESSAGE(fabs(dRelErr) < dMaxErr,
            strprintf("solveTime: %d\tStep size: %.8f%%\tdRelErr: %.8f%%\tnBits: %0x\n",
                                int64_t(blocks[i].nTime) - blocks[i - 1].nTime, dStep * 100, dRelErr * 100, nBits));
    }
    auto failMsg = strprintf(
        "Min error: %16.14f%%\tMax error: %16.14f%%\tMax step: %16.14f%%\n", dMin * 100, dMax * 100, dMaxStep * 100);
    BOOST_CHECK_MESSAGE(dMin < -0.0001013168981059 && dMin > -0.0001013168981060 && dMax > 0.0001166792656485 &&
                            dMax < 0.0001166792656486,
        failMsg);
    failMsg = strprintf("Min relError: %16.14f%%\tMax relError: %16.14f%%\n", dRelMin * 100, dRelMax * 100);
    BOOST_CHECK_MESSAGE(dRelMin < -0.0001013168981059 && dRelMin > -0.0001013168981060 &&
                            dRelMax > 0.0001166792656485 && dRelMax < 0.0001166792656486,
        failMsg);

    // Difficulty increases as long as we produce fast blocks
    for (size_t j = 0; j < 100; i++, j++)
    {
        uint32_t nextBits;
        arith_uint256 currentTarget;
        currentTarget.SetCompact(nBits);

        blocks[i] = GetBlockIndex(&blocks[i - 1], 500, nBits);
        nextBits = GetNextASERTWorkRequired(&blocks[i], &blkHeaderDummy, params, &blocks[1]);
        arith_uint256 nextTarget;
        nextTarget.SetCompact(nextBits);

        // Make sure that target is decreased
        BOOST_CHECK(nextTarget <= currentTarget);

        nBits = nextBits;
    }
}

std::string StrPrintCalcArgs(const arith_uint256 refTarget,
    const int64_t targetSpacing,
    const int64_t timeDiff,
    const int64_t heightDiff,
    const arith_uint256 expectedTarget,
    const uint32_t expectednBits)
{
    return strprintf("\n"
                     "ref=         %s\n"
                     "spacing=     %d\n"
                     "timeDiff=    %d\n"
                     "heightDiff=  %d\n"
                     "expTarget=   %s\n"
                     "exp nBits=   0x%08x\n",
        refTarget.ToString(), targetSpacing, timeDiff, heightDiff, expectedTarget.ToString(), expectednBits);
}

// Tests of the CalculateASERT function.
BOOST_AUTO_TEST_CASE(calculate_asert_test)
{
    SelectParams(CBaseChainParams::MAIN);
    const Consensus::Params &params = Params().GetConsensus();
    const int64_t nHalfLife = params.nASERTHalfLife;

    const arith_uint256 powLimit = UintToArith256(params.powLimit);
    arith_uint256 initialTarget = powLimit >> 4;
    int64_t height = 0;

    // The CalculateASERT function uses the absolute ASERT formulation
    // and adds +1 to the height difference that it receives.
    // The time difference passed to it must factor in the difference
    // to the *parent* of the reference block.
    // We assume the parent is ideally spaced in time before the reference block.
    static const int64_t parent_time_diff = 600;

    // Steady
    arith_uint256 nextTarget = CalculateASERT(
        initialTarget, params.nPowTargetSpacing, parent_time_diff + 600 /* nTimeDiff */, ++height, powLimit, nHalfLife);
    BOOST_CHECK(nextTarget == initialTarget);

    // A block that arrives in half the expected time
    nextTarget = CalculateASERT(
        initialTarget, params.nPowTargetSpacing, parent_time_diff + 600 + 300, ++height, powLimit, nHalfLife);
    BOOST_CHECK(nextTarget < initialTarget);

    // A block that makes up for the shortfall of the previous one, restores the target to initial
    arith_uint256 prevTarget = nextTarget;
    nextTarget = CalculateASERT(
        initialTarget, params.nPowTargetSpacing, parent_time_diff + 600 + 300 + 900, ++height, powLimit, nHalfLife);
    BOOST_CHECK(nextTarget > prevTarget);
    BOOST_CHECK(nextTarget == initialTarget);

    // Two days ahead of schedule should double the target (halve the difficulty)
    prevTarget = nextTarget;
    nextTarget =
        CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288 * 1200, 288, powLimit, nHalfLife);
    BOOST_CHECK(nextTarget == prevTarget * 2);

    // Two days behind schedule should halve the target (double the difficulty)
    prevTarget = nextTarget;
    nextTarget =
        CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288 * 0, 288, powLimit, nHalfLife);
    BOOST_CHECK(nextTarget == prevTarget / 2);
    BOOST_CHECK(nextTarget == initialTarget);

    // Ramp up from initialTarget to PowLimit - should only take 4 doublings...
    uint32_t powLimit_nBits = powLimit.GetCompact();
    uint32_t next_nBits;
    for (size_t k = 0; k < 3; k++)
    {
        prevTarget = nextTarget;
        nextTarget = CalculateASERT(
            prevTarget, params.nPowTargetSpacing, parent_time_diff + 288 * 1200, 288, powLimit, nHalfLife);
        BOOST_CHECK(nextTarget == prevTarget * 2);
        BOOST_CHECK(nextTarget < powLimit);
        next_nBits = nextTarget.GetCompact();
        BOOST_CHECK(next_nBits != powLimit_nBits);
    }

    prevTarget = nextTarget;
    nextTarget =
        CalculateASERT(prevTarget, params.nPowTargetSpacing, parent_time_diff + 288 * 1200, 288, powLimit, nHalfLife);
    next_nBits = nextTarget.GetCompact();
    BOOST_CHECK(nextTarget == prevTarget * 2);
    BOOST_CHECK(next_nBits == powLimit_nBits);

    // Fast periods now cannot increase target beyond POW limit, even if we try to overflow nextTarget.
    // prevTarget is a uint256, so 256*2 = 512 days would overflow nextTarget unless CalculateASERT
    // correctly detects this error
    nextTarget = CalculateASERT(
        prevTarget, params.nPowTargetSpacing, parent_time_diff + 512 * 144 * 600, 0, powLimit, nHalfLife);
    next_nBits = nextTarget.GetCompact();
    BOOST_CHECK(next_nBits == powLimit_nBits);

    // We also need to watch for underflows on nextTarget. We need to withstand an extra ~446 days worth of blocks.
    // This should bring down a powLimit target to the a minimum target of 1.
    nextTarget = CalculateASERT(powLimit, params.nPowTargetSpacing, 0, 2 * (256 - 33) * 144, powLimit, nHalfLife);
    next_nBits = nextTarget.GetCompact();
    BOOST_CHECK_EQUAL(next_nBits, arith_uint256(1).GetCompact());

    // Define a structure holding parameters to pass to CalculateASERT.
    // We are going to check some expected results  against a vector of
    // possible arguments.
    struct calc_params
    {
        arith_uint256 refTarget;
        int64_t targetSpacing;
        int64_t timeDiff;
        int64_t heightDiff;
        arith_uint256 expectedTarget;
        uint32_t expectednBits;
    };

    // Define some named input argument values
    const arith_uint256 SINGLE_300_TARGET{"00000000ffb1ffffffffffffffffffffffffffffffffffffffffffffffffffff"};
    const arith_uint256 FUNNY_REF_TARGET{"000000008000000000000000000fffffffffffffffffffffffffffffffffffff"};

    // Define our expected input and output values.
    // The timeDiff entries exclude the `parent_time_diff` - this is
    // added in the call to CalculateASERT in the test loop.
    const std::vector<calc_params> calculate_args = {

        /* refTarget, targetSpacing, timeDiff, heightDiff, expectedTarget, expectednBits */

        {powLimit, 600, 0, 2 * 144, powLimit >> 1, 0x1c7fffff}, {powLimit, 600, 0, 4 * 144, powLimit >> 2, 0x1c3fffff},
        {powLimit >> 1, 600, 0, 2 * 144, powLimit >> 2, 0x1c3fffff},
        {powLimit >> 2, 600, 0, 2 * 144, powLimit >> 3, 0x1c1fffff},
        {powLimit >> 3, 600, 0, 2 * 144, powLimit >> 4, 0x1c0fffff},
        {powLimit, 600, 0, 2 * (256 - 34) * 144, 3, 0x01030000},
        {powLimit, 600, 0, 2 * (256 - 34) * 144 + 119, 3, 0x01030000},
        {powLimit, 600, 0, 2 * (256 - 34) * 144 + 120, 2, 0x01020000},
        {powLimit, 600, 0, 2 * (256 - 33) * 144 - 1, 2, 0x01020000},
        {powLimit, 600, 0, 2 * (256 - 33) * 144, 1, 0x01010000}, // 1 bit less since we do not need to shift to 0
        {powLimit, 600, 0, 2 * (256 - 32) * 144, 1, 0x01010000}, // more will not decrease below 1
        {1, 600, 0, 2 * (256 - 32) * 144, 1, 0x01010000},
        {powLimit, 600, 2 * (512 - 32) * 144, 0, powLimit, powLimit_nBits},
        {1, 600, (512 - 64) * 144 * 600, 0, powLimit, powLimit_nBits},
        {powLimit, 600, 300, 1, SINGLE_300_TARGET, 0x1d00ffb1}, // clamps to powLimit
        // confuses any attempt to detect overflow by inspecting result
        {FUNNY_REF_TARGET, 600, 600 * 2 * 33 * 144, 0, powLimit, powLimit_nBits},
        {1, 600, 600 * 2 * 256 * 144, 0, powLimit, powLimit_nBits}, // overflow to exactly 2^256
        // just under powlimit (not clamped) yet over powlimit_nbits
        {1, 600, 600 * 2 * 224 * 144 - 1, 0, arith_uint256(0xffff8) << 204, powLimit_nBits},
    };

    for (auto &v : calculate_args)
    {
        nextTarget = CalculateASERT(
            v.refTarget, v.targetSpacing, parent_time_diff + v.timeDiff, v.heightDiff, powLimit, nHalfLife);
        next_nBits = nextTarget.GetCompact();
        const auto failMsg = StrPrintCalcArgs(v.refTarget, v.targetSpacing, parent_time_diff + v.timeDiff, v.heightDiff,
                                 v.expectedTarget, v.expectednBits) +
                             strprintf("nextTarget=  %s\nnext nBits=  0x%08x\n", nextTarget.ToString(), next_nBits);
        BOOST_CHECK_MESSAGE(nextTarget == v.expectedTarget && next_nBits == v.expectednBits, failMsg);
    }
}

/**
 * Test transition of cw144 to ASERT algorithm, which involves the selection
 * of an anchor block.
 */
BOOST_AUTO_TEST_CASE(asert_activation_anchor_test)
{
    // Make a custom chain params based on mainnet, activating the cw144 DAA
    // at a lower height than usual, so we don't need to waste time making a
    // 504000-long chain.
    SelectParams(CBaseChainParams::MAIN);
    Consensus::Params params(Params().GetConsensus());
    params.daaHeight = 2016;
    const int64_t activationTime = params.nov2020ActivationTime;
    CBlockHeader blkHeaderDummy;

    // an arbitrary compact target for our chain (based on BCH chain ~ Aug 10 2020).
    uint32_t initialBits = 0x1802a842;

    // Block store for anonymous blocks; needs to be big enough to fit all generated
    // blocks in this test.
    std::vector<CBlockIndex> blocks(10000);
    int bidx = 1;

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

    // Pile up a random number of blocks to establish some history of random height.
    // cw144 DAA requires us to have height at least 2016, dunno why that much.
    for (int i = 1; i < 2000 + int(InsecureRandRange(1000)); i++)
    {
        blocks[bidx] = GetBlockIndex(&blocks[bidx - 1], 600, initialBits);
        bidx++;
        BOOST_REQUIRE(bidx < int(blocks.size()));
    }

    // Start making blocks prior to activation. First, make a block about 1 day before activation.
    // Then put down 145 more blocks with 500 second solvetime each, such that
    // the MTP on the final block is 1 second short of activationTime.
    {
        blocks[bidx] = GetBlockIndex(&blocks[bidx - 1], 600, initialBits);
        blocks[bidx].nTime = activationTime - 140 * 500 - 1;
        bidx++;
    }
    for (int i = 0; i < 145; i++)
    {
        BOOST_REQUIRE(bidx < int(blocks.size()));
        blocks[bidx] = GetBlockIndex(&blocks[bidx - 1], 500, initialBits);
        bidx++;
    }
    CBlockIndex *pindexPreActivation = &blocks[bidx - 1];
    BOOST_CHECK_EQUAL(pindexPreActivation->nTime, activationTime + 5 * 500 - 1);
    BOOST_CHECK_EQUAL(pindexPreActivation->GetMedianTimePast(), activationTime - 1);
    BOOST_CHECK(IsDAAEnabled(params, pindexPreActivation));

    // If we consult DAA, then it uses cw144 which returns a significantly lower target because
    // we have been mining too fast by a ratio 600/500 for a whole day.
    BOOST_CHECK(!IsNov2020Enabled(params, pindexPreActivation));
    BOOST_CHECK_EQUAL(GetNextWorkRequired(pindexPreActivation, &blkHeaderDummy, params), 0x180236e1);

    // ASERT has never run yet, so cache is unpopulated.
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), nullptr);

    /**
     * Now we'll try adding on blocks to activate ASERT. The activation block
     * is going to be our anchor block. We will make several distinct anchor blocks.
     */

    // Create an activating block with expected solvetime, taking the cw144 difficulty we just
    // saw. Since solvetime is expected the next target is unchanged.
    CBlockIndex indexActivation0 = GetBlockIndex(pindexPreActivation, 600, 0x180236e1);
    BOOST_CHECK(IsNov2020Enabled(params, &indexActivation0));
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation0, &blkHeaderDummy, params), 0x180236e1);
    // second call will have used anchor cache, shouldn't change anything
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation0);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation0, &blkHeaderDummy, params), 0x180236e1);

    // Now we'll generate some more activations/anchors, using unique targets for each one
    // (if the algo gets confused between different anchors, we will know).

    // Create an activating block with 0 solvetime, which will drop target by ~415/416.
    CBlockIndex indexActivation1 = GetBlockIndex(pindexPreActivation, 0, 0x18023456);
    BOOST_CHECK(IsNov2020Enabled(params, &indexActivation1));
    // cache will be stale here, and we should get the right result regardless:
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation1, &blkHeaderDummy, params), 0x180232fd);
    // second call will have used anchor cache, shouldn't change anything
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation1);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation1, &blkHeaderDummy, params), 0x180232fd);
    // for good measure, try again with wiped cache
    ResetASERTAnchorBlockCache();
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation1, &blkHeaderDummy, params), 0x180232fd);
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation1);

    // Try activation with expected solvetime, which will keep target the same.
    uint32_t anchorBits2 = 0x180210fe;
    CBlockIndex indexActivation2 = GetBlockIndex(pindexPreActivation, 600, anchorBits2);
    BOOST_CHECK(IsNov2020Enabled(params, &indexActivation2));
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation2, &blkHeaderDummy, params), anchorBits2);
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation2);

    // Try a three-month solvetime which will cause us to hit powLimit.
    uint32_t anchorBits3 = 0x18034567;
    CBlockIndex indexActivation3 = GetBlockIndex(pindexPreActivation, 86400 * 90, anchorBits3);
    BOOST_CHECK(IsNov2020Enabled(params, &indexActivation2));
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation3, &blkHeaderDummy, params), 0x1d00ffff);
    // If the next block jumps back in time, we get back our original difficulty level.
    CBlockIndex indexActivation3_return = GetBlockIndex(&indexActivation3, -86400 * 90 + 2 * 600, anchorBits3);
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation3_return, &blkHeaderDummy, params), anchorBits3);
    // Retry for cache
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation3_return, &blkHeaderDummy, params), anchorBits3);
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation3);

    // Make an activation with MTP == activation exactly. This is a backwards timestamp jump
    // so the resulting target is 1.2% lower.
    CBlockIndex indexActivation4 = GetBlockIndex(pindexPreActivation, 0, 0x18011111);
    indexActivation4.nTime = activationTime;
    BOOST_CHECK_EQUAL(indexActivation4.GetMedianTimePast(), activationTime);
    BOOST_CHECK(IsNov2020Enabled(params, &indexActivation4));
    BOOST_CHECK_EQUAL(GetNextWorkRequired(&indexActivation4, &blkHeaderDummy, params), 0x18010db3);
    BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation4);

    // Finally create a random chain on top of our second activation, using ASERT targets all the way.
    // Erase cache so that this will do a fresh search for anchor at every step (fortauntely this is
    // not too slow, due to the skiplist traversal)
    CBlockIndex *pindexChain2 = &indexActivation2;
    for (int i = 1; i < 1000; i++)
    {
        BOOST_REQUIRE(bidx < int(blocks.size()));
        ResetASERTAnchorBlockCache();
        uint32_t nextBits = GetNextWorkRequired(pindexChain2, &blkHeaderDummy, params);
        BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation2);
        blocks[bidx] = GetBlockIndex(pindexChain2, InsecureRandRange(1200), nextBits);
        pindexChain2 = &blocks[bidx++];
    }
    // Scan back down to make sure all targets are same when we keep cached anchor.
    for (CBlockIndex *pindex = pindexChain2; pindex != &indexActivation2; pindex = pindex->pprev)
    {
        uint32_t nextBits = GetNextWorkRequired(pindex->pprev, &blkHeaderDummy, params);
        BOOST_CHECK_EQUAL(nextBits, pindex->nBits);
        BOOST_CHECK_EQUAL(GetASERTAnchorBlockCache(), &indexActivation2);
    }
}

BOOST_AUTO_TEST_SUITE_END()


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

#include "theia/sfm/bundle_adjustment/bundle_adjust_two_views.h"

#include <ceres/ceres.h>
#include <Eigen/Core>
#include <vector>

#include "theia/matching/feature_correspondence.h"
#include "theia/sfm/bundle_adjustment/angular_epipolar_error.h"
#include "theia/sfm/bundle_adjustment/bundle_adjustment.h"
#include "theia/sfm/bundle_adjustment/unit_norm_three_vector_parameterization.h"
#include "theia/sfm/camera/camera.h"
#include "theia/sfm/camera/create_reprojection_error_cost_function.h"
#include "theia/sfm/camera_intrinsics_prior.h"
#include "theia/sfm/triangulation/triangulation.h"
#include "theia/sfm/twoview_info.h"
#include "theia/sfm/types.h"
#include "theia/util/timer.h"

namespace theia {

namespace {

void SetSolverOptions(const BundleAdjustmentOptions& options,
                      ceres::Solver::Options* solver_options) {
  CHECK_NOTNULL(solver_options);
  solver_options->linear_solver_type = ceres::DENSE_SCHUR;
  solver_options->visibility_clustering_type = ceres::CANONICAL_VIEWS;
  solver_options->logging_type = ceres::SILENT;
  solver_options->num_threads = options.num_threads;
  solver_options->max_num_iterations = 200;
  // Solver options takes ownership of the ordering so that we can order the BA
  // problem by points and cameras.
  solver_options->linear_solver_ordering.reset(
      new ceres::ParameterBlockOrdering);
}

// The only intrinsic parameter we want to optimize is the focal length, so we
// keep all intrinsics constant except for focal length by default.
void AddCameraParametersToProblem(const bool constant_extrinsic_parameters,
                                  const bool constant_intrinsic_parameters,
                                  Camera* camera,
                                  ceres::Problem* problem) {
  double* camera_extrinsics = camera->mutable_extrinsics();
  double* camera_intrinsics = camera->mutable_intrinsics();
  const int num_intrinsics = camera->CameraIntrinsics()->NumParameters();

  // Add extrinsics to problem
  problem->AddParameterBlock(camera_extrinsics, Camera::kExtrinsicsSize);
  if (constant_extrinsic_parameters) {
    problem->SetParameterBlockConstant(camera_extrinsics);
  }

  // Keep the intrinsics constant if desired.
  if (constant_intrinsic_parameters) {
    problem->AddParameterBlock(camera_intrinsics, num_intrinsics);
    problem->SetParameterBlockConstant(camera_intrinsics);
  } else {
    // NOTE: We start at index 1 because the focal length is considered
    // variable.
    std::vector<int> constant_intrinsics(num_intrinsics - 1);
    std::iota(constant_intrinsics.begin(),
              constant_intrinsics.end(),
              1);

    ceres::SubsetParameterization* subset_parameterization =
        new ceres::SubsetParameterization(num_intrinsics,
                                          constant_intrinsics);
    problem->AddParameterBlock(camera_intrinsics,
                               num_intrinsics,
                               subset_parameterization);
  }
}

}  // namespace

// Triangulates all 3d points and performs standard bundle adjustment on the
// points and cameras.
BundleAdjustmentSummary BundleAdjustTwoViews(
    const TwoViewBundleAdjustmentOptions& options,
    const std::vector<FeatureCorrespondence>& correspondences,
    Camera* camera1,
    Camera* camera2,
    std::vector<Eigen::Vector4d>* points3d) {
  CHECK_NOTNULL(camera1);
  CHECK_NOTNULL(camera2);
  CHECK_NOTNULL(points3d);
  CHECK_EQ(points3d->size(), correspondences.size());

  BundleAdjustmentSummary summary;

  // Start setup timer.
  Timer timer;

  // Set problem options.
  ceres::Problem::Options problem_options;
  ceres::Problem problem(problem_options);

  // Set solver options.
  ceres::Solver::Options solver_options;
  SetSolverOptions(options.ba_options, &solver_options);
  ceres::ParameterBlockOrdering* parameter_ordering =
      solver_options.linear_solver_ordering.get();

  // Add the two cameras as parameter blocks.
  AddCameraParametersToProblem(true,
                               options.constant_camera1_intrinsics,
                               camera1,
                               &problem);
  AddCameraParametersToProblem(false,
                               options.constant_camera2_intrinsics,
                               camera2,
                               &problem);
  parameter_ordering->AddElementToGroup(camera1->mutable_extrinsics(), 2);
  parameter_ordering->AddElementToGroup(camera1->mutable_intrinsics(), 1);

  parameter_ordering->AddElementToGroup(camera2->mutable_extrinsics(), 2);
  parameter_ordering->AddElementToGroup(camera2->mutable_intrinsics(), 1);

  // Add triangulated points to the problem.
  for (int i = 0; i < points3d->size(); i++) {
    problem.AddResidualBlock(CreateReprojectionErrorCostFunction(
                                 camera1->GetCameraIntrinsicsModelType(),
                                 correspondences[i].feature1),
                             NULL,
                             camera1->mutable_extrinsics(),
                             camera1->mutable_intrinsics(),
                             points3d->at(i).data());
    problem.AddResidualBlock(CreateReprojectionErrorCostFunction(
                                 camera2->GetCameraIntrinsicsModelType(),
                                 correspondences[i].feature2),
                             NULL,
                             camera2->mutable_extrinsics(),
                             camera2->mutable_intrinsics(),
                             points3d->at(i).data());

    parameter_ordering->AddElementToGroup(points3d->at(i).data(), 0);
  }

  // End setup time.
  summary.setup_time_in_seconds = timer.ElapsedTimeInSeconds();

  // Solve the problem.
  ceres::Solver::Summary solver_summary;
  ceres::Solve(solver_options, &problem, &solver_summary);
  LOG_IF(INFO, options.ba_options.verbose) << solver_summary.FullReport();

  // Set the BundleAdjustmentSummary.
  summary.solve_time_in_seconds = solver_summary.total_time_in_seconds;
  summary.initial_cost = solver_summary.initial_cost;
  summary.final_cost = solver_summary.final_cost;

  // This only indicates whether the optimization was successfully run and makes
  // no guarantees on the quality or convergence.
  summary.success = solver_summary.termination_type != ceres::FAILURE;

  return summary;
}

BundleAdjustmentSummary BundleAdjustTwoViewsAngular(
    const BundleAdjustmentOptions& options,
    const std::vector<FeatureCorrespondence>& correspondences,
    TwoViewInfo* info) {
  CHECK_NOTNULL(info);

  BundleAdjustmentSummary summary;

  // Start setup timer.
  Timer timer;

  // Set problem options.
  ceres::Problem::Options problem_options;

  ceres::Problem problem(problem_options);

  // Set solver options.
  ceres::Solver::Options solver_options;
  SetSolverOptions(options, &solver_options);
  // Allow Ceres to determine the ordering.
  solver_options.linear_solver_ordering.reset();

  // Add the relative rotation as a parameter block.
  const int kParameterBlockSize = 3;
  problem.AddParameterBlock(info->rotation_2.data(), kParameterBlockSize);
  // Add the position as a parameter block, ensuring that the norm is 1.
  ceres::LocalParameterization* position_parameterization =
      new ceres::AutoDiffLocalParameterization<
          UnitNormThreeVectorParameterization, 3, 3>;
  problem.AddParameterBlock(info->position_2.data(),
                            kParameterBlockSize,
                            position_parameterization);

  // Add all the epipolar constraints from feature matches.
  for (const FeatureCorrespondence& match : correspondences) {
    problem.AddResidualBlock(
        AngularEpipolarError::Create(match.feature1, match.feature2),
        NULL,
        info->rotation_2.data(),
        info->position_2.data());
  }

  // End setup time.
  summary.setup_time_in_seconds = timer.ElapsedTimeInSeconds();

  // Solve the problem.
  ceres::Solver::Summary solver_summary;
  ceres::Solve(solver_options, &problem, &solver_summary);
  LOG_IF(INFO, options.verbose) << solver_summary.FullReport();

  // Set the BundleAdjustmentSummary.
  summary.solve_time_in_seconds = solver_summary.total_time_in_seconds;
  summary.initial_cost = solver_summary.initial_cost;
  summary.final_cost = solver_summary.final_cost;

  // This only indicates whether the optimization was successfully run and makes
  // no guarantees on the quality or convergence.
  summary.success = solver_summary.termination_type != ceres::FAILURE;
  return summary;
}

}  // namespace theia


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

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.

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

#ifndef BOOST_GEOMETRY_GEOMETRY_POLICIES_RELATE_DIRECTION_HPP
#define BOOST_GEOMETRY_GEOMETRY_POLICIES_RELATE_DIRECTION_HPP

#include <cstddef>
#include <string>

#include <boost/concept_check.hpp>

#include <boost/geometry/arithmetic/determinant.hpp>
#include <boost/geometry/strategies/side_info.hpp>

#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/select_calculation_type.hpp>
#include <boost/geometry/util/select_most_precise.hpp>

namespace boost { namespace geometry
{

namespace policies { namespace relate
{

struct direction_type
{
    // NOTE: "char" will be replaced by enum in future version

    inline direction_type(side_info const& s, char h,
                int ha, int hb,
                int da = 0, int db = 0,
                bool op = false)
        : how(h)
        , opposite(op)
        , how_a(ha)
        , how_b(hb)
        , dir_a(da)
        , dir_b(db)
        , sides(s)
    {
        arrival[0] = ha;
        arrival[1] = hb;
    }

    inline direction_type(char h, bool op, int ha = 0, int hb = 0)
        : how(h)
        , opposite(op)
        , how_a(ha)
        , how_b(hb)
        , dir_a(0)
        , dir_b(0)
    {
        arrival[0] = ha;
        arrival[1] = hb;
    }

    // TODO: replace this
    // NOTE: "char" will be replaced by enum in future version
    // "How" is the intersection formed?
    char how;

    // Is it opposite (for collinear/equal cases)
    bool opposite;

    // Information on how A arrives at intersection, how B arrives at intersection
    // 1: arrives at intersection
    // -1: starts from intersection
    int how_a;
    int how_b;

    // Direction: how is A positioned from B
    // 1: points left, seen from IP
    // -1: points right, seen from IP
    // In case of intersection: B's TO direction
    // In case that B's TO direction is at A: B's from direction
    // In collinear cases: it is 0
    int dir_a; // Direction of A-s TO from IP
    int dir_b; // Direction of B-s TO from IP

    // New information
    side_info sides;
    // THIS IS EQUAL TO arrival_a, arrival_b - they probably can go now we have robust fractions
    int arrival[2]; // 1=arrival, -1=departure, 0=neutral; == how_a//how_b

    // About arrival[0] (== arrival of a2 w.r.t. b) for COLLINEAR cases
    // Arrival  1: a1--------->a2         (a arrives within b)
    //                      b1----->b2

    // Arrival  1: (a in b)
    //

    // Arrival -1:      a1--------->a2     (a does not arrive within b)
    //             b1----->b2

    // Arrival -1: (b in a)               a_1-------------a_2
    //                                         b_1---b_2

    // Arrival  0:                        a1------->a2  (a arrives at TO-border of b)
    //                                        b1--->b2

};

struct segments_direction
{
    typedef direction_type return_type;

    template
    <
        typename Segment1,
        typename Segment2,
        typename SegmentIntersectionInfo
    >
    static inline return_type segments_crosses(side_info const& sides,
                    SegmentIntersectionInfo const& ,
                    Segment1 const& , Segment2 const& )
    {
        bool const ra0 = sides.get<0,0>() == 0;
        bool const ra1 = sides.get<0,1>() == 0;
        bool const rb0 = sides.get<1,0>() == 0;
        bool const rb1 = sides.get<1,1>() == 0;

        return
            // opposite and same starting point (FROM)
            ra0 && rb0 ? calculate_side<1>(sides, 'f', -1, -1)

            // opposite and point to each other (TO)
            : ra1 && rb1 ? calculate_side<0>(sides, 't', 1, 1)

            // not opposite, forming an angle, first a then b,
            // directed either both left, or both right
            // Check side of B2 from A. This is not calculated before
            : ra1 && rb0 ? angle<1>(sides, 'a', 1, -1)

            // not opposite, forming a angle, first b then a,
            // directed either both left, or both right
            : ra0 && rb1 ? angle<0>(sides, 'a', -1, 1)

            // b starts from interior of a
            : rb0 ? starts_from_middle(sides, 'B', 0, -1)

            // a starts from interior of b (#39)
            : ra0 ? starts_from_middle(sides, 'A', -1, 0)

            // b ends at interior of a, calculate direction of A from IP
            : rb1 ? b_ends_at_middle(sides)

            // a ends at interior of b
            : ra1 ? a_ends_at_middle(sides)

            // normal intersection
            : calculate_side<1>(sides, 'i', -1, -1)
            ;
    }

    template <typename Ratio>
    static inline int arrival_value(Ratio const& r_from, Ratio const& r_to)
    {
        //     a1--------->a2
        // b1----->b2
        // a departs: -1

        // a1--------->a2
        //         b1----->b2
        // a arrives: 1

        // a1--------->a2
        //     b1----->b2
        // both arrive there -> r-to = 1/1, or 0/1 (on_segment)

        // First check the TO (for arrival), then FROM (for departure)
        return r_to.in_segment() ? 1
            : r_to.on_segment() ? 0
            : r_from.on_segment() ? -1
            : -1
            ;
    }

    template <typename Ratio>
    static inline void analyze(Ratio const& r,
        int& in_segment_count,
        int& on_end_count,
        int& outside_segment_count)
    {
        if (r.on_end())
        {
            on_end_count++;
        }
        else if (r.in_segment())
        {
            in_segment_count++;
        }
        else
        {
            outside_segment_count++;
        }
    }

    template <typename Segment1, typename Segment2, typename Ratio>
    static inline return_type segments_collinear(
        Segment1 const& , Segment2 const&,
        Ratio const& ra_from_wrt_b, Ratio const& ra_to_wrt_b,
        Ratio const& rb_from_wrt_a, Ratio const& rb_to_wrt_a)
    {
        // If segments are opposite, the ratio of the FROM w.r.t. the other
        // is larger than the ratio of the TO w.r.t. the other
        bool const opposite = ra_to_wrt_b < ra_from_wrt_b;

        return_type r('c', opposite);

        // IMPORTANT: the order of conditions is different as in intersection_points.hpp
        // We assign A in 0 and B in 1
        r.arrival[0] = arrival_value(ra_from_wrt_b, ra_to_wrt_b);
        r.arrival[1] = arrival_value(rb_from_wrt_a, rb_to_wrt_a);

        // Analyse them
        int a_in_segment_count = 0;
        int a_on_end_count = 0;
        int a_outside_segment_count = 0;
        int b_in_segment_count = 0;
        int b_on_end_count = 0;
        int b_outside_segment_count = 0;
        analyze(ra_from_wrt_b,
            a_in_segment_count, a_on_end_count, a_outside_segment_count);
        analyze(ra_to_wrt_b,
            a_in_segment_count, a_on_end_count, a_outside_segment_count);
        analyze(rb_from_wrt_a,
            b_in_segment_count, b_on_end_count, b_outside_segment_count);
        analyze(rb_to_wrt_a,
            b_in_segment_count, b_on_end_count, b_outside_segment_count);

        if (a_on_end_count == 1
            && b_on_end_count == 1
            && a_outside_segment_count == 1
            && b_outside_segment_count == 1)
        {
            // This is a collinear touch
            // -------->             A (or B)
            //         <----------   B (or A)
            // We adapt the "how"
            // TODO: how was to be refactored anyway,
            if (! opposite)
            {
                r.how = 'a';
            }
            else
            {
                r.how = r.arrival[0] == 0 ? 't' : 'f';
            }
        }
        else if (a_on_end_count == 2
                 && b_on_end_count == 2)
        {
            r.how = 'e';
        }

        return r;
    }

    template <typename Segment>
    static inline return_type degenerate(Segment const& , bool)
    {
        return return_type('0', false);
    }

    template <typename Segment, typename Ratio>
    static inline return_type one_degenerate(Segment const& ,
            Ratio const& ,
            bool)
    {
        // To be decided
        return return_type('0', false);
    }

    static inline return_type disjoint()
    {
        return return_type('d', false);
    }

    static inline return_type error(std::string const&)
    {
        // Return "E" to denote error
        // This will throw an error in get_turn_info
        // TODO: change to enum or similar
        return return_type('E', false);
    }

private :

    template <std::size_t I>
    static inline return_type calculate_side(side_info const& sides,
                char how, int how_a, int how_b)
    {
        int const dir = sides.get<1, I>() == 1 ? 1 : -1;
        return return_type(sides, how, how_a, how_b, -dir, dir);
    }

    template <std::size_t I>
    static inline return_type angle(side_info const& sides,
                char how, int how_a, int how_b)
    {
        int const dir = sides.get<1, I>() == 1 ? 1 : -1;
        return return_type(sides, how, how_a, how_b, dir, dir);
    }

    static inline return_type starts_from_middle(side_info const& sides,
                char which,
                int how_a, int how_b)
    {
        // Calculate ARROW of b segment w.r.t. s1
        int dir = sides.get<1, 1>() == 1 ? 1 : -1;

        // From other perspective, then reverse
        bool const is_a = which == 'A';
        if (is_a)
        {
            dir = -dir;
        }

        return return_type(sides, 's',
            how_a,
            how_b,
            is_a ? dir : -dir,
            ! is_a ? dir : -dir);
    }

    // To be harmonized
    static inline return_type a_ends_at_middle(side_info const& sides)
    {
        // Ending at the middle, one ARRIVES, the other one is NEUTRAL
        // (because it both "arrives"  and "departs" there)
        int const dir = sides.get<1, 1>() == 1 ? 1 : -1;
        return return_type(sides, 'm', 1, 0, dir, dir);
    }

    static inline return_type b_ends_at_middle(side_info const& sides)
    {
        int const dir = sides.get<0, 1>() == 1 ? 1 : -1;
        return return_type(sides, 'm', 0, 1, dir, dir);
    }

};

}} // namespace policies::relate

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_GEOMETRY_POLICIES_RELATE_DIRECTION_HPP


// Copyright (c) 2021 CNES
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "pyinterp/geohash/int64.hpp"

#include <pybind11/eigen.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

#include <Eigen/Core>

#include "pyinterp/geodetic/box.hpp"
#include "pyinterp/geodetic/point.hpp"

namespace py = pybind11;
namespace geohash = pyinterp::geohash;
namespace geodetic = pyinterp::geodetic;

// Checking the value defining the precision of a geohash.
static inline auto check_range(uint32_t precision) -> void {
  if (precision < 1 || precision > 64) {
    throw std::invalid_argument("precision must be within [1, 64]");
  }
}

void init_geohash_int64(py::module& m) {
  m.def(
       "error",
       [](const uint32_t& precision) -> py::tuple {
         check_range(precision);
         auto lon_lat_err = geohash::int64::error_with_precision(precision);
         return py::make_tuple(std::get<1>(lon_lat_err),
                               std::get<0>(lon_lat_err));
       },
       py::arg("precision"),
       R"__doc__(
Returns the accuracy in longitude/latitude and degrees for the number of bits
used to encode the GeoHash.

Args:
    precision (int): Number of bits used to encode the geohash code.
Returns:
    tuple: Accuracy in longitude/latitude and degrees.
Raises:
    ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "encode",
          [](const geodetic::Point& point, uint32_t precision) -> uint64_t {
            check_range(precision);
            return geohash::int64::encode(point, precision);
          },
          py::arg("point"), py::arg("precision") = 64,
          R"__doc__(
Encode a point into geohash code with the given precision.

Args:
  point (pyinterp.geodetic.Point) Point to encode.
  precision (int, optional) Number of bits used to encode the geohash code.
Returns:
  int: geohash code.
Raises:
  ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "encode",
          [](const Eigen::Ref<const Eigen::VectorXd>& lon,
             const Eigen::Ref<const Eigen::VectorXd>& lat,
             const uint32_t precision) -> pyinterp::Vector<uint64_t> {
            check_range(precision);
            return geohash::int64::encode(lon, lat, precision);
          },
          py::arg("lon"), py::arg("lat"), py::arg("precision") = 64,
          R"__doc__(
Encode coordinates into geohash with the given precision.

Args:
  lon (numpy.ndarray) Longitudes in degrees.
  lat (numpy.ndarray) Latitudes in degrees.
  precision (int, optional) Number of bits used to encode the geohash code.
Returns:
  numpy.ndarray: geohash codes.
Raises:
  ValueError: If the given precision is not within [1, 64].
  ValueError: If the lon and lat vectors have different sizes.
)__doc__")
      .def(
          "decode",
          [](const uint64_t hash, const uint32_t precision,
             const bool round) -> geodetic::Point {
            check_range(precision);
            return geohash::int64::decode(hash, precision, round);
          },
          py::arg("hash"), py::arg("precision") = 64, py::arg("round") = false,
          R"__doc__(
Decode a hash into a geographic point with the given precision.

Args:
  hash (int): Geohash.
  precision (int, optional): Required accuracy.
  round (optional, bool): If true, the coordinates of the point will be
    rounded to the accuracy defined by the GeoHash.
Returns:
  pyinterp.geodetic.Point: decoded geographic point.
Raises:
    ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "decode",
          [](const Eigen::Ref<const pyinterp::Vector<uint64_t>>& hash,
             const uint32_t precision,
             const bool round) -> std::tuple<Eigen::VectorXd, Eigen::VectorXd> {
            check_range(precision);
            return geohash::int64::decode(hash, precision, round);
          },
          py::arg("hash"), py::arg("precision") = 64, py::arg("round") = false,
          R"__doc__(
Decode hash into a geographic points with the given precision.

Args:
  hash (numpy.ndarray): Geohash.
  precision (int, optional): Required accuracy.
  round (optional, bool): If true, the coordinates of the point will be
    rounded to the accuracy defined by the GeoHash."
Returns:
  tuple: longitudes/latitudes of the decoded points.
Raises:
    ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "bounding_box",
          [](const uint64_t hash, const uint32_t precision) -> geodetic::Box {
            check_range(precision);
            return geohash::int64::bounding_box(hash, precision);
          },
          py::arg("hash"), py::arg("precision") = 64,
          R"__doc__(
Returns the bounding box encoded by the geohash with the specified precision.

Args:
  hash (int): Geohash.
  precision (int, optional): Required accuracy.
Returns:
  pyinterp.geodetic.Box: Bounding box.
Raises:
    ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "bounding_boxes",
          [](const std::optional<geodetic::Box>& box,
             const uint32_t precision) -> pyinterp::Vector<uint64_t> {
            check_range(precision);
            return geohash::int64::bounding_boxes(box, precision);
          },
          py::arg("box") = py::none(), py::arg("precision") = 5,
          R"__doc__(
Returns all geohash codes contained in the defined bounding box.

Args:
  box (pyinterp.geohash.Box, optional): Bounding box.
  precision (int, optional): Required accuracy.
Returns:
  numpy.ndarray: Geohash codes.
Raises:
    ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "neighbors",
          [](const uint64_t hash,
             const uint32_t precision) -> Eigen::Matrix<uint64_t, 8, 1> {
            check_range(precision);
            return geohash::int64::neighbors(hash, precision);
          },
          py::arg("hash"), py::arg("precision") = 64,
          R"__doc__(
Returns all neighbors hash clockwise from north around northwest
at the given precision.

Args:
  hash (int): Geohash code.
  precision (int, optional): Required accuracy.
Returns:
  numpy.ndarray: Geohash codes.
Raises:
  ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def(
          "grid_properties",
          [](const geodetic::Box& box,
             const uint32_t precision) -> std::tuple<uint64_t, size_t, size_t> {
            check_range(precision);
            return geohash::int64::grid_properties(box, precision);
          },
          py::arg("box") = py::none(), py::arg("precision") = 64,
          R"__doc__(
Returns the property of the grid covering the given bounding box.

Args:
  box (pyinterp.geodetic.Box, optional): Bounding box.
  precision (int, optional): Required accuracy.
Returns:
  tuple:: geohash of the minimum corner point, number of boxes in longitudes
    and latitudes.
Raises:
  ValueError: If the given precision is not within [1, 64].
)__doc__")
      .def("where", &geohash::int64::where, py::arg("hash"),
           R"__doc__(
Returns the start and end indexes of the different GeoHash boxes.

Args:
  hash (numpy.ndarray): Geohash codes.
Returns:
  dict: dictionary between successive identical geohash codes and start and
    end indexes in the table provided as input.
)__doc__");
}


/**
 * \file FeedbackDelayNetworkFilter.hxx
 */

#ifndef ATK_DELAY_FEEDBACKDELAYNETWORKFILTER_HXX
#define ATK_DELAY_FEEDBACKDELAYNETWORKFILTER_HXX

#include <ATK/Delay/FeedbackDelayNetworkFilter.h>
#include <ATK/Core/Utilities.h>

#include <Eigen/Dense>

#include <complex>

namespace ATK
{
  template<typename Mixture>
  class FeedbackDelayNetworkFilter<Mixture>::HFDN_Impl: public Mixture::MixtureImpl
  {
  public:
    using Vector =  typename Mixture::MixtureImpl::Vector;
    using Matrix =  typename Mixture::MixtureImpl::Matrix;

    std::vector<Vector, boost::alignment::aligned_allocator<Vector, 32>> delay_line;
    std::vector<Vector, boost::alignment::aligned_allocator<Vector, 32>> processed_input;
    int64_t index{0};
    Vector ingain = Vector::Zero();
    Vector outgain = Vector::Zero();
    Vector feedback = Vector::Zero();

    explicit HFDN_Impl(gsl::index max_delay)
      :processed_input(max_delay, Vector::Zero())
    {
    }
  };

  template<typename Mixture>
  FeedbackDelayNetworkFilter<Mixture>::FeedbackDelayNetworkFilter(gsl::index max_delay)
    :Parent(1, 2), impl(std::make_unique<HFDN_Impl>(max_delay)), max_delay(max_delay)
  {
    delay.fill(max_delay - 1);
  }

  template<typename Mixture>
  FeedbackDelayNetworkFilter<Mixture>::~FeedbackDelayNetworkFilter()
  {
  }

  template<typename Mixture>
  void FeedbackDelayNetworkFilter<Mixture>::set_delay(unsigned int channel, gsl::index delay)
  {
    if (delay == 0)
    {
      throw ATK::RuntimeError("Delay must be strictly positive");
    }
    if (delay >= max_delay)
    {
      throw ATK::RuntimeError("Delay must be less than delay line size");
    }

    this->delay[channel] = delay;
  }

  template<typename Mixture>
  gsl::index FeedbackDelayNetworkFilter<Mixture>::get_delay(unsigned int channel) const
  {
    return delay[channel];
  }

  template<typename Mixture>
  void FeedbackDelayNetworkFilter<Mixture>::set_ingain(unsigned int channel, DataType ingain)
  {
    impl->ingain(channel) = ingain;
  }

  template<typename Mixture>
  typename FeedbackDelayNetworkFilter<Mixture>::DataType FeedbackDelayNetworkFilter<Mixture>::get_ingain(unsigned int channel) const
  {
    return impl->ingain(channel);
  }

  template<typename Mixture>
  void FeedbackDelayNetworkFilter<Mixture>::set_feedback(unsigned int channel, DataType feedback)
  {
    if (std::abs(feedback * static_cast<DataType>(Mixture::gain_factor)) >= 1)
    {
      throw ATK::RuntimeError("Feedback must be between " + std::to_string(-Mixture::gain_factor) + " and " + std::to_string(Mixture::gain_factor) + " to avoid divergence");
    }
    impl->feedback(channel) = feedback * static_cast<DataType>(Mixture::gain_factor);
  }

  template<typename Mixture>
  typename FeedbackDelayNetworkFilter<Mixture>::DataType FeedbackDelayNetworkFilter<Mixture>::get_feedback(unsigned int channel) const
  {
    return impl->feedback(channel) / static_cast<DataType>(Mixture::gain_factor);
  }

  template<typename Mixture>
  void FeedbackDelayNetworkFilter<Mixture>::set_outgain(unsigned int channel, DataType outgain)
  {
    impl->outgain(channel) = outgain;
  }

  template<typename Mixture>
  typename FeedbackDelayNetworkFilter<Mixture>::DataType FeedbackDelayNetworkFilter<Mixture>::get_outgain(unsigned int channel) const
  {
    return impl->outgain(channel);
  }

  template<typename Mixture>
  void FeedbackDelayNetworkFilter<Mixture>::full_setup()
  {
    // reset the delay line
    impl->processed_input.assign(max_delay, HFDN_Impl::Vector::Zero());
  }

  template<typename Mixture>
  void FeedbackDelayNetworkFilter<Mixture>::process_impl(gsl::index size) const
  {
    const DataType* ATK_RESTRICT input = converted_inputs[0];
    DataType* ATK_RESTRICT output = outputs[0];

    impl->delay_line.resize(size);

    for (gsl::index i = 0; i < size; ++i)
    {
      for (gsl::index channel = 0; channel < nb_channels; ++channel)
      {
        auto j = impl->index - static_cast<int64_t>(delay[channel]);
        if (j < 0)
        {
          j += max_delay;
        }

        impl->delay_line[i](channel) = impl->processed_input[j](channel);
      }
      impl->processed_input[impl->index] = (impl->ingain * input[i]).array() + impl->mix(impl->delay_line[i]).array() * impl->feedback.array();

      output[i] = impl->outgain.dot(impl->delay_line[i]);

      ++impl->index;
      if(impl->index == max_delay)
      {
        impl->index = 0;
      }
    }
  }
}

#endif


/*    Copyright (c) 2010-2018, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 */

#define BOOST_TEST_MAIN

#include <limits>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/bind.hpp>

#include <Eigen/Core>

#include "Tudat/Astrodynamics/Gravitation/basicSolidBodyTideGravityFieldVariations.h"
#include "Tudat/Astrodynamics/Gravitation/gravityFieldVariations.h"
#include "Tudat/Astrodynamics/Gravitation/timeDependentSphericalHarmonicsGravityField.h"
#include "Tudat/Astrodynamics/Gravitation/tabulatedGravityFieldVariations.h"
#include "Tudat/External/SpiceInterface/spiceInterface.h"
#include "Tudat/InputOutput/basicInputOutput.h"

namespace tudat
{
namespace unit_tests
{

BOOST_AUTO_TEST_SUITE( test_gravity_field_variations )

using namespace tudat::gravitation;
using namespace tudat::spice_interface;

//! Function to get nominal gravity field coefficients for Jupiter
/*!
 * Function to get nominal gravity field coefficients for Jupiter (returned by reference)
 * \param cosineCoefficients Jupiter's cosine coefficients
 * \param sineCoefficients Jupiter's sine coefficients
 */
void getNominalJupiterGravityField(
        Eigen::MatrixXd& cosineCoefficients, Eigen::MatrixXd& sineCoefficients )
{
    // Define unnormalized coefficients.
    double jupiterJ2 = 14.736E-3;
    double jupiterJ3 = 1.4E-6;
    double jupiterJ4 = -587.0E-6;
    double jupiterJ6 = 31.0E-6;
    double jupiterc22 = -0.03E-6;
    double jupiters22 = -0.007E-6;

    cosineCoefficients.setZero( 6, 6 );
    sineCoefficients.setZero( 6, 6 );

    // Normalize coefficients and set in matrices.
    cosineCoefficients( 0, 0 ) = 1.0;
    cosineCoefficients( 2, 0 ) =  -jupiterJ2 / basic_mathematics::calculateLegendreGeodesyNormalizationFactor( 2, 0 );
    cosineCoefficients( 2, 2 ) =  jupiterc22 / basic_mathematics::calculateLegendreGeodesyNormalizationFactor( 2, 2 );
    cosineCoefficients( 3, 0 ) =  -jupiterJ3 / basic_mathematics::calculateLegendreGeodesyNormalizationFactor( 3, 0 );
    cosineCoefficients( 4, 0 ) =  -jupiterJ4 / basic_mathematics::calculateLegendreGeodesyNormalizationFactor( 4, 0 );
    cosineCoefficients( 5, 0 ) =  -jupiterJ6 / basic_mathematics::calculateLegendreGeodesyNormalizationFactor( 6, 0 );
    sineCoefficients( 2, 2 ) =  jupiters22/ basic_mathematics::calculateLegendreGeodesyNormalizationFactor( 2, 2 );
}

//! Function to get tabulated gravity field variations
/*!
 * Function to get tabulated gravity field variations, based on arbitrary values, purely for test
 * purposes.
 * \param cosineCoefficientCorrections Map of corrections to cosine coefficients as function of
 * time.
 * \param sineCoefficientCorrections Map of corrections to sine coefficients as function of
 * time.
 */
void getTabulatedGravityFieldVariationValues(
        std::map< double, Eigen::MatrixXd >& cosineCoefficientCorrections,
        std::map< double, Eigen::MatrixXd >& sineCoefficientCorrections )
{

    // Define settings for coefficient variations
    double angularFrequency = 2.0 * mathematical_constants::PI / 86400.0;
    double amplitude = 1.0E-7;
    double startTime = 0.99E7;
    double endTime = 1.01E7;
    double timeStep = 3600.0;

    // Iterate over time steps
    double currentTime = startTime;
    while( currentTime < endTime )
    {
        // Initialize correctiuons to zero
        cosineCoefficientCorrections[ currentTime ].setZero( 4, 5 );
        sineCoefficientCorrections[ currentTime ].setZero( 4, 5 );

        // Set corrections for current time step.
        for( unsigned int i = 1; i < 5; i++ )
        {
            for( unsigned int j = 0; j <= i; j++ )
            {
                cosineCoefficientCorrections[ currentTime ]( i - 1, j ) =
                        amplitude *
                        std::cos( currentTime * angularFrequency + static_cast< double >( i * j ) );
                if( j != 0 )
                {
                    sineCoefficientCorrections[ currentTime ]( i - 1, j ) =
                            amplitude *
                            std::cos( currentTime * angularFrequency -
                                      static_cast< double >( i * j ) );
                }
            }
        }
        currentTime += timeStep;
    }
}

//! Function to get predefined tabulated gravity field variations.
/*!
 * Function to get predefined tabulated gravity field variations (non-physical values, purelu
 * for test purposes) using getTabulatedGravityFieldVariationValues function
 * \return Predefined tabulated gravity field variations.
 */
std::shared_ptr< TabulatedGravityFieldVariations > getTabulatedGravityFieldVariations( )
{
    // Get coefficient tables.
    std::map< double, Eigen::MatrixXd > cosineCoefficientCorrections;
    std::map< double, Eigen::MatrixXd > sineCoefficientCorrections;
    getTabulatedGravityFieldVariationValues(
                cosineCoefficientCorrections, sineCoefficientCorrections );

    // Create correction object.
    return std::make_shared< TabulatedGravityFieldVariations >(
                cosineCoefficientCorrections, sineCoefficientCorrections, 1, 0 );
}

//! Function to get predefined gravity field variations object.
/*!
 *  Function to get predefined gravity field variations object, consisting of dummy tabulated
 *  variations from getTabulatedGravityFieldVariationValues and degree 2 tidal variations.
 * \return Predefined gravity field variations object.
 */
std::shared_ptr< GravityFieldVariationsSet > getTestGravityFieldVariations( )
{
    // Define bodies raising rides.
    std::vector< std::string > deformingBodies;
    deformingBodies.push_back( "Io" );
    deformingBodies.push_back( "Europa" );

    // Retrieve required data of bodies raising tides.
    std::vector< std::function< Eigen::Vector6d( const double ) > >
            deformingBodyStateFunctions;
    std::vector< std::function< double( ) > > deformingBodyMasses;
    for( unsigned int i = 0; i < deformingBodies.size( ); i++ )
    {
        deformingBodyStateFunctions.push_back(
                    std::bind( &getBodyCartesianStateAtEpoch,
                                 deformingBodies.at( i ), "SSB", "J2000",
                                 "None", std::placeholders::_1 ) );
        deformingBodyMasses.push_back(
                    std::bind( &getBodyGravitationalParameter,
                                 deformingBodies.at( i ) ) );
    }

    // Define Love numbers ( constant for degree 2 only)
    std::complex< double > constantLoveNumber( 0.5, 0.5E-3 );
    std::vector< std::complex< double > > constantSingleDegreeLoveNumber( 3, constantLoveNumber );
    std::vector< std::vector< std::complex< double > > >
        loveNumbers( 1, constantSingleDegreeLoveNumber );

    // Set up gravity field variation of Jupiter due to Galilean moons.
    std::shared_ptr< GravityFieldVariations > solidBodyGravityFieldVariations =
            std::make_shared< BasicSolidBodyTideGravityFieldVariations >(
                std::bind( &getBodyCartesianStateAtEpoch,
                             "Jupiter", "SSB", "J2000", "None", std::placeholders::_1 ),
                std::bind( &computeRotationQuaternionBetweenFrames,
                             "J2000", "IAU_Jupiter", std::placeholders::_1 ),
                deformingBodyStateFunctions,
                getAverageRadius( "Jupiter" ),
                std::bind( &getBodyGravitationalParameter, "Jupiter" ),
                deformingBodyMasses, loveNumbers, deformingBodies );

    // Get tabulated gravity field variations.
    std::shared_ptr< GravityFieldVariations > tabulatedGravityFieldVariations =
            getTabulatedGravityFieldVariations( );

    // Create and return full gravity field variations object.
    return std::make_shared< GravityFieldVariationsSet >(
                std::vector< std::shared_ptr< GravityFieldVariations > >{ solidBodyGravityFieldVariations, tabulatedGravityFieldVariations },
                std::vector< BodyDeformationTypes >{ basic_solid_body, tabulated_variation },
                std::vector< std::string >{ "BasicTidal", "Tabulated" } );
}

BOOST_AUTO_TEST_CASE( testGravityFieldVariations )
{
    // Load spice kernels.
    spice_interface::loadStandardSpiceKernels( { input_output::getSpiceKernelPath( ) + "de430_jup310_small.bsp" } );

    // Define properties of nominal field
    double gravitationalParameter = getBodyGravitationalParameter( "Jupiter" );
    double referenceRadius = getAverageRadius( "Jupiter" );
    Eigen::MatrixXd nominalCosineCoefficients;
    Eigen::MatrixXd nominalSineCoefficients;
    getNominalJupiterGravityField( nominalCosineCoefficients, nominalSineCoefficients );

    // Create gravity field corrections object and retrieve corrections
    std::vector< std::shared_ptr< GravityFieldVariations > > gravityFieldVariationsList =
            getTestGravityFieldVariations( )->getVariationObjects( );

    // Define data structures for storing expected gravity field variations.
    std::pair< Eigen::MatrixXd, Eigen::MatrixXd > directGravityFieldVariations;
    Eigen::Matrix< double, 5, 5 > expectedCosineCoefficientsCorrections =
            Eigen::Matrix< double, 5, 5 >::Zero( );
    Eigen::Matrix< double, 5, 5 > expectedSineCoefficientsCorrections =
            Eigen::Matrix< double, 5, 5 >::Zero( );

    // Calculate gravity field variations directly.
    int minimumDegree, minimumOrder, numberOfDegrees, numberOfOrders;
    double testTime = 1.0E7;
    for( unsigned int i = 0; i < gravityFieldVariationsList.size( ); i++ )
    {
        minimumDegree = gravityFieldVariationsList.at( i )->getMinimumDegree( );
        numberOfDegrees = gravityFieldVariationsList.at( i )->getNumberOfDegrees( );

        minimumOrder = gravityFieldVariationsList.at( i )->getMinimumOrder( );
        numberOfOrders = gravityFieldVariationsList.at( i )->getNumberOfOrders( );

        directGravityFieldVariations =
                gravityFieldVariationsList.at( i )->
                calculateSphericalHarmonicsCorrections( testTime );
        expectedCosineCoefficientsCorrections.block(
                    minimumDegree, minimumOrder, numberOfDegrees, numberOfOrders ) +=
                directGravityFieldVariations.first;
        expectedSineCoefficientsCorrections.block(
                    minimumDegree, minimumOrder, numberOfDegrees, numberOfOrders ) +=
                directGravityFieldVariations.second;
    }

    // Create time-varying gravity field.
    std::shared_ptr< TimeDependentSphericalHarmonicsGravityField > timeDependentGravityField =
            std::make_shared< TimeDependentSphericalHarmonicsGravityField >(
                gravitationalParameter, referenceRadius, nominalCosineCoefficients,
                nominalSineCoefficients, getTestGravityFieldVariations( ) );
    timeDependentGravityField->update( 2.0 * testTime );

    // Calculate variations for current test time.
    timeDependentGravityField->update( testTime );

    // Get corrections at current time step from timeDependentGravityField
    Eigen::MatrixXd perturbedCosineCoefficients =
            timeDependentGravityField->getCosineCoefficients( );
    Eigen::MatrixXd perturbedSineCoefficients =
            timeDependentGravityField->getSineCoefficients( );
    Eigen::MatrixXd calculatedCosineCoefficientCorrections =
            ( perturbedCosineCoefficients - nominalCosineCoefficients ).block( 0, 0, 5, 5 );
    Eigen::MatrixXd calculatedSineCoefficientCorrections =
            ( perturbedSineCoefficients - nominalSineCoefficients ).block( 0, 0, 5, 5 );

    // Compare corrections against expected variations.
    for( unsigned int i = 0; i < 5; i++ )
    {
        for( unsigned int j = 0; j < 5; j++ )
        {
            BOOST_CHECK_SMALL( calculatedCosineCoefficientCorrections( i, j ) -
                               expectedCosineCoefficientsCorrections( i, j ), 1.0E-18 );
            BOOST_CHECK_SMALL( calculatedSineCoefficientCorrections( i, j ) -
                               expectedSineCoefficientsCorrections( i, j ), 1.0E-18 );
        }
    }

    // Test calculated tidal corrections against manual corrections directly from Cartesian states
    // of perturbing bodies.
    {
        // Define Love numbers
        // Define Love numbers ( constant for degree 2 only)
        std::complex< double > constantLoveNumber( 0.5, 0.5E-3 );
        std::vector< std::complex< double > > constantSingleDegreeLoveNumber( 3, constantLoveNumber );
        std::vector< std::vector< std::complex< double > > >
            loveNumbers( 1, constantSingleDegreeLoveNumber );

        // Manually calculate tidal corrections directly.
        std::pair< Eigen::MatrixXd, Eigen::MatrixXd > directIoTide =
                calculateSolidBodyTideSingleCoefficientSetCorrectionFromAmplitude(
                    loveNumbers, getBodyGravitationalParameter( "Io" )
                    / getBodyGravitationalParameter( "Jupiter" ),
                    getAverageRadius( "Jupiter" ),  spice_interface::getBodyCartesianPositionAtEpoch(
                        "Io", "Jupiter", "IAU_Jupiter", "None", testTime ), 2, 2 );
        std::pair< Eigen::MatrixXd, Eigen::MatrixXd > directEuropaTide =
                calculateSolidBodyTideSingleCoefficientSetCorrectionFromAmplitude(
                    loveNumbers, getBodyGravitationalParameter( "Europa" )
                    / getBodyGravitationalParameter( "Jupiter" ),
                    getAverageRadius( "Jupiter" ),  spice_interface::getBodyCartesianPositionAtEpoch(
                        "Europa", "Jupiter", "IAU_Jupiter", "None", testTime ), 2, 2 );

        // Calculate tidal corrections from objects
        std::pair< Eigen::MatrixXd, Eigen::MatrixXd > tidalCorrectionsFromObject =
                ( ( timeDependentGravityField->getGravityFieldVariationsSet( )
                    ->getGravityFieldVariation( basic_solid_body ) ).second
                  ->calculateSphericalHarmonicsCorrections( testTime ) );

        // Compare results.
        Eigen::MatrixXd directCosineCorrections = directIoTide.first + directEuropaTide.first;
        Eigen::MatrixXd directSineCorrections = directIoTide.second + directEuropaTide.second;

        for( unsigned int i = 0; i < 3; i++ )
        {
            BOOST_CHECK_SMALL( directCosineCorrections( 2, i )
                               - tidalCorrectionsFromObject.first( 0, i ), 1.0E-20 );
            BOOST_CHECK_SMALL( directSineCorrections( 2, i )
                               - tidalCorrectionsFromObject.second( 0, i ), 1.0E-20 );
        }
    }
}

BOOST_AUTO_TEST_SUITE_END( )

} // namespace unit_tests
} // namespace tudat


#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>
#include <string>

#include "det_model_calbindin.hpp"
#include "det_model_pmca.hpp"
#include "det_model_vgcc.hpp"
#include "det_model_hh_post.hpp"
#include "det_model_ca_sensor.hpp"
#include "physical_constants.hpp"
#include "stl_vector_operation_functions.hpp"
#include "stoch_odeint_explicit_euler.hpp"

#include <boost/numeric/odeint.hpp> /*  Specifying the file within < > lets c++ search for it at -I path  */
#include <boost/range/iterator_range.hpp>
#include <boost/range/iterator.hpp>

namespace bno=boost::numeric::odeint;
namespace consts=astron::phy_const;
namespace myode=stoch_odeint_explicit_euler;

typedef std::vector< double > state_type;
typedef state_type::iterator state_type_it;
typedef bno::runge_kutta4 < state_type > rk4_stepper_type;
//-----------------
int main(int argc, char *argv[])
{
  //-------------------------------------
  std::string filename;         // Output filename variable
  // decode arguments passed
  if (argc < 2){
    filename = argv[0];
    std::cout << "No filename prefix provided" << std::endl;
    filename = filename.substr(2,filename.length()-2) + "_run";
  }
  else{
    filename = std::string(argv[1]);
  }
  std::ofstream fout(filename);
  if (!fout){
    std::cout << "Error opening file " << filename << "\n";
    return (0);
  }
  std::cout << "File = " << filename << std::endl;
  double conCaIn = 100E-09;
  if (argc >=3 ){
    conCaIn = std::stod(argv[2]);
  }
  else{
    std::cout << "No 3rd argument provided" << std::endl;
  }
  // ----------------------------------
  // Initialize deterministic objects
  const double volume = 1E-18;
  const int nVesicles = 7;

  rk4_stepper_type stepper_cb, stepper_pmca, stepper_hhp, stepper_vgcc, stepper_sensor;

  state_type Xpmca = {45E-06,1530/(consts::Av * 1000 * volume),0,0,2E-03};	       //1530
  state_type Xcb = {45E-06,40E-06,0,0,0,0,0,0,0}; //45E-06i
  state_type Xhhp = {0,1,0,-80.0E-03};
  state_type Xvgcc = {1,0,0,0,0};
  state_type Xsensor = {0, nVesicles /(consts::Av * 1000 * volume) ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0 };

  DET_MODEL_CALBINDIN det_cb(Xcb);
  DET_MODEL_PMCA det_pmca(Xpmca);
  DET_MODEL_HH_POST det_hhp(Xhhp);
  DET_MODEL_VGCC det_vgcc(Xvgcc);
  DET_MODEL_CALCIUM_SENSOR det_sensor(Xsensor,volume);
  //--------------------------------------
  myode::STOCH_ODEINT_EXPLICIT_EULER< state_type > odeint_euler;
  std::vector<int> s = {0,0,0,1};
  //--------------------------------------
  double t = 0.0;
  const double dt = 15E-06;
  const double tmax = 30;
  // ------------------------------------
  std::vector<double> rels(4);
  conCaIn = 45000E-09;		// 26400E-09
  double conCaStep = 10E-09;
  double conCaMax = 10E-03;
  double relMax = 0.0;
  double relMaxtime = 0.0;
  double V1 = -80E-03;
  double V2 = -80E-03;
  double Vb = -80E-03;
  double Vm = Vb;

  double Vstep = 10E-03;
  //-------------------------------------
  //while (V1 <= 120E-03){
  //t = 0.0;
  //Vm = Vb;
  //det_vgcc.X[0] = 1;
  for(int i=0; i<det_sensor.X.size(); i++){
    det_sensor.X[i] = 0.0;
  }
  det_sensor.X[0] = conCaIn;
  det_sensor.X[1] = nVesicles /(consts::Av * 1000 * volume);

  det_sensor.reset();

  while (t <= tmax){
    //det_pmca.X[0] = conCaIn;
    //det_pmca.X[4] = 2E-03;
    stepper_pmca.do_step( det_pmca, det_pmca.X, t, dt);
    //conCaIn  = det_pmca.X[0];
    //det_cb.X[0] = conCaIn;
    stepper_cb.do_step( det_cb, det_cb.X, t, dt);
    //det_cb.set_boundary_cond();
    //conCaIn = det_cb.X[0];
    //deint_euler.do_step( det_hhp,det_hhp.X,s,t,dt);
    stepper_hhp.do_step( det_hhp,det_hhp.X, t, dt);
    //det_vgcc.V = det_hhp.X[3];
    stepper_vgcc.do_step(det_vgcc,det_vgcc.X, t, dt);
    //---------------------------
    if( (t > 15000E-03) && ( t < (15004E-03 )) ){
      det_hhp.IExt = 20E-02;	// External current to trigger presynaptic spike
      //Vm = V1;
    }
    if( (t > 15004E-03) && ( t < (18000E-03 )) ){
      det_hhp.IExt = 00E-02;	// External current to trigger presynaptic spike
      //Vm = V1;
    }
    if( (t > 18000E-03) && ( t < (18004E-03 )) ){
      det_hhp.IExt = 20E-02;	// External current to trigger presynaptic spike
      //Vm = V1;
    }
    if( (t >= 18004E-03) && ( t <= (tmax )) ){
      //if( (t >= 8E-03) && ( t <= (tmax )) ){
      det_hhp.IExt = 0;
      //Vm = Vb;
    }
    // copy values here
    det.

    //det_vgcc.conCaIn = conCaIn;
    //det_vgcc.update_variables(dt);
    //conCaIn = det_vgcc.conCaIn;
    //--------------------------
    //det_sensor.X[0] = conCaIn;
    //stepper_sensor.do_step(det_sensor, det_sensor.X,t,dt);
    //det_sensor.update(t,dt);
    //rels = det_sensor.get_rel_rates();
    //conCaIn = det_sensor.X[0];
    //if (rels[3] > relMax){
    //  relMax = rels[3];
    //  relMaxtime = t;
    //};
    //-------------------------
    //fout << t << " " << conCaIn << " " << det_sensor.get_rel_rates() << " " << det_sensor.get_num_vesciles() << std::endl;
    fout << t << " " << conCaIn << " " << det_pmca.X << " " << det_cb.X << " " << det_hhp.X << " " << det_vgcc.X << " " << det_vgcc.I_Ca << " " << std::endl;
    //std::cout << det_hhp.X << std::endl;
    //std::cin.get();
    t = t + dt;
  };
  //fout << conCaIn << " " << relMax  << " " << relMaxtime << " " << det_sensor.get_Pr() << std::endl;
  //std::cout << conCaIn << " " << relMax << " " << det_sensor.get_Pr() << std::endl;
  //conCaIn = conCaIn + conCaStep;
  //V1+=Vstep;
  //relMax = 0;
  //relMaxtime = 0.0;
  //};
  fout.close();
  return 0;
}