/*    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 <string>
#include <thread>

#include <limits>

#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>

#include "Tudat/Basics/testMacros.h"
#include "Tudat/SimulationSetup/tudatEstimationHeader.h"
#include "Tudat/Astrodynamics/OrbitDetermination/EstimatableParameters/directTidalTimeLag.h"

namespace tudat
{
namespace unit_tests
{
BOOST_AUTO_TEST_SUITE( test_arcwise_environment )

//Using declarations.
using namespace tudat::observation_models;
using namespace tudat::orbit_determination;
using namespace tudat::estimatable_parameters;
using namespace tudat::interpolators;
using namespace tudat::numerical_integrators;
using namespace tudat::spice_interface;
using namespace tudat::simulation_setup;
using namespace tudat::orbital_element_conversions;
using namespace tudat::ephemerides;
using namespace tudat::propagators;
using namespace tudat::basic_astrodynamics;
using namespace tudat::coordinate_conversions;
using namespace tudat::ground_stations;
using namespace tudat::observation_models;

//! Unit test to check if tidal time lag parameters are estimated correctly
BOOST_AUTO_TEST_CASE( test_ArcwiseEnvironmentParameters )
{
    //Load spice kernels.
    spice_interface::loadStandardSpiceKernels( );

    // Define bodies in simulation
    std::vector< std::string > bodyNames;
    bodyNames.push_back( "Earth" );
    bodyNames.push_back( "Sun" );

    // Specify initial time
    double initialEphemerisTime = double( 1.0E7 );
    double finalEphemerisTime = initialEphemerisTime + 1.0 * 86400.0;

    // Create bodies needed in simulation
    std::map< std::string, std::shared_ptr< BodySettings > > bodySettings =
            getDefaultBodySettings( bodyNames );

    NamedBodyMap bodyMap = createBodies( bodySettings );
    bodyMap[ "Vehicle" ] = std::make_shared< Body >( );
    bodyMap[ "Vehicle" ]->setConstantBodyMass( 400.0 );

    // Create aerodynamic coefficient interface settings.
    double referenceArea = 4.0;
    double aerodynamicCoefficient = 1.2;
    std::shared_ptr< AerodynamicCoefficientSettings > aerodynamicCoefficientSettings =
            std::make_shared< ConstantAerodynamicCoefficientSettings >(
                referenceArea, aerodynamicCoefficient * ( Eigen::Vector3d( ) << 1.2, -0.01, 0.1 ).finished( ), 1, 1 );

    // Create and set aerodynamic coefficients object
    bodyMap[ "Vehicle" ]->setAerodynamicCoefficientInterface(
                createAerodynamicCoefficientInterface( aerodynamicCoefficientSettings, "Vehicle" ) );

    // Create radiation pressure settings
    double referenceAreaRadiation = 4.0;
    double radiationPressureCoefficient = 1.2;
    std::vector< std::string > occultingBodies;
    occultingBodies.push_back( "Earth" );
    std::shared_ptr< RadiationPressureInterfaceSettings > asterixRadiationPressureSettings =
            std::make_shared< CannonBallRadiationPressureInterfaceSettings >(
                "Sun", referenceAreaRadiation, radiationPressureCoefficient, occultingBodies );

    // Create and set radiation pressure settings
    bodyMap[ "Vehicle" ]->setRadiationPressureInterface(
                "Sun", createRadiationPressureInterface(
                    asterixRadiationPressureSettings, "Vehicle", bodyMap ) );

    bodyMap[ "Vehicle" ]->setEphemeris( std::make_shared< TabulatedCartesianEphemeris< > >(
                                            std::shared_ptr< interpolators::OneDimensionalInterpolator
                                            < double, Eigen::Vector6d > >( ), "Earth", "ECLIPJ2000" ) );

    setGlobalFrameBodyEphemerides( bodyMap, "SSB", "ECLIPJ2000" );

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////            CREATE ACCELERATIONS          //////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Set accelerations on Vehicle that are to be taken into account.
    SelectedAccelerationMap accelerationMap;
    std::map< std::string, std::vector< std::shared_ptr< AccelerationSettings > > > accelerationsOfVehicle;
    accelerationsOfVehicle[ "Earth" ].push_back( std::make_shared< AccelerationSettings >(
                                                     basic_astrodynamics::central_gravity ) );
    accelerationsOfVehicle[ "Sun" ].push_back( std::make_shared< AccelerationSettings >(
                                                   basic_astrodynamics::cannon_ball_radiation_pressure ) );
    accelerationsOfVehicle[ "Earth" ].push_back( std::make_shared< AccelerationSettings >(
                                                     basic_astrodynamics::aerodynamic ) );
    accelerationMap[ "Vehicle" ] = accelerationsOfVehicle;

    // Set bodies for which initial state is to be estimated and integrated.
    std::vector< std::string > bodiesToIntegrate;
    std::vector< std::string > centralBodies;
    bodiesToIntegrate.push_back( "Vehicle" );
    centralBodies.push_back( "Earth" );

    // Create acceleration models
    AccelerationMap accelerationModelMap = createAccelerationModelsMap(
                bodyMap, accelerationMap, bodiesToIntegrate, centralBodies );

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////             CREATE PROPAGATION SETTINGS            ////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Set Keplerian elements for Asterix.
    Eigen::Vector6d asterixInitialStateInKeplerianElements;
    asterixInitialStateInKeplerianElements( semiMajorAxisIndex ) = 7200.0E3;
    asterixInitialStateInKeplerianElements( eccentricityIndex ) = 0.05;
    asterixInitialStateInKeplerianElements( inclinationIndex ) = unit_conversions::convertDegreesToRadians( 85.3 );
    asterixInitialStateInKeplerianElements( argumentOfPeriapsisIndex ) = unit_conversions::convertDegreesToRadians( 235.7 );
    asterixInitialStateInKeplerianElements( longitudeOfAscendingNodeIndex ) = unit_conversions::convertDegreesToRadians( 23.4 );
    asterixInitialStateInKeplerianElements( trueAnomalyIndex ) = unit_conversions::convertDegreesToRadians( 139.87 );

    double earthGravitationalParameter = bodyMap.at( "Earth" )->getGravityFieldModel( )->getGravitationalParameter( );

    // Set (perturbed) initial state.
    Eigen::Matrix< double, 6, 1 > systemInitialState = convertKeplerianToCartesianElements(
                asterixInitialStateInKeplerianElements, earthGravitationalParameter );

    // Create propagator settings
    std::shared_ptr< DependentVariableSaveSettings > dependentVariableSaveSettings;
    std::vector< std::shared_ptr< SingleDependentVariableSaveSettings > > dependentVariables;
    dependentVariables.push_back(
                std::make_shared< SingleDependentVariableSaveSettings >(
                    aerodynamic_force_coefficients_dependent_variable, "Vehicle", "Earth" ) );
    dependentVariables.push_back(
                std::make_shared< SingleDependentVariableSaveSettings >(
                    radiation_pressure_coefficient_dependent_variable, "Vehicle", "Sun" ) );
    dependentVariableSaveSettings = std::make_shared< DependentVariableSaveSettings >( dependentVariables );

    std::shared_ptr< TranslationalStatePropagatorSettings< double > > propagatorSettings =
            std::make_shared< TranslationalStatePropagatorSettings< double > >(
                centralBodies, accelerationModelMap, bodiesToIntegrate, systemInitialState, double( finalEphemerisTime ),
                cowell, dependentVariableSaveSettings);

    // Create integrator settings
    std::shared_ptr< IntegratorSettings< double > > integratorSettings =
            std::make_shared< RungeKuttaVariableStepSizeSettingsScalarTolerances< double > >(
                double( initialEphemerisTime ), 90.0,
                RungeKuttaCoefficients::CoefficientSets::rungeKuttaFehlberg78,
                90.0, 90.0, 1.0, 1.0 );

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////             CREATE OBSERVATION SETTINGS            ////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    LinkEnds linkEnds;
    linkEnds[ observed_body ] = std::make_pair( "Vehicle", "" );
    observation_models::ObservationSettingsMap observationSettingsMap;
    observationSettingsMap.insert(
                std::make_pair( linkEnds, std::make_shared< ObservationSettings >( position_observable ) ) );

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////    DEFINE PARAMETERS THAT ARE TO BE ESTIMATED      ////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Define list of parameters to estimate.
    std::vector< std::shared_ptr< EstimatableParameterSettings > > parameterNames;
    parameterNames.push_back( std::make_shared< InitialTranslationalStateEstimatableParameterSettings< double > >(
                                  "Vehicle", systemInitialState, "Earth" ) );

    std::vector< double > arcStartTimeList =
    { initialEphemerisTime, initialEphemerisTime + 0.33 * 86400.0, initialEphemerisTime + 0.66 * 86400.0 };
    parameterNames.push_back(
                std::make_shared< ArcWiseDragCoefficientEstimatableParameterSettings >( "Vehicle", arcStartTimeList ) );
    parameterNames.push_back(
                std::make_shared< ArcWiseRadiationPressureCoefficientEstimatableParameterSettings >( "Vehicle", arcStartTimeList ) );

    // Create parameters
    std::shared_ptr< estimatable_parameters::EstimatableParameterSet< double > > parametersToEstimate =
            createParametersToEstimate( parameterNames, bodyMap );

    // Print identifiers and indices of parameters to terminal.
    printEstimatableParameterEntries( parametersToEstimate );

    Eigen::VectorXd truthParameters = parametersToEstimate->getFullParameterValues< double >( );
    truthParameters( 7 ) += 0.1;
    truthParameters( 8 ) += 0.2;

    truthParameters( 10 ) += 0.1;
    truthParameters( 11 ) += 0.2;

    parametersToEstimate->resetParameterValues( truthParameters );
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////          INITIALIZE ORBIT DETERMINATION OBJECT     ////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Create orbit determination object (propagate orbit, create observation models)
    OrbitDeterminationManager< double, double > orbitDeterminationManager =
            OrbitDeterminationManager< double, double >(
                bodyMap, parametersToEstimate, observationSettingsMap,
                integratorSettings, propagatorSettings );

    std::map< double, Eigen::VectorXd > dependentVariableData =
            orbitDeterminationManager.getVariationalEquationsSolver( )->getDynamicsSimulatorBase(
                )->getDependentVariableNumericalSolutionBase( )[ 0 ];

    // Test whether arc-wise coefficients are correctly used.
    double testDragCoefficient = 0.0;
    double testRadiationPressureCoefficient = 0.0;
    for( auto variableIterator : dependentVariableData )
    {
        double currentTime = variableIterator.first;

        if( currentTime < arcStartTimeList.at( 1 ) )
        {
            testDragCoefficient = truthParameters( 6 );
            testRadiationPressureCoefficient = truthParameters( 9 );
        }
        else if( currentTime < arcStartTimeList.at( 2 ) )
        {
            testDragCoefficient = truthParameters( 7 );
            testRadiationPressureCoefficient = truthParameters( 10 );
        }
        else
        {
            testDragCoefficient = truthParameters( 8 );
            testRadiationPressureCoefficient = truthParameters( 11 );
        }

        BOOST_CHECK_EQUAL( testDragCoefficient, variableIterator.second( 0 ) );
        BOOST_CHECK_EQUAL( aerodynamicCoefficient * -0.01, variableIterator.second( 1 ) );
        BOOST_CHECK_EQUAL( aerodynamicCoefficient * 0.1, variableIterator.second( 2 ) );
        BOOST_CHECK_EQUAL( testRadiationPressureCoefficient, variableIterator.second( 3 ) );

    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////          SIMULATE OBSERVATIONS                     ////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Define time of first observation
    double observationTimeStart = initialEphemerisTime + 1000.0;

    // Define time between two observations
    double  observationInterval = 30.0;

    // Simulate observations for 3 days
    std::vector< double > baseTimeList;
    double currentTime = initialEphemerisTime + 3600.0;
    while( currentTime < finalEphemerisTime - 3600.0 )
    {
        baseTimeList.push_back( currentTime );
        currentTime += observationInterval;
    }

    // Create measureement simulation input
    std::map< ObservableType, std::map< LinkEnds, std::pair< std::vector< double >, LinkEndType > > > measurementSimulationInput;
    measurementSimulationInput[ position_observable ][ linkEnds ] =
            std::make_pair( baseTimeList, observed_body );

    // Set typedefs for POD input (observation types, observation link ends, observation values, associated times with
    // reference link ends.
    typedef Eigen::Matrix< double, Eigen::Dynamic, 1 > ObservationVectorType;
    typedef std::map< LinkEnds, std::pair< ObservationVectorType, std::pair< std::vector< double >, LinkEndType > > >
            SingleObservablePodInputType;
    typedef std::map< ObservableType, SingleObservablePodInputType > PodInputDataType;

    // Simulate observations
    PodInputDataType observationsAndTimes = simulateObservations< double, double >(
                measurementSimulationInput, orbitDeterminationManager.getObservationSimulators( ) );

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////    PERTURB PARAMETER VECTOR AND ESTIMATE PARAMETERS     ////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Perturb parameter estimate
    Eigen::Matrix< double, Eigen::Dynamic, 1 > parameterPerturbation =
            Eigen::Matrix< double, Eigen::Dynamic, 1 >::Zero( truthParameters.rows( ) );
    parameterPerturbation.segment( 0, 3 ) = Eigen::Vector3d::Constant( 10.0 );
    parameterPerturbation.segment( 3, 3 ) = Eigen::Vector3d::Constant( 1.0E-2 );
    parameterPerturbation.segment( 6, 6 ) = Eigen::VectorXd::Constant( 6, 1.0 );
    Eigen::Matrix< double, Eigen::Dynamic, 1 > initialParameterEstimate = truthParameters;
    initialParameterEstimate += parameterPerturbation;

    // Define estimation input
    std::shared_ptr< PodInput< double, double > > podInput =
            std::make_shared< PodInput< double, double > >(
                observationsAndTimes, initialParameterEstimate.rows( ),
                Eigen::MatrixXd::Zero( truthParameters.rows( ), truthParameters.rows( ) ),
                initialParameterEstimate - truthParameters );
    podInput->defineEstimationSettings( true, true, false, true );

    // Perform estimation
    std::shared_ptr< PodOutput< double > > podOutput = orbitDeterminationManager.estimateParameters(
                podInput, std::make_shared< EstimationConvergenceChecker >( 4 ) );
    Eigen::VectorXd parameterEstimate = podOutput->parameterEstimate_ - truthParameters;

    for( int i = 0; i < 3; i++ )
    {
        BOOST_CHECK_SMALL( std::fabs( parameterEstimate( i ) ), 1.0E-2 );
        BOOST_CHECK_SMALL( std::fabs( parameterEstimate( i + 3 ) ), 1.0E-4 );
        BOOST_CHECK_SMALL( std::fabs( parameterEstimate( i + 6 ) ), 1.0E-5 );
        BOOST_CHECK_SMALL( std::fabs( parameterEstimate( i + 9 ) ), 1.0E-4 );

    }

}

BOOST_AUTO_TEST_SUITE_END( )

}

}


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

/*!
 * \ingroup boost_simd_swar
 * \defgroup boost_simd_swar_cumprod cumprod
 *
 * \par Description
 * compute the cumulate prod of the vector elements
 *
 * \par Header file
 *
 * \code
 * #include <nt2/include/functions/cumprod.hpp>
 * \endcode
 *
 *
 * \synopsis
 *
 * \code
 * namespace boost::simd
 * {
 *   template <class A0>
 *     meta::call<tag::cumprod_(A0)>::type
 *     cumprod(const A0 & a0);
 * }
 * \endcode
 *
 * \param a0 the unique parameter of cumprod
 *
 * \return a value of the same type as the parameter
 *
 * \par Notes
 * \par
 * This is a swar operation. As such it has not real interest outside
 * SIMD mode.
 * \par
 * Such an operation is a transform of an SIMD vector, that will return
 * vectors obtained on a non necessarily elementwise basis from the inputs
 * elements
 * \par
 * If usable and used in scalar mode, it reduces to the operation
 * on a one element vector.
 *
**/

