#include <iostream>
#include <sstream>
#include <cfloat>

#include <armadillo>

#include <cpp-argparse/OptionParser.h>
#include <besiq/io/covariates.hpp>
#include <besiq/stats/snp_count.hpp>

#include <plink/plink_file.hpp>
#include <dcdflib/libdcdf.hpp>

#include "gene_environment.hpp"

using namespace arma;
using namespace optparse;

const std::string USAGE = "besiq-lars [OPTIONS] plink_file";
const std::string DESCRIPTION = "Uses the LARS algorithm to find important loci.";
const std::string VERSION = "besiq 0.0.1";
const std::string EPILOG = "";

class lars_variables;
class lars_result;
arma::vec lars(lars_variables &variable_set, lars_result &result, size_t max_vars = 15, bool lasso = true, double threshold = 1.0);

/**
 * This class is responsible for keeping track which variables
 * are currently in the active and inactive sets, excluding those
 * that are currently in the ignored set.
 */
class active_set
{
public:
    /**
     * Constructor.
     *
     * @param size Total number of variables ([0...size-1]).
     */
    active_set(size_t size)
    {
        m_active = std::set<unsigned int>( );
        m_ignored = std::set<unsigned int>( );
        m_size = size;
        m_last_added = -1;
    }

    /**
     * Add a variable to the active set. It is assumed that this
     * variable is not ignored.
     *
     * @param x Index of variable to add.
     */
    void add(unsigned int x)
    {
        m_last_added = x;
        m_active.insert( x );
    }

    /**
     * Ignores a parameters, ignored parameters are neither
     * active or inactive.
     *
     * @param x Index of variable to ignore.
     */
    void ignore(unsigned int x)
    {
        m_active.erase( x );
        m_ignored.insert( x );
    }

    /**
     * Move a variable from active to inactive.
     *
     * @param Index of the variable to move.
     */
    void drop(unsigned int x)
    {
        m_active.erase( x );
    }

    /**
     * Returns a vector of indicies of the currently active variables
     * suitable for the arma package.
     *
     * @return A vector of indicies of active variables.
     */
    arma::uvec get_active() const
    {
        arma::uvec active = arma::zeros<arma::uvec>( m_active.size( ) );
        std::set<unsigned int>::const_iterator it;
        unsigned int index = 0;
        for(it = m_active.begin( ); it != m_active.end( ); ++it)
        {
            active[ index ] = *it;
            index++;
        }

        return active;
    }

    /**
     * Returns a vector of indicies of the currently inactive variables
     * suitable for the arma package.
     *
     * @return A vector of indicies of inactive variables.
     */
    arma::uvec get_inactive() const
    {
        arma::uvec inactive = arma::zeros<arma::uvec>( m_size - m_active.size( ) - m_ignored.size( ) );
        unsigned int index = 0;
        for(int i = 0; i < m_size; i++)
        {
            if( m_active.count( i ) > 0 || m_ignored.count( i ) > 0 )
            {
                continue;
            }

            inactive[ index ] = i;
            index++;
        }

        return inactive;
    }

    /**
     * Returns the last added variable to the active set.
     *
     * @return The last added variable to the active set.
     */
    unsigned int get_last_added() const
    {
        return m_last_added;
    }

    /**
     * Returns the number of active variables.
     *
     * @return the number of active variables.

*/
    size_t size() const
    {
        return m_active.size( );
    }

private:
    /**
     * Total number of variables.
     */
    size_t m_size;

    /**
     * Set of active variables.
     */
    std::set<unsigned int> m_active;

    /**
     * Set of ignored variables.
     */
    std::set<unsigned int> m_ignored;

    /**
     * Last added index.
     */
    unsigned int m_last_added;
};

struct knot_info
{
    std::string variable;
    unsigned int variable_index;
    double lambda;
    arma::uvec active;
    arma::mat X_active;
    arma::vec beta_active;
    arma::mat X_h0;
};

struct lars_knot
{
    knot_info info;
    bool remove;
    double T;
    double pvalue;
    double explained_var;
    bool valid_p;
};

class lars_result
{
public:
    lars_result(const arma::vec &phenotype, bool calculate_p) :
        m_phenotype( phenotype ),
        m_calculate_p( calculate_p )
    {
        m_pheno_var = arma::accu( pow( phenotype, 2 ) ) / (phenotype.n_elem - 1);
    }

    void init(double lambda)
    {
        lars_knot start;
        start.info.variable = "NULL";
        start.info.variable_index = -1;
        start.info.lambda = lambda;
        start.remove = false;
        start.T = 0.0;
        start.pvalue = 1.0;
        start.explained_var = 0.0;
        start.valid_p = false;

        m_knots.push_back( start );
    }

    void add_knot(bool remove, knot_info &info, double model_var)
    {
        lars_knot new_knot;
        new_knot.info = info;
        new_knot.remove = remove;
        new_knot.explained_var = 1.0 - model_var / m_pheno_var;

        if( remove || !m_calculate_p )
        {
            new_knot.T = 0;
            new_knot.pvalue = 1.0;

            m_knots.push_back( new_knot );

            return;
        }

        /* Calculate p for new beta and add last component of the path */
        double cur_cor = dot( info.X_active * info.beta_active, m_phenotype );

        /* Compute h0 */
        double prev_cor = 0;
        if( info.X_active.n_cols > 1 )
        {
            null_lars null( info.X_h0, m_phenotype );
            lars_result null_result( m_phenotype, false );
            lars( null, null_result, info.X_h0.n_cols, true );

            std::vector<lars_knot> null_knot = null_result.get_knots( );

            unsigned int knot_index = -1;
            for(int i = 1; i < null_knot.size( ); i++)
            {
                if( info.lambda < null_knot[ i - 1 ].info.lambda && info.lambda > null_knot[ i ].info.lambda )
                {
                    knot_index = i;
                }
            }

            lars_knot &prev = null_knot[ knot_index - 1 ];
            lars_knot &next = null_knot[ knot_index ];

            arma::vec beta_prev = arma::zeros<arma::vec>( info.X_h0.n_cols );
            align_beta( prev.info.beta_active, prev.info.active, &beta_prev );

            arma::vec beta_next = arma::zeros<arma::vec>( info.X_h0.n_cols );
            align_beta( next.info.beta_active, next.info.active, &beta_next );

            double lambda_prev = prev.info.lambda;
            double lambda_next = next.info.lambda;

            arma::vec k = (beta_next - beta_prev)/(lambda_next - lambda_prev);
            arma::vec beta_h0 = beta_prev + (info.lambda - lambda_prev) * k;

            prev_cor = dot( info.X_h0 * beta_h0, m_phenotype );
        }

        double T = (cur_cor - prev_cor ) / model_var;
        double p = 1.0;
        if( T > 0.0 )
        {
            p = 1 - exp_cdf( T, 1.0 );
            new_knot.valid_p = true;
        }
        else
        {
            new_knot.valid_p = false;
        }

        new_knot.T = T;
        new_knot.pvalue = p;

        m_knots.push_back( new_knot );
    }

    std::vector<lars_knot> get_knots()
    {
        return m_knots;
    }

    void align_beta(arma::vec &beta, arma::uvec &active, arma::vec *aligned_beta)
    {
        for(int i = 0; i < active.n_elem; i++)
        {
            (*aligned_beta)[ active[ i ] ] = beta[ i ];
        }
    }

    void write_result(std::ostream &out, bool only_pvalues)
    {
        if( only_pvalues )
        {
            out << "step\tvariable\taction\tbeta\tT\tp\tlambda\tbeta_sum\texplained_var\n";
            for(int i = 1; i < m_knots.size( ); i++)
            {
                lars_knot &knot = m_knots[ i ];
                std::string action = "add";
                arma::vec this_beta = knot.info.beta_active.elem( find( knot.info.active == knot.info.variable_index ) );
                if( knot.remove )
                {
                    action = "remove";
                    this_beta = arma::zeros<arma::vec>( 1 );
                }

                out << i << "\t" <<
                    knot.info.variable << "\t" <<
                    action << "\t" <<
                    this_beta[ 0 ] << "\t" <<
                    knot.T << "\t" <<
                    knot.pvalue << "\t" <<
                    knot.info.lambda <<  "\t" <<
                    arma::sum( arma::abs( knot.info.beta_active ) ) <<  "\t" <<
                    knot.explained_var << "\n";
            }
        }
        else
        {
            int var = 0;
            std::map<unsigned int, unsigned int> new_pos;
            std::vector<std::string> name;
            for(int i = 1; i < m_knots.size( ); i++)
            {
                lars_knot &knot = m_knots[ i ];
                if( knot.remove )
                {
                    continue;
                }

                if( new_pos.count( knot.info.variable_index ) <= 0 )
                {
                    new_pos[ knot.info.variable_index ] = var;
                    name.push_back( knot.info.variable );
                    var++;
                }
            }

            out << "step\tvariable\tT\tp\tbeta_sum\texplained_var\tlambda";
            for(int i = 0; i < name.size( ); i++)
            {
                out <<  "\t" << name[ i ];
            }
            out << "\n";

            for(int i = 1; i < m_knots.size( ); i++)
            {
                lars_knot &knot = m_knots[ i ];
                arma::vec beta = arma::zeros<arma::vec>( new_pos.size( ) );

                for(int j = 0; j < knot.info.active.n_elem; j++)
                {
                    beta[ new_pos[ knot.info.active[ j ] ] ] = knot.info.beta_active[ j ];
                }

                out << i << "\t" << knot.info.variable << "\t" << knot.T << "\t" << knot.pvalue << "\t" << arma::sum( arma::abs( knot.info.beta_active ) ) << "\t" << knot.explained_var << "\t" << knot.info.lambda << "\t";
                for(int j = 0; j < beta.n_elem; j++)
                {
                    out << "\t" << beta[ j ];
                }
                out << "\n";
            }

        }
    }

private:
    arma::vec m_phenotype;
    bool m_calculate_p;
    double m_pheno_var;
    std::vector<lars_knot> m_knots;
};

arma::vec
nice_division(arma::vec &a, arma::vec &b)
{
    b.elem( find( b < DBL_MIN ) ).fill( DBL_MIN );
    arma::vec x = a / b;
    x.elem( find( x <= 0 ) ).fill( DBL_MAX );

    return x;
}

/**
 * Solves the lasso problem for a given lambda. Used for computing the
 * null model during significance testing. The algorithm is currently
 * a pathwise coordinate descent.
 *
 * @param X Covariates.
 * @param y Outcome.
 * @param lambda Shrinkage factor.
 * @param start Starting values for beta.
 * @max_num_iter Maximum number of iterations before giving up.
 */
arma::vec optimize_lars_gd(const arma::mat &X, const arma::mat &y, double lambda, const arma::vec &start, size_t max_num_iter = 50)
{
    arma::vec beta = start;
    arma::vec r = y - X * beta;
    double prev_rss = 0;
    double cur_rss = sum( r % r );
    int num_iter = 0;

    while( std::abs( prev_rss - cur_rss ) / prev_rss > 1e-20 && num_iter++ < max_num_iter )
    {
        prev_rss = cur_rss;
        for(int j = 0; j < X.n_cols; j++)
        {
            double beta_star = arma::dot( X.col( j ), r ) + beta[ j ];
            double update = std::abs( beta_star ) - lambda;
            update = (update > 0) ? update : 0;

            double new_beta = (beta_star > 0) ? update : -update;
            double beta_increase = new_beta - beta[ j ];

            beta[ j ] = new_beta;

            if( std::abs( beta_increase ) > 0 )
            {
                r = r - X.col( j ) * beta_increase;
            }
        }
        cur_rss = sum( r % r );
    }

    return beta;
}

/**
 * Finds the maximum correlation and returns its index.
 *
 * @param v A vector of correlations.
 * @param inactive A subset of indicies to consider.
 * @param max_index The index of the maximum value in v will be stored here.
 *
 * @reutrn The maximum value of v restricted to the indicies in inactive.
 */
double
find_max(const arma::vec &v, const arma::uvec &inactive, unsigned int *max_index)
{
    double max_value = 0;
    *max_index = v.n_elem;
    for(int i = 0; i < inactive.n_elem; i++)
    {
        unsigned int cur_index = inactive[ i ];
        double cur_value = v[ cur_index ];

        // Notice the order of these two statements
        *max_index = ( cur_value > max_value ) ? cur_index : *max_index;
        max_value = ( cur_value > max_value ) ? cur_value : max_value;
    }

    return max_value;
}

arma::vec
lars(lars_variables &variable_set, lars_result &result, size_t max_vars, bool lasso, double threshold)
{
    arma::vec phenotype = variable_set.get_centered_phenotype( );
    size_t n = variable_set.get_num_samples( );
    size_t m = variable_set.get_num_variables( );

    arma::vec mu = arma::zeros<arma::vec>( n );
    arma::vec beta = arma::zeros<arma::vec>( m );
    arma::vec c = arma::zeros<arma::vec>( m );

    active_set active( m );
    arma::uvec inactive;
    arma::uvec drop;
    arma::uvec prev_active;
    double eps = 1e-10;

    /* It is the first lambda that needs to be added not the last... */
    variable_set.calculate_cor( phenotype - mu, c );
    result.init( arma::max( abs( c ) ) );

    int i = 0;
    while( active.size( ) < std::min( max_vars, m ) )
    {
        variable_set.calculate_cor( phenotype - mu, c );
        arma::vec cabs = abs( c );
        unsigned int max_index;
        double C = find_max( cabs, active.get_inactive( ), &max_index );

        if( C < eps )
        {
            break;
        }

        /* If previous step was a drop we should not add
         * more variables in this step only increase beta. */
        bool prev_was_drop = drop.n_elem > 0;
        if( !prev_was_drop )
        {
            prev_active = active.get_active( );
            active.add( max_index );
        }
        inactive = active.get_inactive( );

        /* Create the active matrix */
        arma::mat X_active = variable_set.get_active( active.get_active( ) );

        /* Equation 2.4 */
        arma::vec s = sign( c.elem( active.get_active( ) ) );

        /* Equation 2.5 */
        arma::mat G = X_active.t( ) * X_active;
        arma::mat Ginv;
        if( !pinv( Ginv, G ) )
        {
            active.ignore( max_index );
            continue;
        }
        double A = 1.0 / sqrt( arma::accu( s.t( ) * Ginv * s ) );

        /* Equation 2.6 */
        arma::vec w = A * Ginv * s;
        arma::vec u = X_active * w;

        double gamma = C / A;
        if( active.size( ) < m )
        {
            /* Equation 2.11 */
            arma::vec a = variable_set.eig_prod( u );

            /* Equation 2.13 */
            arma::vec cc = c.elem( inactive );
            arma::vec ac = a.elem( inactive );

            arma::vec a1 = C - cc;
            arma::vec b1 = A - ac;

            arma::vec gn = nice_division( a1, b1 );

            arma::vec a2 = C + cc;
            arma::vec b2 = A + ac;
            arma::vec gp = nice_division( a2, b2 );

            gamma = std::min( gn.min( ), gp.min( ) );
        }

        if( lasso )
        {
            drop.clear( );
            /* Equation 3.4 */
            arma::vec gammaj = -beta.elem( active.get_active( ) ) / w;

            /* Equation 3.5 */
            arma::uvec valid_elems = arma::find( gammaj > eps );
            if( valid_elems.n_elem > 0 )
            {
                double gammatilde = std::min( arma::min( gammaj.elem( valid_elems ) ), gamma );

                if( gammatilde < gamma )
                {
                    gamma = gammatilde;
                    drop = active.get_active( ).elem( find( gammaj == gammatilde ) );
                    assert( drop.n_elem == 1 );
                }
            }
        }

        beta.elem( active.get_active( ) ) = beta.elem( active.get_active( ) ) + w * gamma;
        mu = mu + gamma * u;

        bool cur_is_drop = drop.n_elem > 0;
        knot_info info;
        if( cur_is_drop )
        {
            /* This knot is a deletion, so zero out beta and remove the deleted
             * variable from the active set, and in the next step we need to
             * increase beta to get to the knot of the newly added variable. */
            beta.elem( drop ).fill( 0.0 );
            active.drop( drop[ 0 ] );
        }

        /**
         * If addition we use the max index, if deletion we use the
         * drop index, if previous was a drop we use the last added
         * variable because it is that one we are moving forward with.
         */
        if( !cur_is_drop && !prev_was_drop )
        {
            info.variable_index = max_index;
        }
        else if( cur_is_drop )
        {
            info.variable_index = drop[ 0 ];
        }
        else
        {
            info.variable_index = active.get_last_added( );
        }
        info.variable = variable_set.get_name( info.variable_index );

        info.active = active.get_active( );
        info.beta_active = beta.elem( info.active );
        info.X_active = !cur_is_drop ? X_active : variable_set.get_active( info.active );

        double model_var = sum( pow( phenotype - mu, 2 ) ) / (n - 1 - active.size( ) );
        arma::vec r = phenotype - mu;
        s = sign( c.elem( active.get_active( ) ) );
        info.lambda = arma::max( s % ( info.X_active.t( ) * r ) );

        if( prev_active.n_elem > 0 )
        {
            info.X_h0 = variable_set.get_active( prev_active );
        }

        result.add_knot( cur_is_drop, info, model_var );

        i++;
    }

    return beta;
}

int
main(int argc, char *argv[])
{
    OptionParser parser = OptionParser( ).usage( USAGE )
                                         .version( VERSION )
                                         .description( DESCRIPTION )
                                         .epilog( EPILOG );

    parser.add_option( "-p", "--pheno" ).help( "Read phenotypes from this file instead of a plink file." );
    parser.add_option( "-e", "--mpheno" ).help( "Name of the phenotype that you want to read (if there are more than one in the phenotype file)." );
    parser.add_option( "-c", "--cov" ).action( "store" ).type( "string" ).metavar( "filename" ).help( "Performs the analysis by including the covariates in this file." );
    parser.add_option( "-o", "--out" ).help( "The output file that will contain the results (binary)." );
    parser.add_option( "-m", "--max-variables" ).help( "Maximum number of variables in the model" ).set_default( 10 );
    parser.add_option( "-t", "--threshold" ).help( "Stop after a variable has a p-value less than this threshold." ).set_default( 1.0 );
    parser.add_option( "-a", "--maf" ).help( "Filter variants with maf (it is important to set this to avoid interactions with monotonic snps)" ).set_default( 0.05 );
    parser.add_option( "--only-pvalues" ).help( "Only output the beta that enters in each step along with its p-value." ).action( "store_true" );
    parser.add_option( "--only-main" ).help( "Only output the main effects." ).action( "store_true" );

    Values options = parser.parse_args( argc, argv );
    if( parser.args( ).size( ) != 1 )
    {
        parser.print_help( );
        exit( 1 );
    }

    std::ios_base::sync_with_stdio( false );

    /* Read all genotypes */
    plink_file_ptr genotype_file = open_plink_file( parser.args( )[ 0 ] );
    genotype_matrix_ptr genotypes = create_filtered_genotype_matrix( genotype_file, (float) options.get( "maf" ) );
    std::vector<std::string> order = genotype_file->get_sample_iids( );

    /* Make error streams separate from stdout */
    arma::set_stream_err1( std::cerr );
    arma::set_stream_err2( std::cerr );

    /* Parse phenotypes */
    arma::uvec cov_missing = arma::zeros<arma::uvec>( genotype_file->get_samples( ).size( ) );
    arma::uvec pheno_missing = arma::zeros<arma::uvec>( genotype_file->get_samples( ).size( ) );
    arma::vec phenotype;
    arma::mat cov;
    std::vector<std::string> cov_names;
    if( options.is_set( "pheno" ) )
    {
        std::ifstream phenotype_file( options[ "pheno" ].c_str( ) );
        phenotype = parse_phenotypes( phenotype_file, pheno_missing, order, options[ "mpheno" ] );
    }
    else
    {
        phenotype = create_phenotype_vector( genotype_file->get_samples( ), pheno_missing );
    }
    if( options.is_set( "cov" ) )
    {
        std::ifstream covariate_file( options[ "cov" ].c_str( ) );
        cov = parse_covariate_matrix( covariate_file, cov_missing, order, &cov_names );
    }

    /* Open output stream */
    std::ofstream output_file;
    if( options.is_set( "out" ) )
    {
        output_file.open( options[ "out" ].c_str( ) );
    }
    std::ostream &out = options.is_set( "out" ) ? output_file : std::cout;

    bool only_pvalues = options.is_set( "only_pvalues" );
    bool only_main = options.is_set( "only_main" );

    gene_environment variable_set( genotypes, cov, phenotype, cov_names, only_main );
    variable_set.impute_missing( );
    lars_result result( variable_set.get_centered_phenotype( ), true );
    lars( variable_set, result, (int) options.get( "max_variables" ), (double) options.get( "threshold" ) );
    result.write_result( out, only_pvalues );

    return 0;
}