namespace boost { namespace simd { namespace tag
  {
    /*!
     * \brief Define the tag cumprod_ of functor cumprod
     *        in namespace boost::simd::tag for toolbox boost.simd.swar
    **/
    struct cumprod_ : ext::cumulative_<cumprod_, tag::multiplies_, tag::One>
    {
      typedef ext::cumulative_<cumprod_, tag::multiplies_, tag::One> parent;
    };
  }
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::cumprod_, cumprod, 1)
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::cumprod_, cumprod, 2)
} }

#endif

// modified by jt the 25/12/2010


#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>

const std::string input = R"(cpy a b
dec b
cpy a d
cpy 0 a
cpy b c
inc a
dec c
jnz c -2
dec d
jnz d -5
dec b
cpy b c
cpy c d
dec d
inc c
jnz d -2
tgl c
cpy -16 c
jnz 1 c
cpy 77 c
jnz 73 d
inc a
inc d
jnz d -2
inc c
jnz c -5)";

struct Argument
{
    int index;
    bool is_register;

    Argument(int _index, bool _is_register)
        : index(_index)
        , is_register(_is_register)
    {
    }
};

struct Instruction
{
    std::string code;
    std::vector<Argument> args;

    void parse(const std::string &str)
    {
        std::vector<std::string> parts;
        boost::split(parts, str, boost::is_any_of(" "));

        code = parts[0];
        args.clear();
        for (int i = 1; i < parts.size(); ++i)
        {
            int index = 0;
            bool is_register = false;

            if ((parts[i].size() == 1)
                && (parts[i][0] >= 'a')
                && (parts[i][0] <= 'd'))
            {
                index = parts[i][0] - 'a';
                is_register = true;
            }
            else
            {
                index = atoi(parts[i].c_str());
                is_register = false;
            }

            args.push_back(Argument(index, is_register));
        }
    }
};