// Copyright (c) Jeremy Siek 2001
// Copyright (c) Douglas Gregor 2004
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// NOTE: this final is generated by libs/graph/doc/biconnected_components.w

#ifndef BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP
#define BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP

#include <stack>
#include <vector>
#include <algorithm> // for std::min and std::max
#include <boost/config.hpp>
#include <boost/limits.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/concept/assert.hpp>

namespace boost
{
  namespace detail
  {
    template<typename ComponentMap, typename DiscoverTimeMap,
             typename LowPointMap, typename PredecessorMap,
             typename OutputIterator, typename Stack,
             typename ArticulationVector, typename IndexMap,
             typename DFSVisitor>
    struct biconnected_components_visitor : public dfs_visitor<>
    {
      biconnected_components_visitor
        (ComponentMap comp, std::size_t& c,
         std::size_t& children_of_root, DiscoverTimeMap dtm,
         std::size_t& dfs_time, LowPointMap lowpt, PredecessorMap pred,
         OutputIterator out, Stack& S,
         ArticulationVector& is_articulation_point, IndexMap index_map,
         DFSVisitor vis)
          : comp(comp), c(c), children_of_root(children_of_root),
            dtm(dtm), dfs_time(dfs_time), lowpt(lowpt),
            pred(pred), out(out), S(S),
            is_articulation_point(is_articulation_point),
            index_map(index_map), vis(vis) { }

      template <typename Vertex, typename Graph>
      void initialize_vertex(const Vertex& u, Graph& g)
      {
        put(pred, u, u);
        vis.initialize_vertex(u, g);
      }

      template <typename Vertex, typename Graph>
      void start_vertex(const Vertex& u, Graph& g)
      {
        children_of_root = 0;
        vis.start_vertex(u, g);
      }

      template <typename Vertex, typename Graph>
      void discover_vertex(const Vertex& u, Graph& g)
      {
        put(dtm, u, ++dfs_time);
        put(lowpt, u, get(dtm, u));
        vis.discover_vertex(u, g);
      }

      template <typename Edge, typename Graph>
      void examine_edge(const Edge& e, Graph& g)
      {
        vis.examine_edge(e, g);
      }

      template <typename Edge, typename Graph>
      void tree_edge(const Edge& e, Graph& g)
      {
        typename boost::graph_traits<Graph>::vertex_descriptor src = source(e, g);
        typename boost::graph_traits<Graph>::vertex_descriptor tgt = target(e, g);

        S.push(e);
        put(pred, tgt, src);
        if ( get(pred, src) == src ) {
          ++children_of_root;
        }
        vis.tree_edge(e, g);
      }

      template <typename Edge, typename Graph>
      void back_edge(const Edge& e, Graph& g)
      {
        BOOST_USING_STD_MIN();

        typename boost::graph_traits<Graph>::vertex_descriptor src = source(e, g);
        typename boost::graph_traits<Graph>::vertex_descriptor tgt = target(e, g);
        if ( tgt != get(pred, src) ) {
          S.push(e);
          put(lowpt, src,
              min BOOST_PREVENT_MACRO_SUBSTITUTION(get(lowpt, src),
                                                   get(dtm, tgt)));
        }
        vis.back_edge(e, g);
      }

      template <typename Edge, typename Graph>
      void forward_or_cross_edge(const Edge& e, Graph& g)
      {
        vis.forward_or_cross_edge(e, g);
      }

      template <typename Vertex, typename Graph>
      void finish_vertex(const Vertex& u, Graph& g)
      {
        BOOST_USING_STD_MIN();
        Vertex parent = get(pred, u);
        if (parent == u) { // Root of tree is special
          is_articulation_point[get(index_map, u)] = (children_of_root > 1);
        } else {
          put(lowpt, parent,
              min BOOST_PREVENT_MACRO_SUBSTITUTION(get(lowpt, parent),
                                                 get(lowpt, u)));
          if ( get(lowpt, u) >= get(dtm, parent) ) {
            is_articulation_point[get(index_map, parent)] = true;
            while ( get(dtm, source(S.top(), g)) >= get(dtm, u) ) {
              put(comp, S.top(), c);
              S.pop();
            }
            assert (source(S.top(), g) == parent);
            assert (target(S.top(), g) == u);
            put(comp, S.top(), c);
            S.pop();
            ++c;
          }
        }
        if ( is_articulation_point[get(index_map, u)] ) {
          *out++ = u;
        }
        vis.finish_vertex(u, g);
      }

      ComponentMap comp;
      std::size_t& c;
      std::size_t& children_of_root;
      DiscoverTimeMap dtm;
      std::size_t& dfs_time;
      LowPointMap lowpt;
      PredecessorMap pred;
      OutputIterator out;
      Stack& S;
      ArticulationVector& is_articulation_point;
      IndexMap index_map;
      DFSVisitor vis;
    };

  template<typename Graph, typename ComponentMap, typename OutputIterator,
        typename VertexIndexMap, typename DiscoverTimeMap, typename LowPointMap,
        typename PredecessorMap, typename DFSVisitor>
  std::pair<std::size_t, OutputIterator>
    biconnected_components_impl(const Graph & g, ComponentMap comp,
        OutputIterator out, VertexIndexMap index_map, DiscoverTimeMap dtm,
        LowPointMap lowpt, PredecessorMap pred, DFSVisitor dfs_vis)
  {
    typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
    typedef typename graph_traits<Graph>::edge_descriptor edge_t;
    BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph> ));
    BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept<Graph> ));
    BOOST_CONCEPT_ASSERT(( WritablePropertyMapConcept<ComponentMap, edge_t> ));
    BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<DiscoverTimeMap,
                                                  vertex_t> ));
    BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<LowPointMap, vertex_t > ));
    BOOST_CONCEPT_ASSERT(( ReadWritePropertyMapConcept<PredecessorMap,
                                                  vertex_t> ));

    std::size_t num_components = 0;
    std::size_t children_of_root;
    std::size_t dfs_time = 0;
    std::stack<edge_t> S;
	std::vector<char> is_articulation_point(num_vertices(g));

    biconnected_components_visitor<ComponentMap, DiscoverTimeMap,
        LowPointMap, PredecessorMap, OutputIterator, std::stack<edge_t>,
        std::vector<char>, VertexIndexMap, DFSVisitor>
    vis(comp, num_components, children_of_root, dtm, dfs_time,
        lowpt, pred, out, S, is_articulation_point, index_map, dfs_vis);

    depth_first_search(g, visitor(vis).vertex_index_map(index_map));

    return std::pair<std::size_t, OutputIterator>(num_components, vis.out);
  }

    template <typename PredecessorMap>
    struct bicomp_dispatch3
    {
  template<typename Graph, typename ComponentMap, typename OutputIterator,
                typename VertexIndexMap, typename DiscoverTimeMap,
                typename LowPointMap, class P, class T, class R>
      static std::pair<std::size_t, OutputIterator> apply (const Graph & g,
          ComponentMap comp, OutputIterator out, VertexIndexMap index_map,
          DiscoverTimeMap dtm, LowPointMap lowpt,
          const bgl_named_params<P, T, R>& params, PredecessorMap pred)
      {
        return biconnected_components_impl
                (g, comp, out, index_map, dtm, lowpt, pred,
                 choose_param(get_param(params, graph_visitor),
                    make_dfs_visitor(null_visitor())));
      }
    };

    template <>
    struct bicomp_dispatch3<error_property_not_found>
    {
      template<typename Graph, typename ComponentMap, typename OutputIterator,
                typename VertexIndexMap, typename DiscoverTimeMap,
                typename LowPointMap, class P, class T, class R>
      static std::pair<std::size_t, OutputIterator> apply (const Graph & g,
          ComponentMap comp, OutputIterator out, VertexIndexMap index_map,
          DiscoverTimeMap dtm, LowPointMap lowpt,
          const bgl_named_params<P, T, R>& params,
          error_property_not_found)
  {
    typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
    std::vector<vertex_t> pred(num_vertices(g));
    vertex_t vert = graph_traits<Graph>::null_vertex();

        return biconnected_components_impl
                (g, comp, out, index_map, dtm, lowpt,
              make_iterator_property_map(pred.begin(), index_map, vert),
                 choose_param(get_param(params, graph_visitor),
                    make_dfs_visitor(null_visitor())));
  }
    };

    template <typename LowPointMap>
    struct bicomp_dispatch2
    {
  template<typename Graph, typename ComponentMap, typename OutputIterator,
                typename VertexIndexMap, typename DiscoverTimeMap,
                typename P, typename T, typename R>
      static std::pair<std::size_t, OutputIterator> apply (const Graph& g,
          ComponentMap comp, OutputIterator out, VertexIndexMap index_map,
          DiscoverTimeMap dtm, const bgl_named_params<P, T, R>& params,
          LowPointMap lowpt)
      {
        typedef typename property_value< bgl_named_params<P,T,R>,
            vertex_predecessor_t>::type dispatch_type;

        return bicomp_dispatch3<dispatch_type>::apply
            (g, comp, out, index_map, dtm, lowpt, params,
             get_param(params, vertex_predecessor));
      }
    };

    template <>
    struct bicomp_dispatch2<error_property_not_found>
    {
      template<typename Graph, typename ComponentMap, typename OutputIterator,
                typename VertexIndexMap, typename DiscoverTimeMap,
                typename P, typename T, typename R>
      static std::pair<std::size_t, OutputIterator> apply (const Graph& g,
          ComponentMap comp, OutputIterator out, VertexIndexMap index_map,
          DiscoverTimeMap dtm, const bgl_named_params<P, T, R>& params,
          error_property_not_found)
  {
    typedef typename graph_traits<Graph>::vertices_size_type
      vertices_size_type;
    std::vector<vertices_size_type> lowpt(num_vertices(g));
        vertices_size_type vst(0);

        typedef typename property_value< bgl_named_params<P,T,R>,
            vertex_predecessor_t>::type dispatch_type;

        return bicomp_dispatch3<dispatch_type>::apply
            (g, comp, out, index_map, dtm,
             make_iterator_property_map(lowpt.begin(), index_map, vst),
             params, get_param(params, vertex_predecessor));
      }
    };

    template <typename DiscoverTimeMap>
    struct bicomp_dispatch1
    {
      template<typename Graph, typename ComponentMap, typename OutputIterator,
                typename VertexIndexMap, class P, class T, class R>
      static std::pair<std::size_t, OutputIterator> apply(const Graph& g,
          ComponentMap comp, OutputIterator out, VertexIndexMap index_map,
          const bgl_named_params<P, T, R>& params, DiscoverTimeMap dtm)
      {
        typedef typename property_value< bgl_named_params<P,T,R>,
            vertex_lowpoint_t>::type dispatch_type;

        return bicomp_dispatch2<dispatch_type>::apply
            (g, comp, out, index_map, dtm, params,
             get_param(params, vertex_lowpoint));
      }
    };

    template <>
    struct bicomp_dispatch1<error_property_not_found>
    {
      template<typename Graph, typename ComponentMap, typename OutputIterator,
                typename VertexIndexMap, class P, class T, class R>
      static std::pair<std::size_t, OutputIterator> apply(const Graph& g,
          ComponentMap comp, OutputIterator out, VertexIndexMap index_map,
          const bgl_named_params<P, T, R>& params, error_property_not_found)
      {
        typedef typename graph_traits<Graph>::vertices_size_type
            vertices_size_type;
        std::vector<vertices_size_type> discover_time(num_vertices(g));
    vertices_size_type vst(0);

        typedef typename property_value< bgl_named_params<P,T,R>,
            vertex_lowpoint_t>::type dispatch_type;

        return bicomp_dispatch2<dispatch_type>::apply
            (g, comp, out, index_map,
              make_iterator_property_map(discover_time.begin(), index_map, vst),
             params, get_param(params, vertex_lowpoint));
      }
    };

  }

  template<typename Graph, typename ComponentMap, typename OutputIterator,
      typename DiscoverTimeMap, typename LowPointMap>
  std::pair<std::size_t, OutputIterator>
  biconnected_components(const Graph& g, ComponentMap comp,
      OutputIterator out, DiscoverTimeMap dtm, LowPointMap lowpt)
  {
    typedef detail::error_property_not_found dispatch_type;

    return detail::bicomp_dispatch3<dispatch_type>::apply
            (g, comp, out,
             get(vertex_index, g),
             dtm, lowpt,
             bgl_named_params<int, buffer_param_t>(0),
             detail::error_property_not_found());
  }

  template <typename Graph, typename ComponentMap, typename OutputIterator,
      typename P, typename T, typename R>
  std::pair<std::size_t, OutputIterator>
  biconnected_components(const Graph& g, ComponentMap comp, OutputIterator out,
      const bgl_named_params<P, T, R>& params)
  {
    typedef typename property_value< bgl_named_params<P,T,R>,
        vertex_discover_time_t>::type dispatch_type;

    return detail::bicomp_dispatch1<dispatch_type>::apply(g, comp, out,
        choose_const_pmap(get_param(params, vertex_index), g, vertex_index),
        params, get_param(params, vertex_discover_time));
  }

  template < typename Graph, typename ComponentMap, typename OutputIterator>
  std::pair<std::size_t, OutputIterator>
  biconnected_components(const Graph& g, ComponentMap comp, OutputIterator out)
  {
    return biconnected_components(g, comp, out,
        bgl_named_params<int, buffer_param_t>(0));
  }

  namespace graph_detail {
    struct dummy_output_iterator
    {
      typedef std::output_iterator_tag iterator_category;
      typedef void value_type;
      typedef void pointer;
      typedef void difference_type;

      struct reference {
        template<typename T>
        reference& operator=(const T&) { return *this; }
      };

      reference operator*() const { return reference(); }
      dummy_output_iterator& operator++() { return *this; }
      dummy_output_iterator operator++(int) { return *this; }
    };
  } // end namespace graph_detail

  template <typename Graph, typename ComponentMap,
      typename P, typename T, typename R>
  std::size_t
  biconnected_components(const Graph& g, ComponentMap comp,
      const bgl_named_params<P, T, R>& params)
  {
    return biconnected_components(g, comp,
        graph_detail::dummy_output_iterator(), params).first;
  }

  template <typename Graph, typename ComponentMap>
  std::size_t
  biconnected_components(const Graph& g, ComponentMap comp)
  {
    return biconnected_components(g, comp,
                                  graph_detail::dummy_output_iterator()).first;
  }

  template<typename Graph, typename OutputIterator,
      typename P, typename T, typename R>
  OutputIterator
  articulation_points(const Graph& g, OutputIterator out,
      const bgl_named_params<P, T, R>& params)
  {
    return biconnected_components(g, dummy_property_map(), out,
        params).second;
  }

  template<typename Graph, typename OutputIterator>
  OutputIterator
  articulation_points(const Graph& g, OutputIterator out)
  {
    return biconnected_components(g, dummy_property_map(), out,
        bgl_named_params<int, buffer_param_t>(0)).second;
  }

}                               // namespace boost

#endif  /* BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP */


#include "context.h"
#include <stdio.h>
#include <string.h>
#include <vector>
#include "include/secp256k1.h"
#include "include/secp256k1_ecdh.h"
#include "include/secp256k1_schnorrsig.h"
#include "include/secp256k1_generator.h"
#include "include/secp256k1_rangeproof.h"
#include "libsecp256k1-config.h"

#include "log/log.h"

#include <boost/thread.hpp>

#include "hash_impl.h"
#include "num_impl.h"
#include "field_impl.h"
#include "group_impl.h"
#include "scalar_impl.h"

using namespace qbit;

#include <signal.h>

Context::Context() {
}

Context::~Context() {
	if (context_) {
		secp256k1_context_destroy(context_);
		secp256k1_context_destroy(none_);
	}

	if (scratch_) {
		secp256k1_scratch_space_destroy(scratch_);
	}
}

secp256k1_context* Context::noneContext() {
	initialize();
	return none_;
}

secp256k1_context* Context::signatureContext() {
	initialize();
	return context_;
}

ContextPtr Context::instance() {
	//
	static boost::thread_specific_ptr<ContextPtr> tContext;
	if (!tContext.get()) {
		tContext.reset(new ContextPtr(new Context()));
		return ContextPtr(*tContext.get());
	}

	return ContextPtr(*tContext.get());
}

secp256k1_scratch_space* Context::signatureScratch() {
	if (!scratch_) scratch_ = secp256k1_scratch_space_create(context_, 1024 * 1024 * 1024); // magic digits
	return scratch_;
}

void Context::initialize()
{
	if (!context_) {
		none_ = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
		context_ = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
	}
}

bool Context::createCommitment(qbit::vector<unsigned char>& out, const uint256& blind, amount_t amount) {
	secp256k1_pedersen_commitment lCommit;
	unsigned char lSerialized[33];
	if (secp256k1_pedersen_commit(signatureContext(), &lCommit, blind.begin(), amount, secp256k1_generator_h)) {
		secp256k1_pedersen_commitment_serialize(signatureContext(), lSerialized, &lCommit);
		out.insert(out.end(), lSerialized, lSerialized + sizeof(lSerialized));

		return true;
	}

	return false;
}

bool Context::createRangeProof(qbit::vector<unsigned char>& out, const qbit::vector<unsigned char>& commit, const uint256& blind, const uint256& nonce, amount_t amount) {
	unsigned char lProof[5134 + 1];
	size_t lLen = 5134;
	secp256k1_pedersen_commitment lCommit;

	if (secp256k1_pedersen_commitment_parse(signatureContext(), &lCommit, &commit[0]))
		if (secp256k1_rangeproof_sign(signatureContext(), lProof, &lLen, 0, &lCommit, blind.begin(), nonce.begin(), 0, 0, amount, NULL, 0, NULL, 0, secp256k1_generator_h)) {
			out.insert(out.end(), lProof, lProof + lLen);
			return true;
		}

	return false;
}