struct ComputerState
{
    int pc;
    int64_t registers[4];
    bool complete;

    ComputerState()
        : pc(0)
        , complete(false)
    {
        for (int i = 0; i < 4; ++i)
            registers[i] = 0;
    }

    void set(const Argument &arg, int value)
    {
        assert(arg.is_register);
        registers[arg.index] = value;
    }

    int get(const Argument &arg) const
    {
        if (arg.is_register)
            return registers[arg.index];
        else
            return arg.index;
    }

    void update(std::vector<Instruction> &instructions)
    {
        if (!complete
            && (pc < instructions.size())
            && (pc >= 0))
        {
            const Instruction &inst = instructions[pc];

            if (inst.code == "cpy")
            {
                assert(inst.args.size() == 2);

                if (inst.args[1].is_register)
                {
                    set(inst.args[1], get(inst.args[0]));
                }

                pc += 1;
            }
            else if (inst.code == "inc")
            {
                assert(inst.args.size() == 1);

                if (inst.args[0].is_register)
                {
                    set(inst.args[0], get(inst.args[0]) + 1);
                }

                pc += 1;
            }
            else if (inst.code == "dec")
            {
                assert(inst.args.size() == 1);

                if (inst.args[0].is_register)
                {
                    set(inst.args[0], get(inst.args[0]) - 1);
                }

                pc += 1;
            }
            else if (inst.code == "jnz")
            {
                assert(inst.args.size() == 2);

                int test = get(inst.args[0]);
                int distance = get(inst.args[1]);

                if (test != 0)
                    pc = pc + distance;
                else
                    pc += 1;
            }
            else if (inst.code == "tgl")
            {
                int target_inst_index = pc + get(inst.args[0]);

                if ((target_inst_index >= 0)
                    && (target_inst_index < instructions.size()))
                {
                    Instruction &target_inst = instructions[target_inst_index];

                    if (target_inst.args.size() == 1)
                    {
                        if (target_inst.code == "inc")
                            target_inst.code = "dec";
                        else
                            target_inst.code = "inc";
                    }
                    else // 2-argument instructions
                    {
                        if (target_inst.code == "jnz")
                            target_inst.code = "cpy";
                        else
                            target_inst.code = "jnz";
                    }
                }

                pc += 1;
            }
            else
            {
                assert(false);
            }
        }

        if ((pc >= instructions.size())
            || (pc < 0))
        {
            complete = true;
        }
    }

    void print() const
    {
        std::cout << "pc=" << pc << ", reg="
            << registers[0] << ","
            << registers[1] << ","
            << registers[2] << ","
            << registers[3] << std::endl;
    }
};

int run_with_arg(int init)
{
    std::vector<std::string> lines;
    boost::split(lines, input, boost::is_any_of("\n"));

    std::vector<Instruction> instructions;

    for (const std::string &line : lines)
    {
        instructions.emplace_back();
        instructions.back().parse(line);
    }

    ComputerState state;

    state.registers[0] = init;

    while (!state.complete)
    {
        state.update(instructions);

        //state.print();
    }

    return state.registers[0];
}

int main(int argc, char *argv[])
{
    int answer1 = run_with_arg(7);

    std::cout << "Answer #1: " << answer1 << std::endl;

    std::cout << "Answer #2: Calculate f(12) based on these values:" << std::endl;
    for (int i = 7; i <= 10; ++i)
    {
        std::cout << "   f(" << i << ") = " << run_with_arg(i) << std::endl;
    }
    std::cout << std::endl;
    std::cout << "For my input, it looks like f(x) = 5621 + factorial(x)" << std::endl;
    std::cout << "  so f(12) = 5621 + 12! = 5621 = 479007221" << std::endl;
}


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

#include <cstddef>
#include <ostream>
#include <sstream>
#include <stdio.h>

#include <amount.h>
#include <base58.h>
#include <blockencodings.h>
#include <chainparams.h>
#include <chainparamsbase.h>
#include <coins.h>
#include <consensus/consensus.h>
#include <consensus/merkle.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
#include <miner.h>
#include <policy/policy.h>
#include <PoWCore/src/PoW.h>
#include <PoWCore/src/PoWProcessor.h>
#include <PoWCore/src/PoWUtils.h>
#include <PoWCore/src/Sieve.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <random.h>
#include <script/standard.h>
#include <txmempool.h>
#include <uint256.h>
#include <util.h>
#include <utilstrencodings.h>
#include <validation.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>

#include <test/test_bitcoin.h>

#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(gapblock_tests, TestingSetup)

static PoWUtils *powUtils = new PoWUtils();

//class BlockProcessor : public PoWProcessor {
//public:
//    BlockProcessor(CBlock *pblock, CReserveScript coinbasescript) : PoWProcessor() {
//        this->pblock = pblock;
//        this->script = coinbasescript;
//    }

//    bool process(PoW *pow) {
//        pow->get_adder(&pblock->nAdd);
//        bool ret = CheckWork(pblock, script);
//        return !ret;
//    }

//private:
//    CBlock *pblock;
//    CReserveScript script;
//};

class BlockProcessor : public PoWProcessor {

  public:

    BlockProcessor(CBlock *pblock, std::shared_ptr<CReserveScript> coinbase_script) : PoWProcessor() {
      this->pblock = pblock;
      this->script = coinbase_script;
    }

    bool process(PoW *pow) {
      pow->get_adder(&pblock->nAdd);
      bool ret = CheckWork(pblock, script);
      return !ret;
    }

  private:

    CBlock *pblock;
    std::shared_ptr<CReserveScript> script;

};

//CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey)
//{
//    CKey coinbaseKey; // private/public key needed to spend coinbase transactions
//    UpdateVersionBitsParameters(Consensus::DEPLOYMENT_SEGWIT, 0, Consensus::BIP9Deployment::NO_TIMEOUT);
//    coinbaseKey.MakeNewKey(true);
//    CScript scriptPubKey = CScript() <<  ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
//    return CreateNewBlock(scriptPubKey);
//}

//bool CheckWork(CBlock* pblock, CReserveKey& reservekey)
//{
//    uint256 hash = pblock->GetHash();
//    PoW pow(new std::vector<uint8_t>(hash.begin(), hash.end()),
//            pblock->nShift,
//            &pblock->nAdd,
//            pblock->nDifficulty);

//    uint64_t nDifficulty = pow.difficulty();

//    if (nDifficulty < pblock->nDifficulty)
//        return false;

//    //// debug print
//    LogPrintf("GapcoinMiner:\n");
//    LogPrintf("proof-of-work found  \ndifficulty: %" PRIu64 "  \ntarget: %" PRIu64 "\n", nDifficulty, pblock->nDifficulty);
//    pblock->print();
//    LogPrintf("generated %s\n", FormatMoney(pblock->vtx[0].vout[0].nValue));

//    // Found a solution
//    {
//        LOCK(cs_main);
//        if (pblock->hashPrevBlock != chainActive.Tip()->GetBlockHash())
//            return error("GapcoinMiner : generated block is stale");

//        // Remove key from key pool
//        reservekey.KeepKey();

//        // Track how many getdata requests this block gets
//        {
//            LOCK(wallet.cs_wallet);
//            wallet.mapRequestCount[pblock->GetHash()] = 0;
//        }

//        // Process this block the same as if we had received it from another node
//        CValidationState state;
//        if (!ProcessBlock(state, NULL, pblock))
//            return error("GapcoinMiner : ProcessBlock, block not accepted");
//    }

//    return true;
//}

static CFeeRate blockMinFeeRate = CFeeRate(DEFAULT_BLOCK_MIN_TX_FEE);

static BlockAssembler AssemblerForTest(const CChainParams& params) {
    BlockAssembler::Options options;

    options.nBlockMaxWeight = MAX_BLOCK_WEIGHT;
    options.blockMinFeeRate = blockMinFeeRate;
    return BlockAssembler(params, options);
}