bool Context::verifyRangeProof(const unsigned char* commit, const unsigned char* proof, size_t size) {
	uint64_t lMinValue;
	uint64_t lMaxValue;
	secp256k1_pedersen_commitment lCommit;
	if (secp256k1_pedersen_commitment_parse(signatureContext(), &lCommit, commit))
		if (secp256k1_rangeproof_verify(signatureContext(), &lMinValue, &lMaxValue, &lCommit, proof, size, NULL, 0, secp256k1_generator_h))
		return true;

	return false;
}

bool Context::rewindRangeProof(uint64_t* vout, uint256& blind, const uint256& nonce, const unsigned char* commit, const unsigned char* proof, size_t size) {
	uint64_t lMinValue;
	uint64_t lMaxValue;

	secp256k1_pedersen_commitment lCommit;
	if (secp256k1_pedersen_commitment_parse(signatureContext(), &lCommit, commit))
		if (secp256k1_rangeproof_rewind(signatureContext(), blind.begin(), vout, NULL, NULL, nonce.begin(), &lMinValue, &lMaxValue, &lCommit, proof, size, NULL, 0, secp256k1_generator_h))
			return true;
	return false;
}

bool Context::verifyTally(const std::list<std::vector<unsigned char>>& in, const std::list<std::vector<unsigned char>>& out) {
	std::vector<secp256k1_pedersen_commitment> lIn;
	std::vector<secp256k1_pedersen_commitment> lOut;

	std::vector<secp256k1_pedersen_commitment*> lInPtr;
	std::vector<secp256k1_pedersen_commitment*> lOutPtr;

	lIn.resize(in.size());
	lOut.resize(out.size());

	lInPtr.resize(in.size());
	lOutPtr.resize(out.size());

	int lIdx = 0;
	for (std::list<std::vector<unsigned char>>::const_iterator lInIter = in.begin(); lInIter != in.end(); lInIter++, lIdx++) {
		if (secp256k1_pedersen_commitment_parse(signatureContext(), &lIn[lIdx], &(*lInIter)[0])) {
			lInPtr[lIdx] = &lIn[lIdx];
		} else return false;
	}

	lIdx = 0;
	for (std::list<std::vector<unsigned char>>::const_iterator lOutIter = out.begin(); lOutIter != out.end(); lOutIter++, lIdx++) {
		if (secp256k1_pedersen_commitment_parse(signatureContext(), &lOut[lIdx], &(*lOutIter)[0])) {
			lOutPtr[lIdx] = &lOut[lIdx];
		} else return false;
	}

	if (!secp256k1_pedersen_verify_tally(signatureContext(), &lInPtr[0], lInPtr.size(), &lOutPtr[0], lOutPtr.size())) return false;
	return true;
}

bool Math::add(uint256& c, const uint256& a, const uint256& b) {
	int lOverflow;
	secp256k1_scalar lA; secp256k1_scalar_set_b32(&lA, a.begin(), &lOverflow); if (lOverflow) return 0;
	secp256k1_scalar lB; secp256k1_scalar_set_b32(&lB, b.begin(), &lOverflow); if (lOverflow) return 0;
	secp256k1_scalar lC;

	secp256k1_scalar_add(&lC, &lA, &lB);

	unsigned char lRawC[32];
	secp256k1_scalar_get_b32(lRawC, &lC);

	c.set(lRawC);

	return 1;
}

bool Math::mul(uint256& c, const uint256& a, const uint256& b) {
	int lOverflow;
	secp256k1_scalar lA; secp256k1_scalar_set_b32(&lA, a.begin(), &lOverflow); if (lOverflow) return 0;
	secp256k1_scalar lB; secp256k1_scalar_set_b32(&lB, b.begin(), &lOverflow); if (lOverflow) return 0;
	secp256k1_scalar lC;

	secp256k1_scalar_mul(&lC, &lA, &lB);

	unsigned char lRawC[32];
	secp256k1_scalar_get_b32(lRawC, &lC);

	c.set(lRawC);

	return 1;
}

bool Math::neg(uint256& c, const uint256& a) {
	int lOverflow;
	secp256k1_scalar lA; secp256k1_scalar_set_b32(&lA, a.begin(), &lOverflow); if (lOverflow) return 0;
	secp256k1_scalar lC;

	secp256k1_scalar_negate(&lC, &lA);

	unsigned char lRawC[32];
	secp256k1_scalar_get_b32(lRawC, &lC);

	c.set(lRawC);

	return 1;
}


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

#ifndef CRYPTO3_DETAIL_EXPLODER_HPP
#define CRYPTO3_DETAIL_EXPLODER_HPP

#include <nil/crypto3/detail/stream_endian.hpp>
#include <nil/crypto3/detail/unbounded_shift.hpp>
#include <nil/crypto3/detail/reverser.hpp>

#include <boost/integer.hpp>
#include <boost/static_assert.hpp>

#include <iterator>

namespace nil {
    namespace crypto3 {
        namespace detail {

            // By definition, for all exploders, InputValueBits > OutputValueBits,
            // so we're taking one value and splitting it into many smaller values

            /*!
             * @defgroup exploder Exploder functions
             */

            /*!
             * @brief outvalue_helper trait is used to determine the output value type.
             * If OutBits is not an exact power of two for which the type uint_t is defined, the type
             * with the least power of two bits greater than OutBits is taken. Due to current exploder
             * struct definition, this case is possible, when OutputBits is a factor of UnitBits less
             * than UnitBits, and UnitBits is no more than CHAR_BIT.
             *
             * @ingroup exploder
             *
             * @tparam OutIter
             * @tparam OutBits
             * @tparam T
             */
            template<typename OutIter, int OutBits, typename T = typename std::iterator_traits<OutIter>::value_type>
            struct outvalue_helper {
                typedef T type;
            };
            template<typename OutIter, int OutBits>
            struct outvalue_helper<OutIter, OutBits, void> {
                typedef typename boost::uint_t<OutBits>::least type;
            };

            /*!
             * @brief exploder_shift trait is used to determine whether the output elements are splitted
             * from an input element in reverse order. Since the input and output types are integral now,
             * this trait contains the shift indicating the position of output element derived from the
             * input element when k output bits have already been processed.
             *
             * @ingroup exploder
             *
             * @tparam InputEndianness
             * @tparam UnitBits
             * @tparam InputBits
             * @tparam OutputBits
             * @tparam k
             * @tparam IsLittleUnit
             */
            template<typename InputEndianness, int UnitBits, int InputBits, int OutputBits, int k,
                     bool IsLittleUnit = is_little_unit<InputEndianness, UnitBits>::value>
            struct exploder_shift;

            template<typename InputEndianness, int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_shift<InputEndianness, UnitBits, InputBits, OutputBits, k, false> {
                constexpr static int const value = InputBits - (OutputBits + k);
            };

            template<typename InputEndianness, int UnitBits, int InputBits, int OutputBits, int k>
            struct exploder_shift<InputEndianness, UnitBits, InputBits, OutputBits, k, true> {
                constexpr static int const value = k;
            };

            /*!
             * @brief exploder_step obtains an output value represented in OutputEndianness endianness
             * from an input value represented in InputEndianness endianness when k output bits
             * have already been processed. It uses unit_reverser and bit_reverser to deal with the
             * order of units and bits in the output value, respectively. Shift constant is determined
             * by the exploder_shift trait.
             *
             * @ingroup exploder
             *
             * @tparam InputEndianness
             * @tparam OutputEndianness
             * @tparam UnitBits
             * @tparam InputBits
             * @tparam OutputBits
             * @tparam k
             */
            template<typename InputEndianness, typename OutputEndianness, int UnitBits, int InputBits, int OutputBits,
                     int k>
            struct exploder_step {
                constexpr static int const shift =
                    exploder_shift<InputEndianness, UnitBits, InputBits, OutputBits, k>::value;

                template<typename InputValue, typename OutputIterator>
                inline static void step(InputValue const &in, OutputIterator &out) {
                    typedef typename outvalue_helper<OutputIterator, OutputBits>::type OutValue;
                    OutValue tmp = OutValue(low_bits<OutputBits>(unbounded_shr<shift>(in)));
                    unit_reverser<InputEndianness, OutputEndianness, UnitBits>::reverse(tmp);
                    bit_reverser<InputEndianness, OutputEndianness, UnitBits>::reverse(tmp);
                    *out++ = tmp;
                }
            };

            /*!
             * @brief exploder forms a sequence of output values represented in OutputEndianness endianness
             * from an input value represented in InputEndianness endianness. The function explode is
             * invoked recursively, and the parameter k is used to track the number of already processed
             * output values derived from the input value. The recursion ends when all elements the input
             * value can hold have already been processed, i.e. when k == InputBits.
             *
             * @ingroup exploder
             *
             * @tparam InputEndianness
             * @tparam OutputEndianness
             * @tparam InputBits
             * @tparam OutputBits
             * @tparam k
             */
            template<typename InputEndianness, typename OutputEndianness, int InputBits, int OutputBits, int k = 0>
            struct exploder;

            template<template<int> class InputEndian, template<int> class OutputEndian, int UnitBits, int InputBits,
                     int OutputBits, int k>
            struct exploder<InputEndian<UnitBits>, OutputEndian<UnitBits>, InputBits, OutputBits, k> {

                // To keep the implementation managable, input and output sizes must
                // be multiples or factors of the unit size.
                // If one of these is firing, you may want a bit-only stream_endian
                // rather than one that mentions bytes or octets.
                BOOST_STATIC_ASSERT(!(InputBits % UnitBits && UnitBits % InputBits));
                BOOST_STATIC_ASSERT(!(OutputBits % UnitBits && UnitBits % OutputBits));