void static GapcoinMiner()
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    const CChainParams& chainparams = *chainParams;
    std::unique_ptr<CBlockTemplate> pblocktemplate;

    CTxDestination destination = DecodeDestination("Gg6zcf8yLbPdy1b14T135QzhjhZXQTSVW2");
    BOOST_CHECK(IsValidDestination(destination));

    std::shared_ptr<CReserveScript> coinbaseScript = std::make_shared<CReserveScript>();
    coinbaseScript->reserveScript = GetScriptForDestination(destination);

    unsigned int nExtraNonce = 0;
    uint256 hashTarget = uint256S("0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");

    uint64_t nMiningSieveSize = 1000;
    uint64_t nMiningPrimes = 1000;
    uint16_t nMiningShift = 15;

    Sieve sieve(NULL, nMiningPrimes, nMiningSieveSize);
    std::cout << "Sieve initialised." << std::endl;

    try {
        // Create new block
        CBlockIndex* pindexPrev = chainActive.Tip();

        std::cout << "Creating template." << std::endl;
        // std::unique_ptr<CBlockTemplate> pblocktemplate(BlockAssembler(Params()).CreateNewBlock(coinbaseScript->reserveScript));
        BOOST_CHECK(pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(coinbaseScript->reserveScript));

        if (!pblocktemplate.get()) {
            std::cout << "Template creation failed." << std::endl;
            return;
        }
        std::cout << "Template created." << std::endl;

        // std::cout << "pblock" << std::endl;
        // const std::vector<unsigned char>::size_type Z(pblocktemplate->block.nAdd.size());
        // std::cout << "{";
        // for (size_t i=0; i<Z; i++)
        // {
        //     std::cout << static_cast<unsigned int>(pblocktemplate->block.nAdd[i]) << ",";
        // }
        // std::cout << "}" << std::endl;
        // std::cout << "shift: " << pblocktemplate->block.nShift << std::endl;
        // std::cout << "difficulty: " << pblocktemplate->block.nDifficulty << std::endl;
        // std::cout << "nonce: " << pblocktemplate->block.nNonce << std::endl;

        CBlock *pblock = &pblocktemplate->block;
        IncrementExtraNonce(pblock, pindexPrev, nExtraNonce);

        pblock->nDifficulty = PoWUtils::min_test_difficulty;
        uint8_t nAdd[]      = { 25, 1 };
        pblock->nAdd.assign(nAdd, nAdd + sizeof(nAdd) / sizeof(uint8_t));

        const std::vector<unsigned char>::size_type T(pblock->nAdd.size());
        std::cout << "pblock" << std::endl;
        std::cout << "{";
        for (size_t i=0; i<T; i++)
        {
            std::cout << static_cast<unsigned int>(pblock->nAdd[i]) << ",";
        }
        std::cout << "}" << std::endl;
        std::cout << "shift: " << pblock->nShift << std::endl;
        std::cout << "difficulty: " << pblock->nDifficulty << std::endl;
        std::cout << "nonce: " << pblock->nNonce << std::endl;

        // Search
        BlockProcessor processor(pblock, coinbaseScript);
        // std::cout << "PoW processor initialised." << std::endl;
        sieve.set_pprocessor(&processor);
        // std::cout << "PoW processor bound to sieve." << std::endl;

        // int64_t nStart = GetTime();

        pblock->nNonce = 0;

        while (true)
        {
            /* header hash has to be greater than 2^255 - 1 */
            // std::cout << "pblock->GetHash()" << pblock->GetHash().ToString() << " hashTarget: " << hashTarget << std::endl;
            while (pblock->GetHash() < hashTarget) {
                pblock->nNonce += 1;
                if (pblock->nNonce % 1000 == 0)
                    std::cout << "pblock->nNonce = " << pblock->nNonce << std::endl;

                uint256 hash = pblock->GetHash();
                std::vector<uint8_t> vHash(hash.begin(), hash.end());

                PoW pow(&vHash, pblock->nShift, &pblock->nAdd, pblock->nDifficulty);
                sieve.run_sieve(&pow, NULL);
                if (pow.valid()) {
                    // std::cout << "Run with pow processor " << pow.to_s() << std::endl;
                    break;
                }
            }
            std::cout << "pblock: " << pblock->ToString() << std::endl;
            const std::vector<unsigned char>::size_type N(pblock->nAdd.size());
            std::cout << "{";
            for (size_t i=0; i<N; i++)
            {
                std::cout << static_cast<unsigned int>(pblock->nAdd[i]) << ",";
            }
            std::cout << "}" << std::endl;
            std::cout << "shift: " << pblock->nShift << std::endl;
            std::cout << "difficulty: " << pblock->nDifficulty << std::endl;
            std::cout << "nonce: " << pblock->nNonce << std::endl;
        }
    }
    catch (boost::thread_interrupted)
    {
        LogPrintf("GapcoinMiner terminated\n");
        throw;
    }
}

BOOST_AUTO_TEST_CASE(one)
{
    BOOST_TEST_MESSAGE(strprintf("nMiningSieveSize=%s, nMiningPrimes=%s, nMiningShift=%s.", nMiningSieveSize, nMiningPrimes, nMiningShift));
    /* FIXME: diabled, pro tem
    GapcoinMiner();
    */
    BOOST_TEST_MESSAGE(strprintf("Done"));
}

//static CBlock BuildBlockTestCase() {
//    CBlock block;
//    CMutableTransaction tx;
//    tx.vin.resize(1);
//    tx.vin[0].scriptSig.resize(10);
//    tx.vout.resize(1);
//    tx.vout[0].nValue = 42;

//    block.vtx.resize(3);
//    block.vtx[0] = MakeTransactionRef(tx);
//    block.nVersion = 42;
//    block.hashPrevBlock = InsecureRand256();
//    block.nDifficulty = PoWUtils::min_difficulty;
//    uint8_t nAdd[]      = { 25, 1 };
//    block.nAdd.assign(nAdd, nAdd + sizeof(nAdd) / sizeof(uint8_t));
//    tx.vin[0].prevout.hash = InsecureRand256();
//    tx.vin[0].prevout.n = 0;
//    block.vtx[1] = MakeTransactionRef(tx);

//    tx.vin.resize(10);
//    for (size_t i = 0; i < tx.vin.size(); i++) {
//        tx.vin[i].prevout.hash = InsecureRand256();
//        tx.vin[i].prevout.n = 0;
//    }

//    block.vtx[2] = MakeTransactionRef(tx);

//    bool mutated;
//    block.hashMerkleRoot = BlockMerkleRoot(block, &mutated);
//    assert(!mutated);
//    while (!CheckProofOfWork(block.GetHash(), block.nShift, &block.nAdd, block.nDifficulty, Params().GetConsensus())) ++block.nNonce;
//    return block;
//}

//static CBlock CreateGenesisBlock(const char* pszTimestamp, const CScript& genesisOutputScript, uint32_t nTime, uint32_t nNonce, uint64_t nDifficulty, int32_t nVersion, const CAmount& genesisReward)
//{
//    CMutableTransaction txNew;
//    txNew.nVersion = 1;
//    txNew.vin.resize(1);
//    txNew.vout.resize(1);
//    txNew.vin[0].scriptSig = CScript() << 486604799 << CScriptNum(4) << std::vector<unsigned char>((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp));
//    txNew.vout[0].nValue = genesisReward;
//    txNew.vout[0].scriptPubKey = genesisOutputScript;

//    CBlock genesis;
//    genesis.nTime    = nTime;
//    genesis.nNonce   = nNonce;
//    genesis.nVersion = nVersion;
//    genesis.nShift = 20;
//    genesis.nDifficulty = nDifficulty;
//    uint8_t nAdd[] = { 233, 156, 15 };
//    genesis.nAdd.assign(nAdd, nAdd + sizeof(nAdd) / sizeof(uint8_t));
//    genesis.vtx.push_back(MakeTransactionRef(std::move(txNew)));
//    genesis.hashPrevBlock.SetNull();
//    genesis.hashMerkleRoot = BlockMerkleRoot(genesis);
//    return genesis;
//}

//static CBlock CreateGenesisBlock(uint32_t nTime, uint32_t nNonce, uint64_t nDifficulty, int32_t nVersion, const CAmount& genesisReward)
//{
//    const char* pszTimestamp = "The Times 15/Oct/2014 US data sends global stocks into tail-spin";
//    const CScript genesisOutputScript = CScript() << ParseHex("044588d54931b7de2f9faaa5a3c1fde654114ae51273754e1f3f9720127f8977af6bfaa1f33e22e80e4b83f5269921501b411d254929faf1b10d2174ded28ac59d") << OP_CHECKSIG;
//    return CreateGenesisBlock(pszTimestamp, genesisOutputScript, nTime, nNonce, nDifficulty, nVersion, genesisReward);
//}

//static bool CheckPoW(const uint256 hash, const uint16_t nShift, const std::vector<uint8_t> *const nAdd, const uint64_t nDifficulty)
//{
//    std::vector<uint8_t> vHash(hash.begin(), hash.end());

//    PoW pow(&vHash, nShift, nAdd, nDifficulty);

//    // Check proof of work matches claimed amount
//    if (!pow.valid())
//        return error("CheckProofOfWork() : hash does not match nDifficulty");

//    return true;
//}

//static CBlock ReBuildGensisBlock() {
//    CBlock genesis = CreateGenesisBlock(1413914400, 13370, PoWUtils::min_difficulty, 1, 0 * COIN);
//    return genesis;
//}

//BOOST_AUTO_TEST_CASE(ReadHashTest, *boost::unit_test::disabled() *boost::unit_test::label("readhash") *boost::unit_test::description("Test skipped"))
//{
//    // head -c 298 blocks/blk00000.dat | tail -c +9 | hexdump -v -e '/1 "%02x"'
//    string genesisraw  = "01000000000000000000000000000000000000000000000000000000000000000000000094110eb4e3b9c2b70df4b71df19d5405cca21e55c2565e356ae22aadcf101026209f465400000000000010003a340000140003e99c0f0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4804ffff001d0104405468652054696d65732031352f4f63742f3230313420555320646174612073656e647320676c6f62616c2073746f636b7320696e746f207461696c2d7370696effffffff0100000000000000004341044588d54931b7de2f9faaa5a3c1fde654114ae51273754e1f3f9720127f8977af6bfaa1f33e22e80e4b83f5269921501b411d254929faf1b10d2174ded28ac59dac00000000";
//    CBlock block;
//    CDataStream stream(ParseHex(genesisraw), SER_NETWORK, PROTOCOL_VERSION);
//    stream >> block;
//    BOOST_CHECK_EQUAL(block.GetHash().ToString(), "e798f3ae4f57adcf25740fe43100d95ec4fd5d43a1568bc89e2b25df89ff6cb0");
//    BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), "261010cfad2ae26a355e56c2551ea2cc05549df11db7f40db7c2b9e3b40e1194");
//}

//BOOST_AUTO_TEST_CASE(SimpleHashTest, *boost::unit_test::disabled() *boost::unit_test::description("test skipped"))
//{
//    CBlock block = getBlock0();
//    block.GetHash();
//    BOOST_CHECK_EQUAL(block.GetHash().ToString(), "e798f3ae4f57adcf25740fe43100d95ec4fd5d43a1568bc89e2b25df89ff6cb0");
//    BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), "261010cfad2ae26a355e56c2551ea2cc05549df11db7f40db7c2b9e3b40e1194");
//}

//BOOST_AUTO_TEST_CASE(ReadGenesisTest, *boost::unit_test::disabled() *boost::unit_test::description("test skipped"))
//{
//    CBlock block = ReBuildGensisBlock();
//    block.GetHash();
//    BOOST_CHECK_EQUAL(block.GetHash().ToString(), "e798f3ae4f57adcf25740fe43100d95ec4fd5d43a1568bc89e2b25df89ff6cb0");
//    BOOST_CHECK_EQUAL(block.hashMerkleRoot.ToString(), "261010cfad2ae26a355e56c2551ea2cc05549df11db7f40db7c2b9e3b40e1194");
//}

//BOOST_AUTO_TEST_CASE(powcheck, *boost::unit_test::disabled() *boost::unit_test::description("test skipped"))
//{
//    // uint256 hash = pindex->GetBlockHash();
//    // std::vector<uint8_t> vHash(hash.begin(), hash.end());
//    // PoW pow(&vHash, pindex->nShift, &pindex->nAdd, pindex->nDifficulty);

//}

BOOST_AUTO_TEST_SUITE_END()


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

#include "wallet/test/wallet_test_fixture.h"

#include "sapling/util.h"
#include "sapling/address.hpp"
#include "wallet/wallet.h"
#include "wallet/walletdb.h"
#include "util.h"
#include <boost/test/unit_test.hpp>

/**
 * This test covers methods on CWallet
 * GenerateNewZKey()
 * AddZKey()
 * LoadZKey()
 * LoadZKeyMetadata()
 */

BOOST_FIXTURE_TEST_SUITE(wallet_zkeys_tests, WalletTestingSetup)

/**
  * This test covers Sapling methods on CWallet
  * GenerateNewSaplingZKey()
  * AddSaplingZKey()
  * LoadSaplingZKey()
  * LoadSaplingZKeyMetadata()
  */
BOOST_AUTO_TEST_CASE(StoreAndLoadSaplingZkeys) {
    SelectParams(CBaseChainParams::MAIN);

    CWallet wallet;
    LOCK(wallet.cs_wallet);
    // wallet should be empty
    std::set<libzcash::SaplingPaymentAddress> addrs;
    wallet.GetSaplingPaymentAddresses(addrs);
    BOOST_CHECK_EQUAL(0, addrs.size());

    // No HD seed in the wallet
    BOOST_CHECK_THROW(wallet.GenerateNewSaplingZKey(), std::runtime_error);

    // Random seed
    CKey seed;
    seed.MakeNewKey(true);
    wallet.AddKeyPubKey(seed, seed.GetPubKey());
    wallet.GetSaplingScriptPubKeyMan()->SetHDSeed(seed.GetPubKey(), false, true);

    // wallet should have one key
    auto address = wallet.GenerateNewSaplingZKey();
    wallet.GetSaplingPaymentAddresses(addrs);
    BOOST_CHECK_EQUAL(1, addrs.size());

    // verify wallet has incoming viewing key for the address
    BOOST_CHECK(wallet.HaveSaplingIncomingViewingKey(address));

    // manually add new spending key to wallet
    HDSeed seed1(seed.GetPrivKey());
    auto m = libzcash::SaplingExtendedSpendingKey::Master(seed1);
    auto sk = m.Derive(0);
    BOOST_CHECK(wallet.AddSaplingZKey(sk, sk.DefaultAddress()));

    // verify wallet did add it
    auto fvk = sk.expsk.full_viewing_key();
    BOOST_CHECK(wallet.HaveSaplingSpendingKey(fvk));

    // verify spending key stored correctly
    libzcash::SaplingExtendedSpendingKey keyOut;
    wallet.GetSaplingSpendingKey(fvk, keyOut);
    BOOST_CHECK(sk == keyOut);

    // verify there are two keys
    wallet.GetSaplingPaymentAddresses(addrs);
    BOOST_CHECK_EQUAL(2, addrs.size());
    BOOST_CHECK_EQUAL(1, addrs.count(address));
    BOOST_CHECK_EQUAL(1, addrs.count(sk.DefaultAddress()));

    // Generate a diversified address different to the default
    // If we can't get an early diversified address, we are very unlucky
    blob88 diversifier;
    diversifier.begin()[0] = 10;
    auto dpa = sk.ToXFVK().Address(diversifier).get().second;

    // verify wallet only has the default address
    BOOST_CHECK(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress()));
    BOOST_CHECK(!wallet.HaveSaplingIncomingViewingKey(dpa));

    // manually add a diversified address
    auto ivk = fvk.in_viewing_key();
    BOOST_CHECK(wallet.AddSaplingIncomingViewingKeyW(ivk, dpa));

    // verify wallet did add it
    BOOST_CHECK(wallet.HaveSaplingIncomingViewingKey(sk.DefaultAddress()));
    BOOST_CHECK(wallet.HaveSaplingIncomingViewingKey(dpa));

    // Load a third key into the wallet
    auto sk2 = m.Derive(1);
    BOOST_CHECK(wallet.LoadSaplingZKey(sk2));

    // attach metadata to this third key
    auto ivk2 = sk2.expsk.full_viewing_key().in_viewing_key();
    int64_t now = GetTime();
    CKeyMetadata meta(now);
    BOOST_CHECK(wallet.LoadSaplingZKeyMetadata(ivk2, meta));

    // check metadata is the same
    BOOST_CHECK_EQUAL(wallet.GetSaplingScriptPubKeyMan()->mapSaplingZKeyMetadata[ivk2].nCreateTime, now);

    // Load a diversified address for the third key into the wallet
    auto dpa2 = sk2.ToXFVK().Address(diversifier).get().second;
    BOOST_CHECK(wallet.HaveSaplingIncomingViewingKey(sk2.DefaultAddress()));
    BOOST_CHECK(!wallet.HaveSaplingIncomingViewingKey(dpa2));
    BOOST_CHECK(wallet.LoadSaplingPaymentAddress(dpa2, ivk2));
    BOOST_CHECK(wallet.HaveSaplingIncomingViewingKey(dpa2));
}