                typedef InputEndian<UnitBits> InputEndianness;
                typedef OutputEndian<UnitBits> OutputEndianness;
                typedef exploder_step<InputEndianness, OutputEndianness, UnitBits, InputBits, OutputBits, k> step_type;
                typedef exploder<InputEndianness, OutputEndianness, InputBits, OutputBits, k + OutputBits> next_type;

                template<typename InputValue, typename OutIter>
                inline static void explode(InputValue const &x, OutIter &out) {
                    step_type::step(x, out);
                    next_type::explode(x, out);
                }
            };

            template<template<int> class InputEndian, template<int> class OutputEndian, int UnitBits, int InputBits,
                     int OutputBits>
            struct exploder<InputEndian<UnitBits>, OutputEndian<UnitBits>, InputBits, OutputBits, InputBits> {
                template<typename InputValue, typename OutIter>
                inline static void explode(InputValue const &, OutIter &) {
                }
            };

        }    // namespace detail
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_DETAIL_EXPLODER_HPP


//  Copyright John Maddock 2008.
//  Use, modification and distribution are 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)
//
// Wrapper that works with mpfr_class defined in gmpfrxx.h
// See http://math.berkeley.edu/~wilken/code/gmpfrxx/
// Also requires the gmp and mpfr libraries.
//

#ifndef BOOST_MATH_E_FLOAT_BINDINGS_HPP
#define BOOST_MATH_E_FLOAT_BINDINGS_HPP

#include <boost/config.hpp>

#include <e_float/e_float.h>
#include <functions/functions.h>

#include <boost/math/tools/precision.hpp>
#include <boost/math/tools/real_cast.hpp>
#include <boost/math/policies/policy.hpp>
#include <boost/math/distributions/fwd.hpp>
#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/math/bindings/detail/big_digamma.hpp>
#include <boost/math/bindings/detail/big_lanczos.hpp>
#include <boost/lexical_cast.hpp>

namespace boost{ namespace math{ namespace ef{

class e_float
{
public:
   // Constructors:
   e_float() {}
   e_float(const ::e_float& c) : m_value(c){}
   e_float(char c)
   {
      m_value = ::e_float(c);
   }
#ifndef BOOST_NO_INTRINSIC_WCHAR_T
   e_float(wchar_t c)
   {
      m_value = ::e_float(c);
   }
#endif
   e_float(unsigned char c)
   {
      m_value = ::e_float(c);
   }
   e_float(signed char c)
   {
      m_value = ::e_float(c);
   }
   e_float(unsigned short c)
   {
      m_value = ::e_float(c);
   }
   e_float(short c)
   {
      m_value = ::e_float(c);
   }
   e_float(unsigned int c)
   {
      m_value = ::e_float(c);
   }
   e_float(int c)
   {
      m_value = ::e_float(c);
   }
   e_float(unsigned long c)
   {
      m_value = ::e_float((UINT64)c);
   }
   e_float(long c)
   {
      m_value = ::e_float((INT64)c);
   }
#ifdef BOOST_HAS_LONG_LONG
   e_float(boost::ulong_long_type c)
   {
      m_value = ::e_float(c);
   }
   e_float(boost::long_long_type c)
   {
      m_value = ::e_float(c);
   }
#endif
   e_float(float c)
   {
      assign_large_real(c);
   }
   e_float(double c)
   {
      assign_large_real(c);
   }
   e_float(long double c)
   {
      assign_large_real(c);
   }

   // Assignment:
   e_float& operator=(char c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(unsigned char c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(signed char c) { m_value = ::e_float(c); return *this; }
#ifndef BOOST_NO_INTRINSIC_WCHAR_T
   e_float& operator=(wchar_t c) { m_value = ::e_float(c); return *this; }
#endif
   e_float& operator=(short c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(unsigned short c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(int c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(unsigned int c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(long c) { m_value = ::e_float((INT64)c); return *this; }
   e_float& operator=(unsigned long c) { m_value = ::e_float((UINT64)c); return *this; }
#ifdef BOOST_HAS_LONG_LONG
   e_float& operator=(boost::long_long_type c) { m_value = ::e_float(c); return *this; }
   e_float& operator=(boost::ulong_long_type c) { m_value = ::e_float(c); return *this; }
#endif
   e_float& operator=(float c) { assign_large_real(c); return *this; }
   e_float& operator=(double c) { assign_large_real(c); return *this; }
   e_float& operator=(long double c) { assign_large_real(c); return *this; }

   // Access:
   ::e_float& value(){ return m_value; }
   ::e_float const& value()const{ return m_value; }

   // Member arithmetic:
   e_float& operator+=(const e_float& other)
   { m_value += other.value(); return *this; }
   e_float& operator-=(const e_float& other)
   { m_value -= other.value(); return *this; }
   e_float& operator*=(const e_float& other)
   { m_value *= other.value(); return *this; }
   e_float& operator/=(const e_float& other)
   { m_value /= other.value(); return *this; }
   e_float operator-()const
   { return -m_value; }
   e_float const& operator+()const
   { return *this; }

private:
   ::e_float m_value;

   template <class V>
   void assign_large_real(const V& a)
   {
      using std::frexp;
      using std::ldexp;
      using std::floor;
      if (a == 0) {
         m_value = ::ef::zero();
         return;
      }

      if (a == 1) {
         m_value = ::ef::one();
         return;
      }

      if ((boost::math::isinf)(a))
      {
         m_value = a > 0 ? m_value.my_value_inf() : -m_value.my_value_inf();
         return;
      }
      if((boost::math::isnan)(a))
      {
         m_value = m_value.my_value_nan();
         return;
      }

      int e;
      long double f, term;
      ::e_float t;
      m_value = ::ef::zero();

      f = frexp(a, &e);

      ::e_float shift = ::ef::pow2(30);

      while(f)
      {
         // extract 30 bits from f:
         f = ldexp(f, 30);
         term = floor(f);
         e -= 30;
         m_value *= shift;
         m_value += ::e_float(static_cast<INT64>(term));
         f -= term;
      }
      m_value *= ::ef::pow2(e);
   }
};

// Non-member arithmetic:
inline e_float operator+(const e_float& a, const e_float& b)
{
   e_float result(a);
   result += b;
   return result;
}
inline e_float operator-(const e_float& a, const e_float& b)
{
   e_float result(a);
   result -= b;
   return result;
}
inline e_float operator*(const e_float& a, const e_float& b)
{
   e_float result(a);
   result *= b;
   return result;
}
inline e_float operator/(const e_float& a, const e_float& b)
{
   e_float result(a);
   result /= b;
   return result;
}

// Comparison:
inline bool operator == (const e_float& a, const e_float& b)
{ return a.value() == b.value() ? true : false; }
inline bool operator != (const e_float& a, const e_float& b)
{ return a.value() != b.value() ? true : false;}
inline bool operator < (const e_float& a, const e_float& b)
{ return a.value() < b.value() ? true : false; }
inline bool operator <= (const e_float& a, const e_float& b)
{ return a.value() <= b.value() ? true : false; }
inline bool operator > (const e_float& a, const e_float& b)
{ return a.value() > b.value() ? true : false; }
inline bool operator >= (const e_float& a, const e_float& b)
{ return a.value() >= b.value() ? true : false; }

std::istream& operator >> (std::istream& is, e_float& f)
{
   return is >> f.value();
}

std::ostream& operator << (std::ostream& os, const e_float& f)
{
   return os << f.value();
}

inline e_float fabs(const e_float& v)
{
   return ::ef::fabs(v.value());
}

inline e_float abs(const e_float& v)
{
   return ::ef::fabs(v.value());
}

inline e_float floor(const e_float& v)
{
   return ::ef::floor(v.value());
}

inline e_float ceil(const e_float& v)
{
   return ::ef::ceil(v.value());
}

inline e_float pow(const e_float& v, const e_float& w)
{
   return ::ef::pow(v.value(), w.value());
}

inline e_float pow(const e_float& v, int i)
{
   return ::ef::pow(v.value(), ::e_float(i));
}

inline e_float exp(const e_float& v)
{
   return ::ef::exp(v.value());
}

inline e_float log(const e_float& v)
{
   return ::ef::log(v.value());
}

inline e_float sqrt(const e_float& v)
{
   return ::ef::sqrt(v.value());
}

inline e_float sin(const e_float& v)
{
   return ::ef::sin(v.value());
}

inline e_float cos(const e_float& v)
{
   return ::ef::cos(v.value());
}

inline e_float tan(const e_float& v)
{
   return ::ef::tan(v.value());
}

inline e_float acos(const e_float& v)
{
   return ::ef::acos(v.value());
}

inline e_float asin(const e_float& v)
{
   return ::ef::asin(v.value());
}

inline e_float atan(const e_float& v)
{
   return ::ef::atan(v.value());
}

inline e_float atan2(const e_float& v, const e_float& u)
{
   return ::ef::atan2(v.value(), u.value());
}

inline e_float ldexp(const e_float& v, int e)
{
   return v.value() * ::ef::pow2(e);
}

inline e_float frexp(const e_float& v, int* expon)
{
   double d;
   INT64 i;
   v.value().extract_parts(d, i);
   *expon = static_cast<int>(i);
   return v.value() * ::ef::pow2(-i);
}

inline e_float sinh (const e_float& x)
{
   return ::ef::sinh(x.value());
}

inline e_float cosh (const e_float& x)
{
   return ::ef::cosh(x.value());
}

inline e_float tanh (const e_float& x)
{
   return ::ef::tanh(x.value());
}

inline e_float asinh (const e_float& x)
{
   return ::ef::asinh(x.value());
}

inline e_float acosh (const e_float& x)
{
   return ::ef::acosh(x.value());
}

inline e_float atanh (const e_float& x)
{
   return ::ef::atanh(x.value());
}

e_float fmod(const e_float& v1, const e_float& v2)
{
   e_float n;
   if(v1 < 0)
      n = ceil(v1 / v2);
   else
      n = floor(v1 / v2);
   return v1 - n * v2;
}

} namespace detail{

template <>
inline int fpclassify_imp< boost::math::ef::e_float> BOOST_NO_MACRO_EXPAND(boost::math::ef::e_float x, const generic_tag<true>&)
{
   if(x.value().isnan())
      return FP_NAN;
   if(x.value().isinf())
      return FP_INFINITE;
   if(x == 0)
      return FP_ZERO;
   return FP_NORMAL;
}

} namespace ef{

template <class Policy>
inline int itrunc(const e_float& v, const Policy& pol)
{
   BOOST_MATH_STD_USING
   e_float r = boost::math::trunc(v, pol);
   if(fabs(r) > (std::numeric_limits<int>::max)())
      return static_cast<int>(policies::raise_rounding_error("boost::math::itrunc<%1%>(%1%)", 0, 0, v, pol));
   return static_cast<int>(r.value().extract_int64());
}

template <class Policy>
inline long ltrunc(const e_float& v, const Policy& pol)
{
   BOOST_MATH_STD_USING
   e_float r = boost::math::trunc(v, pol);
   if(fabs(r) > (std::numeric_limits<long>::max)())
      return static_cast<long>(policies::raise_rounding_error("boost::math::ltrunc<%1%>(%1%)", 0, 0L, v, pol));
   return static_cast<long>(r.value().extract_int64());
}

#ifdef BOOST_HAS_LONG_LONG
template <class Policy>
inline boost::long_long_type lltrunc(const e_float& v, const Policy& pol)
{
   BOOST_MATH_STD_USING
   e_float r = boost::math::trunc(v, pol);
   if(fabs(r) > (std::numeric_limits<boost::long_long_type>::max)())
      return static_cast<boost::long_long_type>(policies::raise_rounding_error("boost::math::lltrunc<%1%>(%1%)", 0, v, 0LL, pol).value().extract_int64());
   return static_cast<boost::long_long_type>(r.value().extract_int64());
}
#endif

template <class Policy>
inline int iround(const e_float& v, const Policy& pol)
{
   BOOST_MATH_STD_USING
   e_float r = boost::math::round(v, pol);
   if(fabs(r) > (std::numeric_limits<int>::max)())
      return static_cast<int>(policies::raise_rounding_error("boost::math::iround<%1%>(%1%)", 0, v, 0, pol).value().extract_int64());
   return static_cast<int>(r.value().extract_int64());
}

template <class Policy>
inline long lround(const e_float& v, const Policy& pol)
{
   BOOST_MATH_STD_USING
   e_float r = boost::math::round(v, pol);
   if(fabs(r) > (std::numeric_limits<long>::max)())
      return static_cast<long int>(policies::raise_rounding_error("boost::math::lround<%1%>(%1%)", 0, v, 0L, pol).value().extract_int64());
   return static_cast<long int>(r.value().extract_int64());
}

#ifdef BOOST_HAS_LONG_LONG
template <class Policy>
inline boost::long_long_type llround(const e_float& v, const Policy& pol)
{
   BOOST_MATH_STD_USING
   e_float r = boost::math::round(v, pol);
   if(fabs(r) > (std::numeric_limits<boost::long_long_type>::max)())
      return static_cast<boost::long_long_type>(policies::raise_rounding_error("boost::math::llround<%1%>(%1%)", 0, v, 0LL, pol).value().extract_int64());
   return static_cast<boost::long_long_type>(r.value().extract_int64());
}
#endif

}}}

namespace std{

   template<>
   class numeric_limits< ::boost::math::ef::e_float> : public numeric_limits< ::e_float>
   {
   public:
      static const ::boost::math::ef::e_float (min) (void)
      {
         return (numeric_limits< ::e_float>::min)();
      }
      static const ::boost::math::ef::e_float (max) (void)
      {
         return (numeric_limits< ::e_float>::max)();
      }
      static const ::boost::math::ef::e_float epsilon (void)
      {
         return (numeric_limits< ::e_float>::epsilon)();
      }
      static const ::boost::math::ef::e_float round_error(void)
      {
         return (numeric_limits< ::e_float>::round_error)();
      }
      static const ::boost::math::ef::e_float infinity (void)
      {
         return (numeric_limits< ::e_float>::infinity)();
      }
      static const ::boost::math::ef::e_float quiet_NaN (void)
      {
         return (numeric_limits< ::e_float>::quiet_NaN)();
      }
      //
      // e_float's supplied digits member is wrong
      // - it should be same the same as digits 10
      // - given that radix is 10.
      //
      static const int digits = digits10;
   };

} // namespace std

namespace boost{ namespace math{

namespace policies{

template <class Policy>
struct precision< ::boost::math::ef::e_float, Policy>
{
   typedef typename Policy::precision_type precision_type;
   typedef digits2<((::std::numeric_limits< ::boost::math::ef::e_float>::digits10 + 1) * 1000L) / 301L> digits_2;
   typedef typename mpl::if_c<
      ((digits_2::value <= precision_type::value)
      || (Policy::precision_type::value <= 0)),
      // Default case, full precision for RealType:
      digits_2,
      // User customised precision:
      precision_type
   >::type type;
};

}

namespace tools{

template <>
inline int digits< ::boost::math::ef::e_float>(BOOST_MATH_EXPLICIT_TEMPLATE_TYPE_SPEC( ::boost::math::ef::e_float))
{
   return ((::std::numeric_limits< ::boost::math::ef::e_float>::digits10 + 1) * 1000L) / 301L;
}

template <>
inline  ::boost::math::ef::e_float root_epsilon< ::boost::math::ef::e_float>()
{
   return detail::root_epsilon_imp(static_cast< ::boost::math::ef::e_float const*>(0), boost::integral_constant<int, 0>());
}

template <>
inline  ::boost::math::ef::e_float forth_root_epsilon< ::boost::math::ef::e_float>()
{
   return detail::forth_root_epsilon_imp(static_cast< ::boost::math::ef::e_float const*>(0), boost::integral_constant<int, 0>());
}

}

namespace lanczos{

template<class Policy>
struct lanczos<boost::math::ef::e_float, Policy>
{
   typedef typename mpl::if_c<
      std::numeric_limits< ::e_float>::digits10 < 22,
      lanczos13UDT,
      typename mpl::if_c<
         std::numeric_limits< ::e_float>::digits10 < 36,
         lanczos22UDT,
         typename mpl::if_c<
            std::numeric_limits< ::e_float>::digits10 < 50,
            lanczos31UDT,
            typename mpl::if_c<
               std::numeric_limits< ::e_float>::digits10 < 110,
               lanczos61UDT,
               undefined_lanczos
            >::type
         >::type
      >::type
   >::type type;
};

} // namespace lanczos

template <class Policy>
inline boost::math::ef::e_float skewness(const extreme_value_distribution<boost::math::ef::e_float, Policy>& /*dist*/)
{
   //
   // This is 12 * sqrt(6) * zeta(3) / pi^3:
   // See http://mathworld.wolfram.com/ExtremeValueDistribution.html
   //
   return boost::lexical_cast<boost::math::ef::e_float>("1.1395470994046486574927930193898461120875997958366");
}

template <class Policy>
inline boost::math::ef::e_float skewness(const rayleigh_distribution<boost::math::ef::e_float, Policy>& /*dist*/)
{
  // using namespace boost::math::constants;
  return boost::lexical_cast<boost::math::ef::e_float>("0.63111065781893713819189935154422777984404221106391");
  // Computed using NTL at 150 bit, about 50 decimal digits.
  // return 2 * root_pi<RealType>() * pi_minus_three<RealType>() / pow23_four_minus_pi<RealType>();
}

template <class Policy>
inline boost::math::ef::e_float kurtosis(const rayleigh_distribution<boost::math::ef::e_float, Policy>& /*dist*/)
{
  // using namespace boost::math::constants;
  return boost::lexical_cast<boost::math::ef::e_float>("3.2450893006876380628486604106197544154170667057995");
  // Computed using NTL at 150 bit, about 50 decimal digits.
  // return 3 - (6 * pi<RealType>() * pi<RealType>() - 24 * pi<RealType>() + 16) /
  // (four_minus_pi<RealType>() * four_minus_pi<RealType>());
}

template <class Policy>
inline boost::math::ef::e_float kurtosis_excess(const rayleigh_distribution<boost::math::ef::e_float, Policy>& /*dist*/)
{
  //using namespace boost::math::constants;
  // Computed using NTL at 150 bit, about 50 decimal digits.
  return boost::lexical_cast<boost::math::ef::e_float>("0.2450893006876380628486604106197544154170667057995");
  // return -(6 * pi<RealType>() * pi<RealType>() - 24 * pi<RealType>() + 16) /
  //   (four_minus_pi<RealType>() * four_minus_pi<RealType>());
} // kurtosis

namespace detail{

//
// Version of Digamma accurate to ~100 decimal digits.
//
template <class Policy>
boost::math::ef::e_float digamma_imp(boost::math::ef::e_float x, const boost::integral_constant<int, 0>* , const Policy& pol)
{
   //
   // This handles reflection of negative arguments, and all our
   // eboost::math::ef::e_floator handling, then forwards to the T-specific approximation.
   //
   BOOST_MATH_STD_USING // ADL of std functions.

   boost::math::ef::e_float result = 0;
   //
   // Check for negative arguments and use reflection:
   //
   if(x < 0)
   {
      // Reflect:
      x = 1 - x;
      // Argument reduction for tan:
      boost::math::ef::e_float remainder = x - floor(x);
      // Shift to negative if > 0.5:
      if(remainder > 0.5)
      {
         remainder -= 1;
      }
      //
      // check for evaluation at a negative pole:
      //
      if(remainder == 0)
      {
         return policies::raise_pole_error<boost::math::ef::e_float>("boost::math::digamma<%1%>(%1%)", 0, (1-x), pol);
      }
      result = constants::pi<boost::math::ef::e_float>() / tan(constants::pi<boost::math::ef::e_float>() * remainder);
   }
   result += big_digamma(x);
   return result;
}
boost::math::ef::e_float bessel_i0(boost::math::ef::e_float x)
{
    static const boost::math::ef::e_float P1[] = {
        boost::lexical_cast<boost::math::ef::e_float>("-2.2335582639474375249e+15"),
        boost::lexical_cast<boost::math::ef::e_float>("-5.5050369673018427753e+14"),
        boost::lexical_cast<boost::math::ef::e_float>("-3.2940087627407749166e+13"),
        boost::lexical_cast<boost::math::ef::e_float>("-8.4925101247114157499e+11"),
        boost::lexical_cast<boost::math::ef::e_float>("-1.1912746104985237192e+10"),
        boost::lexical_cast<boost::math::ef::e_float>("-1.0313066708737980747e+08"),
        boost::lexical_cast<boost::math::ef::e_float>("-5.9545626019847898221e+05"),
        boost::lexical_cast<boost::math::ef::e_float>("-2.4125195876041896775e+03"),
        boost::lexical_cast<boost::math::ef::e_float>("-7.0935347449210549190e+00"),
        boost::lexical_cast<boost::math::ef::e_float>("-1.5453977791786851041e-02"),
        boost::lexical_cast<boost::math::ef::e_float>("-2.5172644670688975051e-05"),
        boost::lexical_cast<boost::math::ef::e_float>("-3.0517226450451067446e-08"),
        boost::lexical_cast<boost::math::ef::e_float>("-2.6843448573468483278e-11"),
        boost::lexical_cast<boost::math::ef::e_float>("-1.5982226675653184646e-14"),
        boost::lexical_cast<boost::math::ef::e_float>("-5.2487866627945699800e-18"),
    };
    static const boost::math::ef::e_float Q1[] = {
        boost::lexical_cast<boost::math::ef::e_float>("-2.2335582639474375245e+15"),
        boost::lexical_cast<boost::math::ef::e_float>("7.8858692566751002988e+12"),
        boost::lexical_cast<boost::math::ef::e_float>("-1.2207067397808979846e+10"),
        boost::lexical_cast<boost::math::ef::e_float>("1.0377081058062166144e+07"),
        boost::lexical_cast<boost::math::ef::e_float>("-4.8527560179962773045e+03"),
        boost::lexical_cast<boost::math::ef::e_float>("1.0"),
    };
    static const boost::math::ef::e_float P2[] = {
        boost::lexical_cast<boost::math::ef::e_float>("-2.2210262233306573296e-04"),
        boost::lexical_cast<boost::math::ef::e_float>("1.3067392038106924055e-02"),
        boost::lexical_cast<boost::math::ef::e_float>("-4.4700805721174453923e-01"),
        boost::lexical_cast<boost::math::ef::e_float>("5.5674518371240761397e+00"),
        boost::lexical_cast<boost::math::ef::e_float>("-2.3517945679239481621e+01"),
        boost::lexical_cast<boost::math::ef::e_float>("3.1611322818701131207e+01"),
        boost::lexical_cast<boost::math::ef::e_float>("-9.6090021968656180000e+00"),
    };
    static const boost::math::ef::e_float Q2[] = {
        boost::lexical_cast<boost::math::ef::e_float>("-5.5194330231005480228e-04"),
        boost::lexical_cast<boost::math::ef::e_float>("3.2547697594819615062e-02"),
        boost::lexical_cast<boost::math::ef::e_float>("-1.1151759188741312645e+00"),
        boost::lexical_cast<boost::math::ef::e_float>("1.3982595353892851542e+01"),
        boost::lexical_cast<boost::math::ef::e_float>("-6.0228002066743340583e+01"),
        boost::lexical_cast<boost::math::ef::e_float>("8.5539563258012929600e+01"),
        boost::lexical_cast<boost::math::ef::e_float>("-3.1446690275135491500e+01"),
        boost::lexical_cast<boost::math::ef::e_float>("1.0"),
    };
    boost::math::ef::e_float value, factor, r;

    BOOST_MATH_STD_USING
    using namespace boost::math::tools;

    if (x < 0)
    {
        x = -x;                         // even function
    }
    if (x == 0)
    {
        return static_cast<boost::math::ef::e_float>(1);
    }
    if (x <= 15)                        // x in (0, 15]
    {
        boost::math::ef::e_float y = x * x;
        value = evaluate_polynomial(P1, y) / evaluate_polynomial(Q1, y);
    }
    else                                // x in (15, \infty)
    {
        boost::math::ef::e_float y = 1 / x - boost::math::ef::e_float(1) / 15;
        r = evaluate_polynomial(P2, y) / evaluate_polynomial(Q2, y);
        factor = exp(x) / sqrt(x);
        value = factor * r;
    }

    return value;
}

boost::math::ef::e_float bessel_i1(boost::math::ef::e_float x)
{
    static const boost::math::ef::e_float P1[] = {
        lexical_cast<boost::math::ef::e_float>("-1.4577180278143463643e+15"),
        lexical_cast<boost::math::ef::e_float>("-1.7732037840791591320e+14"),
        lexical_cast<boost::math::ef::e_float>("-6.9876779648010090070e+12"),
        lexical_cast<boost::math::ef::e_float>("-1.3357437682275493024e+11"),
        lexical_cast<boost::math::ef::e_float>("-1.4828267606612366099e+09"),
        lexical_cast<boost::math::ef::e_float>("-1.0588550724769347106e+07"),
        lexical_cast<boost::math::ef::e_float>("-5.1894091982308017540e+04"),
        lexical_cast<boost::math::ef::e_float>("-1.8225946631657315931e+02"),
        lexical_cast<boost::math::ef::e_float>("-4.7207090827310162436e-01"),
        lexical_cast<boost::math::ef::e_float>("-9.1746443287817501309e-04"),
        lexical_cast<boost::math::ef::e_float>("-1.3466829827635152875e-06"),
        lexical_cast<boost::math::ef::e_float>("-1.4831904935994647675e-09"),
        lexical_cast<boost::math::ef::e_float>("-1.1928788903603238754e-12"),
        lexical_cast<boost::math::ef::e_float>("-6.5245515583151902910e-16"),
        lexical_cast<boost::math::ef::e_float>("-1.9705291802535139930e-19"),
    };
    static const boost::math::ef::e_float Q1[] = {
        lexical_cast<boost::math::ef::e_float>("-2.9154360556286927285e+15"),
        lexical_cast<boost::math::ef::e_float>("9.7887501377547640438e+12"),
        lexical_cast<boost::math::ef::e_float>("-1.4386907088588283434e+10"),
        lexical_cast<boost::math::ef::e_float>("1.1594225856856884006e+07"),
        lexical_cast<boost::math::ef::e_float>("-5.1326864679904189920e+03"),
        lexical_cast<boost::math::ef::e_float>("1.0"),
    };
    static const boost::math::ef::e_float P2[] = {
        lexical_cast<boost::math::ef::e_float>("1.4582087408985668208e-05"),
        lexical_cast<boost::math::ef::e_float>("-8.9359825138577646443e-04"),
        lexical_cast<boost::math::ef::e_float>("2.9204895411257790122e-02"),
        lexical_cast<boost::math::ef::e_float>("-3.4198728018058047439e-01"),
        lexical_cast<boost::math::ef::e_float>("1.3960118277609544334e+00"),
        lexical_cast<boost::math::ef::e_float>("-1.9746376087200685843e+00"),
        lexical_cast<boost::math::ef::e_float>("8.5591872901933459000e-01"),
        lexical_cast<boost::math::ef::e_float>("-6.0437159056137599999e-02"),
    };
    static const boost::math::ef::e_float Q2[] = {
        lexical_cast<boost::math::ef::e_float>("3.7510433111922824643e-05"),
        lexical_cast<boost::math::ef::e_float>("-2.2835624489492512649e-03"),
        lexical_cast<boost::math::ef::e_float>("7.4212010813186530069e-02"),
        lexical_cast<boost::math::ef::e_float>("-8.5017476463217924408e-01"),
        lexical_cast<boost::math::ef::e_float>("3.2593714889036996297e+00"),
        lexical_cast<boost::math::ef::e_float>("-3.8806586721556593450e+00"),
        lexical_cast<boost::math::ef::e_float>("1.0"),
    };
    boost::math::ef::e_float value, factor, r, w;

    BOOST_MATH_STD_USING
    using namespace boost::math::tools;

    w = abs(x);
    if (x == 0)
    {
        return static_cast<boost::math::ef::e_float>(0);
    }
    if (w <= 15)                        // w in (0, 15]
    {
        boost::math::ef::e_float y = x * x;
        r = evaluate_polynomial(P1, y) / evaluate_polynomial(Q1, y);
        factor = w;
        value = factor * r;
    }
    else                                // w in (15, \infty)
    {
        boost::math::ef::e_float y = 1 / w - boost::math::ef::e_float(1) / 15;
        r = evaluate_polynomial(P2, y) / evaluate_polynomial(Q2, y);
        factor = exp(w) / sqrt(w);
        value = factor * r;
    }

    if (x < 0)
    {
        value *= -value;                 // odd function
    }
    return value;
}

} // namespace detail

}}
#endif // BOOST_MATH_E_FLOAT_BINDINGS_HPP