/**
  * This test covers methods on CWalletDB to load/save crypted sapling z keys.
  */
BOOST_AUTO_TEST_CASE(WriteCryptedSaplingZkeyDirectToDb) {
    SelectParams(CBaseChainParams::TESTNET);

    BOOST_CHECK(!pwalletMain->HasSaplingSPKM());
    assert(pwalletMain->SetupSPKM(true));

    // wallet should be empty
    std::set<libzcash::SaplingPaymentAddress> addrs;
    pwalletMain->GetSaplingPaymentAddresses(addrs);
    BOOST_CHECK_EQUAL(0, addrs.size());

    // Add random key to the wallet
    auto address = pwalletMain->GenerateNewSaplingZKey();

    // wallet should have one key
    pwalletMain->GetSaplingPaymentAddresses(addrs);
    BOOST_CHECK_EQUAL(1, addrs.size());

    // encrypt wallet
    SecureString strWalletPass;
    strWalletPass.reserve(100);
    strWalletPass = "hello";
    BOOST_CHECK(pwalletMain->EncryptWallet(strWalletPass));

    // adding a new key will fail as the wallet is locked
    BOOST_CHECK_THROW(pwalletMain->GenerateNewSaplingZKey(), std::runtime_error);

    // unlock wallet and then add
    pwalletMain->Unlock(strWalletPass);
    libzcash::SaplingPaymentAddress address2 = pwalletMain->GenerateNewSaplingZKey();

    // Create a new wallet from the existing wallet path
    bool fFirstRun;
    CWallet wallet2(pwalletMain->strWalletFile);
    BOOST_CHECK_EQUAL(DB_LOAD_OK, wallet2.LoadWallet(fFirstRun));

    // Confirm it's not the same as the other wallet
    BOOST_CHECK(pwalletMain != &wallet2);
    BOOST_CHECK(wallet2.HasSaplingSPKM());

    // wallet should have two keys
    wallet2.GetSaplingPaymentAddresses(addrs);
    BOOST_CHECK_EQUAL(2, addrs.size());

    //check we have entries for our payment addresses
    BOOST_CHECK(addrs.count(address));
    BOOST_CHECK(addrs.count(address2));

    // spending key is crypted, so we can't extract valid payment address
    libzcash::SaplingExtendedSpendingKey keyOut;
    BOOST_CHECK(!wallet2.GetSaplingExtendedSpendingKey(address, keyOut));

    // unlock wallet to get spending keys and verify payment addresses
    wallet2.Unlock(strWalletPass);

    BOOST_CHECK(wallet2.GetSaplingExtendedSpendingKey(address, keyOut));
    BOOST_CHECK(address == keyOut.DefaultAddress());

    BOOST_CHECK(wallet2.GetSaplingExtendedSpendingKey(address2, keyOut));
    BOOST_CHECK(address2 == keyOut.DefaultAddress());
}

BOOST_AUTO_TEST_SUITE_END()


#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Regression

#include <fstream>
#include <iostream>

#include <boost/test/unit_test.hpp>

#include <xolotl/core/flux/W111FitFluxHandler.h>
#include <xolotl/core/network/PSIReactionNetwork.h>
#include <xolotl/options/Options.h>
#include <xolotl/test/CommandLine.h>
#include <xolotl/util/MPIUtils.h>

using namespace std;
using namespace xolotl;
using namespace core;
using namespace flux;

using Kokkos::ScopeGuard;
BOOST_GLOBAL_FIXTURE(ScopeGuard);

/**
 * The test suite is responsible for testing the W111FitFluxHandler.
 */
BOOST_AUTO_TEST_SUITE(W111FitFluxHandlerTester_testSuite)

BOOST_AUTO_TEST_CASE(checkComputeIncidentFlux)
{
	// Create the option to create a network
	xolotl::options::Options opts;
	// Create a good parameter file
	std::string parameterFile = "param.txt";
	std::ofstream paramFile(parameterFile);
	paramFile << "netParam=9 0 0 0 0" << std::endl;
	paramFile.close();

	// Create a fake command line to read the options
	test::CommandLine<2> cl{{"fakeXolotlAppNameForTests", parameterFile}};
	util::mpiInit(cl.argc, cl.argv);
	opts.readParams(cl.argc, cl.argv);

	std::remove(parameterFile.c_str());

	// Create a grid
	std::vector<double> grid;
	for (int l = 0; l < 7; l++) {
		grid.push_back((double)l * 1.25);
	}
	// Specify the surface position
	int surfacePos = 0;

	// Create the network
	using NetworkType =
		network::PSIReactionNetwork<network::PSIFullSpeciesList>;
	NetworkType::AmountType maxV = opts.getMaxV();
	NetworkType::AmountType maxI = opts.getMaxI();
	NetworkType::AmountType maxHe = opts.getMaxImpurity();
	NetworkType::AmountType maxD = opts.getMaxD();
	NetworkType::AmountType maxT = opts.getMaxT();
	NetworkType network({maxHe, maxD, maxT, maxV, maxI}, grid.size(), opts);
	network.syncClusterDataOnHost();
	network.getSubpaving().syncZones(plsm::onHost);
	// Get its size
	const int dof = network.getDOF();

	// Create the W111 flux handler
	auto testFitFlux = make_shared<W111FitFluxHandler>(opts);
	// Set the flux amplitude
	testFitFlux->setFluxAmplitude(1.0);
	// Initialize the flux handler
	testFitFlux->initializeFluxHandler(network, surfacePos, grid);

	// Create a time
	double currTime = 1.0;

	// The array of concentration
	double newConcentration[5 * dof];

	// Initialize their values
	for (int i = 0; i < 5 * dof; i++) {
		newConcentration[i] = 0.0;
	}

	// The pointer to the grid point we want
	double* updatedConc = &newConcentration[0];
	double* updatedConcOffset = updatedConc + dof;

	// Update the concentrations at some grid points
	testFitFlux->computeIncidentFlux(
		currTime, updatedConcOffset, 1, surfacePos);
	updatedConcOffset = updatedConc + 2 * dof;
	testFitFlux->computeIncidentFlux(
		currTime, updatedConcOffset, 2, surfacePos);
	updatedConcOffset = updatedConc + 3 * dof;
	testFitFlux->computeIncidentFlux(
		currTime, updatedConcOffset, 3, surfacePos);

	// Check the value at some grid points
	BOOST_REQUIRE_CLOSE(newConcentration[9], 0.3168967, 0.01);
	BOOST_REQUIRE_CLOSE(newConcentration[18], 0.306857, 0.01);
	BOOST_REQUIRE_CLOSE(newConcentration[27], 0.1762458, 0.01);

	// Finalize MPI
	MPI_Finalize();

	return;
}

BOOST_AUTO_TEST_SUITE_END()


/*
 * Copyright (c) 2011-2021, The DART development contributors
 * All rights reserved.
 *
 * The list of contributors can be found at:
 *   https://github.com/dartsim/dart/blob/master/LICENSE
 *
 * This file is provided under the following "BSD-style" License:
 *   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.
 *   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 HOLDER OR
 *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *   AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *   POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include "dart/math/TriMesh.hpp"

#include <Eigen/Geometry>

#include "dart/math/Geometry.hpp"

namespace dart {
namespace math {

//==============================================================================
template <typename S>
TriMesh<S>::TriMesh()
{
  // Do nothing
}

//==============================================================================
template <typename S>
void TriMesh<S>::setTriangles(
    const Vertices& vertices, const Triangles& triangles)
{
  clear();

  this->mVertices = vertices;
  mTriangles = triangles;
}

//==============================================================================
template <typename S>
void TriMesh<S>::computeVertexNormals()
{
  computeTriangleNormals();

  this->mVertexNormals.clear();
  this->mVertexNormals.resize(this->mVertices.size(), Vector3::Zero());

  for (auto i = 0u; i < mTriangles.size(); ++i)
  {
    auto& triangle = mTriangles[i];
    this->mVertexNormals[triangle[0]] += mTriangleNormals[i];
    this->mVertexNormals[triangle[1]] += mTriangleNormals[i];
    this->mVertexNormals[triangle[2]] += mTriangleNormals[i];
  }

  this->normalizeVertexNormals();
}

//==============================================================================
template <typename S>
bool TriMesh<S>::hasTriangles() const
{
  return !mTriangles.empty();
}

//==============================================================================
template <typename S>
bool TriMesh<S>::hasTriangleNormals() const
{
  return hasTriangles() && mTriangles.size() == mTriangleNormals.size();
}

//==============================================================================
template <typename S>
const typename TriMesh<S>::Triangles& TriMesh<S>::getTriangles() const
{
  return mTriangles;
}

//==============================================================================
template <typename S>
const typename TriMesh<S>::Normals& TriMesh<S>::getTriangleNormals() const
{
  return mTriangleNormals;
}

//==============================================================================
template <typename S>
void TriMesh<S>::clear()
{
  mTriangles.clear();
  mTriangleNormals.clear();
  Base::clear();
}

//==============================================================================
template <typename S>
TriMesh<S> TriMesh<S>::operator+(const TriMesh& other) const
{
  return (TriMesh(*this) += other);
}

//==============================================================================
template <typename S>
TriMesh<S>& TriMesh<S>::operator+=(const TriMesh& other)
{
  if (other.isEmpty())
    return *this;

  const auto oldNumVertices = this->mVertices.size();
  const auto oldNumTriangles = mTriangles.size();

  Base::operator+=(other);

  // Insert triangle normals if both meshes have normals. Otherwise, clean the
  // triangle normals.
  if ((!hasTriangles() || hasTriangleNormals()) && other.hasTriangleNormals())
  {
    mTriangleNormals.insert(
        mTriangleNormals.end(),
        other.mTriangleNormals.begin(),
        other.mTriangleNormals.end());
  }
  else
  {
    mTriangleNormals.clear();
  }

  const Triangle offset = Triangle::Constant(oldNumVertices);
  mTriangles.resize(mTriangles.size() + other.mTriangles.size());
  for (auto i = 0u; i < other.mTriangles.size(); ++i)
  {
    mTriangles[i + oldNumTriangles] = other.mTriangles[i] + offset;
  }

  return *this;
}

//==============================================================================
template <typename S>
std::shared_ptr<TriMesh<S>> TriMesh<S>::generateConvexHull(bool optimize) const
{
  auto triangles = Triangles();
  auto vertices = Vertices();
  std::tie(vertices, triangles)
      = computeConvexHull3D<S, Index>(this->mVertices, optimize);

  auto mesh = std::make_shared<TriMesh<S>>();
  mesh->setTriangles(vertices, triangles);

  return mesh;
}

//==============================================================================
template <typename S>
void TriMesh<S>::computeTriangleNormals()
{
  mTriangleNormals.resize(mTriangles.size());

  for (auto i = 0u; i < mTriangles.size(); ++i)
  {
    auto& triangle = mTriangles[i];
    const Vector3 v01
        = this->mVertices[triangle[1]] - this->mVertices[triangle[0]];
    const Vector3 v02
        = this->mVertices[triangle[2]] - this->mVertices[triangle[0]];
    mTriangleNormals[i] = v01.cross(v02);
  }

  normalizeTriangleNormals();
}

//==============================================================================
template <typename S>
void TriMesh<S>::normalizeTriangleNormals()
{
  for (auto& normal : mTriangleNormals)
  {
    normal.normalize();
  }
}

} // namespace math
} // namespace dart


#ifndef MODEL_TRACKER_HPP
#define MODEL_TRACKER_HPP

#include <set>
#include <boost/lexical_cast.hpp>

#include "../exrpc/exrpc.hpp"
/*
#include "frovedis/ml/glm/linear_model.hpp"
#include "frovedis/ml/recommendation/matrix_factorization_model.hpp"
#include "frovedis/ml/clustering/kmeans.hpp"
#include "frovedis/ml/tree/tree_model.hpp"
#include "frovedis/ml/fm/model.hpp"
#include "frovedis/ml/nb/nb_model.hpp"
#include "frovedis/ml/fpm/fp_growth_model.hpp"
#include "frovedis/ml/clustering/spectral_clustering.hpp"
#include "frovedis/ml/clustering/agglomerative.hpp"
*/

using namespace frovedis;

enum MODEL_KIND {
  GLM = 0,
  LR,
  SVM,
  LNRM,
  MFM,
  KMEANS,
  DTM,
  NBM,
  FMM,
  FPM,
  FPR,
  ACM,
  SCM,
  SEM,
  SPARSE_CONV_INFO,
  W2V,
  DBSCAN,
  KNN,
  KNC,
  KNR,
  LDA,
  LDASP,
  RFM,
  GBT,
  SVR,
  KSVC,
  RR,
  LSR,
  GMM,
  STANDARDSCALER
};

enum MAT_KIND {
  RMJR = 1,
  CMJR = 2,
  BCLC = 3,
  SCRS = 4,
  SCCS = 5,
  SELL = 6,
  SHYBRID = 7,
  RMJR_L = 101,
  CMJR_L = 102,
  BCLC_L = 103,
  SCRS_L = 104,
  SCCS_L = 105,
  SELL_L = 106,
  SHYBRID_L = 107
};

enum DTYPE {
  INT = 1,
  LONG = 2,
  FLOAT = 3,
  DOUBLE = 4,
  STRING = 5,
  BOOL = 6,
  ULONG = 7,
  WORDS = 8
};

enum OPTYPE {
  // --- conditional ---
  EQ = 1,
  NE = 2,
  GT = 3,
  GE = 4,
  LT = 5,
  LE = 6,
  // --- special conditional ---
  AND = 11,
  OR = 12,
  NOT = 13,
  LIKE = 14,
  NLIKE = 15,
  ISNULL = 16,
  ISNOTNULL = 17,
  // --- mathematical ---
  ADD = 21,
  SUB = 22,
  MUL = 23,
  IDIV = 24,
  FDIV = 25,
  MOD = 26,
  POW = 27,
  // --- aggregator ---
  aMAX = 41,
  aMIN = 42,
  aSUM = 43,
  aAVG = 44,
  aVAR = 45,
  aSEM = 46,
  aSTD = 47,
  aMAD = 48,
  aCNT = 49,
  aSIZE = 50,
};

// [MODEL_ID] => [MODEL_KIND, MODEL_PTR]
extern std::map<int,std::pair<MODEL_KIND,exrpc_ptr_t>> model_table;
extern std::set<int> deleted_model_tracker;
extern std::set<int> under_training_model_tracker;

void register_model(int mid, MODEL_KIND m, exrpc_ptr_t mptr);
void register_for_train(int mid);
void unregister_from_train(int mid);
bool is_deleted(int mid);
bool is_registered_model(int mid);
bool is_under_training(int mid);

void finalize_model_table();
void finalize_trackers();
void cleanup_frovedis_server();
int get_numeric_dtype(const std::string& dt);
std::string get_string_dtype(short dt);

// retuns the model head for the requested registered model id
template <class M>
M* get_model_ptr(int mid) {
  //std::cout<<"inside get model ptr \n\n";
  if(model_table.find(mid) == model_table.end()) { // if not registered

  //std::cout<<"not registered \n\n";
    if(!is_under_training(mid)) { // if not under training
     // std::cout<<"not under training\n\n";
      std::string message = "request for either non-registered or deleted model: [";
      message += boost::lexical_cast<std::string>(mid) + " ]\n";
      REPORT_ERROR(USER_ERROR, message);
    }
    else while(is_under_training(mid)); // waits until training is completed
  }
  auto p = model_table[mid];
  return reinterpret_cast<M*>(p.second);
}

// convert a numeric string to number
template <class T>
T do_cast (const std::string& data) {
  T c_data = 0;
  try {
    c_data = boost::lexical_cast<T>(data);
  }
  catch (const boost::bad_lexical_cast &excpt) {
    REPORT_ERROR(USER_ERROR, "invalid type for casting: " + data);
  }
  return c_data;
}

template <>
bool do_cast<bool>(const std::string& data);

#endif


#include <objectif-lune/Singleton.hpp>
#include <iostream>
#include <math.h>
#include <boost/thread.hpp>

int main(int argc, char *argv[])
{
	std::cout << "starting example server" << std::endl;

	objectifLune::Server* server = objectifLune::Singleton::Get();
	server->waitForConnections();

	float magic = 42;

	server->registerVariable("tweak-me-variable", &magic, 0, 100, "pretty please");
	server->registerVariable("pure-magic", &magic, 0, 100, "asdfasdfasdf");

	int delay = 50;
	unsigned long frameCounter = 0;
	while(true)
	{
		server->scalar("magic value", magic);
//		server->scalar("frame time", 20.0f + (rand() & 100) / 20);
//		server->scalar("long time field", 2000.0f + (rand() & 1000) / 20);
//		server->scalar("textures loaded", (rand() & 9));
//		server->scalar("lucky number", (rand() & 99));
//		server->scalar("fraction of a whole", (float)(rand() & 99) /3.0f);
//
//		server->info("Uh, everything's under control. Situation normal.");

//		for (unsigned int i = 0; i < 10; i++) {
//			server->data(frameCounter, "acceleration", sin(frameCounter / 100.0f));
//						server->data(frameCounter, "frame time", (float)(rand() % 999999) / 100.0f);
//			server->data(frameCounter, "ASDFYygj/", sin(frameCounter / 100.0f) + 1.2f);
//			server->data(frameCounter, "long name is looooooooooooooooong", sin(frameCounter / 100.0f));
//			frameCounter++;
//		}
		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));

//		server->trace("1 this is a trace message");
//		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));
//		server->debug("2 This debug message is a tad longer than the other messages, because it is very important and hence needs the extra space to express its meaning, position in the universe, and everything else.");
//		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));
//		server->info("3 This service is kindly provided by the intergallactic time traveling agency");
//		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));
//		server->warn("4 Final Warning!");
//		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));
//		server->error("5 There is an error in this message");
//		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));
//		server->fatal("6 Fatal message is fatal");
//		boost::this_thread::sleep(boost::posix_time::milliseconds(delay));
	}
}


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//
#include "ClStackWorkload.hpp"
#include "ClWorkloadUtils.hpp"
#include <aclCommon/ArmComputeTensorUtils.hpp>
#include <backendsCommon/CpuTensorHandle.hpp>
#include <cl/ClTensorHandle.hpp>
#include <cl/ClLayerSupport.hpp>

#include <arm_compute/core/Types.h>

#include <boost/numeric/conversion/cast.hpp>
#include <boost/polymorphic_pointer_cast.hpp>

namespace armnn
{
using namespace armcomputetensorutils;

namespace
{
int CalcAxis(const unsigned int axis, const unsigned int inputDimensions)
{
    const int intAxis = boost::numeric_cast<int>(axis);
    return boost::numeric_cast<int>(inputDimensions) - intAxis;
}
} //namespace

arm_compute::Status ClStackWorkloadValidate(const std::vector<const TensorInfo*>& inputs,
                                            const TensorInfo& output,
                                            const StackDescriptor& descriptor)
{
    std::vector<arm_compute::ITensorInfo*> aclInputPtrs;
    arm_compute::TensorInfo aclInputInfo;
    for (const TensorInfo* input : inputs)
    {
        aclInputInfo = BuildArmComputeTensorInfo(*input);
        aclInputPtrs.emplace_back(&aclInputInfo);
    }
    const arm_compute::TensorInfo aclOutputInfo = BuildArmComputeTensorInfo(output);

    int aclAxis = CalcAxis(descriptor.m_Axis, descriptor.m_InputShape.GetNumDimensions());

    return arm_compute::CLStackLayer::validate(aclInputPtrs, aclAxis, &aclOutputInfo);
}

ClStackWorkload::ClStackWorkload(const StackQueueDescriptor& descriptor, const WorkloadInfo& info)
: BaseWorkload<StackQueueDescriptor>(descriptor, info)
{
    std::vector<arm_compute::ICLTensor*> aclInputs;
    for (auto input : m_Data.m_Inputs)
    {
        arm_compute::ICLTensor& aclInput = boost::polymorphic_pointer_downcast<IClTensorHandle>(input)->GetTensor();
        aclInputs.emplace_back(&aclInput);
    }
    arm_compute::ICLTensor& output = boost::polymorphic_pointer_downcast<IClTensorHandle>(
                                                                         m_Data.m_Outputs[0])->GetTensor();

    m_Layer.reset(new arm_compute::CLStackLayer());
    int aclAxis = CalcAxis(descriptor.m_Parameters.m_Axis, descriptor.m_Parameters.m_InputShape.GetNumDimensions());
    m_Layer->configure(aclInputs, aclAxis, &output);
}

void ClStackWorkload::Execute() const
{
    if (m_Layer)
    {
        ARMNN_SCOPED_PROFILING_EVENT_CL("ClStackWorkload_Execute");
        m_Layer->run();
    }
}

} //namespace armnn


#include <mw/mmr/MMRUtil.h>
#include <mw/crypto/Hasher.h>
#include <mw/util/BitUtil.h>

#include <boost/dynamic_bitset.hpp>
#include <cmath>

using namespace mmr;

mw::Hash MMRUtil::CalcParentHash(const Index& index, const mw::Hash& left_hash, const mw::Hash& right_hash)
{
    return Hasher()
        .Append<uint64_t>(index.GetPosition())
        .Append(left_hash)
        .Append(right_hash)
        .hash();
}

BitSet MMRUtil::BuildCompactBitSet(const uint64_t num_leaves, const BitSet& unspent_leaf_indices)
{
    BitSet compactable_node_indices(num_leaves * 2);

    boost::dynamic_bitset<> prunable_nodes(num_leaves * 2);

    LeafIndex leaf_idx = LeafIndex::At(0);
    while (leaf_idx.Get() < num_leaves) {
        if (unspent_leaf_indices.size() > leaf_idx.Get() && !unspent_leaf_indices.test(leaf_idx.Get())) {
            prunable_nodes.set(leaf_idx.GetPosition());
        }

        leaf_idx = leaf_idx.Next();
    }

    LeafIndex next_leaf = LeafIndex::At(num_leaves);
    Index last_node = Index::At(next_leaf.GetPosition() - 1);

    uint64_t height = 1;
    while ((std::pow(2, height + 1) - 2) <= next_leaf.GetPosition()) {
        SiblingIter iter(height, last_node);
        while (iter.Next()) {
            Index right_child = iter.Get().GetRightChild();
            if (prunable_nodes.test(right_child.GetPosition())) {
                Index left_child = iter.Get().GetLeftChild();
                if (prunable_nodes.test(left_child.GetPosition())) {
                    compactable_node_indices.set(right_child.GetPosition());
                    compactable_node_indices.set(left_child.GetPosition());
                    prunable_nodes.set(iter.Get().GetPosition());
                }
            }
        }

        ++height;
    }

    return compactable_node_indices;
}

BitSet MMRUtil::DiffCompactBitSet(const BitSet& prev_compact, const BitSet& new_compact)
{
    BitSet diff;

    for (size_t i = 0; i < new_compact.size(); i++) {
        if (prev_compact.size() > i && prev_compact.test(i)) {
            assert(new_compact.test(i));
            continue;
        }

        diff.bitset.push_back(new_compact.test(i));
    }

    return diff;
}

/// <summary>
/// An MMR can be rebuilt from the leaves and a small set of carefully chosen parent hashes.
/// This calculates the positions of those parent hashes.
/// </summary>
/// <param name="unspent_leaf_indices">The unspent leaf indices.</param>
/// <returns>The pruned parent positions.</returns>
BitSet MMRUtil::CalcPrunedParents(const BitSet& unspent_leaf_indices)
{
    BitSet ret(unspent_leaf_indices.size() * 2);

    LeafIndex leaf_idx = LeafIndex::At(0);
    while (leaf_idx.Get() < unspent_leaf_indices.size()) {
        if (!unspent_leaf_indices.test(leaf_idx.Get())) {
            ret.set(leaf_idx.GetPosition());
        }

        leaf_idx = leaf_idx.Next();
    }

    Index last_node = LeafIndex::At(unspent_leaf_indices.size()).GetNodeIndex();

    uint64_t height = 1;
    while ((std::pow(2, height + 1) - 2) <= last_node.GetPosition()) {
        SiblingIter iter(height, last_node);
        while (iter.Next()) {
            Index right_child = iter.Get().GetRightChild();
            if (ret.test(right_child.GetPosition())) {
                Index left_child = iter.Get().GetLeftChild();
                if (ret.test(left_child.GetPosition())) {
                    ret.set(right_child.GetPosition(), false);
                    ret.set(left_child.GetPosition(), false);
                    ret.set(iter.Get().GetPosition());
                }
            }
        }

        ++height;
    }

    return ret;
}

SiblingIter::SiblingIter(const uint64_t height, const Index& last_node)
    : m_height(height),
    m_lastNode(last_node),
    m_baseInc((uint64_t)std::pow(2, height + 1) - 1),
    m_siblingNum(0),
    m_next()
{
}

bool SiblingIter::Next()
{
    if (m_siblingNum == 0) {
        m_next = Index(m_baseInc - 1, m_height);
    } else {
        uint64_t increment = m_baseInc + BitUtil::CountRightmostZeros(m_siblingNum);
        m_next = Index(m_next.GetPosition() + increment, m_height);
    }

    ++m_siblingNum;
    return m_next <= m_lastNode;
};