#include <iostream>
#include <vector>
#include <cmath>
#include <chrono>
#include <NTL/ZZ_pX.h> // contains ZZ_p.h and ZZ.h

using namespace std;
using namespace std::chrono;
using namespace NTL;

void check_p(ZZ_p &ans, long p, vector<ZZ_p> coeffs);
void findF(ZZ_pX &F, ZZ_pX &f, long rtp);
void poly_eval(ZZ_pX &h, ZZ_pX &f, ZZ_pX &g);
void evalF(vector<ZZ_p> &FVals, ZZ_pX &F, long rtp, long prtp);

ZZ p;
ZZ mod;
uint64_t start;
bool stopchecking;

void dumb_check(ZZ_p &ans, long p){
    for(long i = 0; i < p; i++){
        mul(ans, ans, i);
        if(i % 1024 == 0){
            if(80000 < duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() - start){
                stopchecking = true;
                return;
            }
        }
    }
}
int main(){
    stopchecking = false;
    for(long i = 1024; i <= 1099511627776; i*=2){
        NextPrime(p, ZZ(i));
        long p1;
        conv(p1, p);
        mul(mod, p, p);
        ZZ_p ans;
        ans.init(mod);
        vector<ZZ_p> coeffs(2);
        coeffs[0].init(mod);
        coeffs[1].init(mod);
        coeffs[0] = 0;
        coeffs[1] = 1;
        start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
        dumb_check(ans, p1);
        uint64_t time = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() - start;
        if(stopchecking){
            break;
        }
        //cout << "final answer: " << answer << endl;
        cout << time << ", ";
    }
    cout << endl;
}

// TODO: init all ZZ_p variables
void check_p(ZZ_p &ans, long p, vector<ZZ_p> coeffs){
    ZZ_pX f;
    for(int i = 0; i < coeffs.size(); i++){
        SetCoeff(f, i, coeffs[i]);
    }

    long rtp = sqrt(p-1); // rtp = floor(sqrt(p-1))
    ZZ_pX F;
    findF(F, f, rtp);

    long prtp = (p-1)/rtp;
    vector<ZZ_p> FVals(prtp);
    evalF(FVals, F, rtp, prtp); // prtp := floor((p-1)/floor(sqrt(p-1)))

    ZZ_p out(1);
    out.init(mod);
    for(long i = rtp*prtp+1; i < p; i++){
        mul(out, out, eval(f, ZZ_p(i)));
    }
    for(long i = 0; i < FVals.size(); i++){
        mul(out, out, FVals[i]);
    }

    ans = out;
}

// return F = f(X+1)f(X+2)...f(X+rtp) mod p
void findF(ZZ_pX &F, ZZ_pX &f, long rtp){
    vector<ZZ_pX> FTree(2*rtp); // rtp leaves -> 2*rtp nodes

    long leftmost = 1 << ((int)ceil(log2(rtp))); // bottom leftmost node in tree

    // Initialize the leaves in FTree: [X+1, X+2, ..., X+rtp]
    for (long i = leftmost; i < 2 * rtp; i++) { // leaves on lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, i - leftmost + 1);
        SetCoeff(leaf, 1, 1);
        poly_eval(leaf, f, leaf); // leaf = f(leaf())
        FTree[i] = leaf;
    }
    for (long i = rtp; i < leftmost; i++) { // leaves on second lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, i + rtp - leftmost + 1);
        SetCoeff(leaf, 1, 1);
        poly_eval(leaf, f, leaf); // leaf = f(leaf())
        FTree[i] = leaf;
    }

    // Calculate the rest of the product tree FTree
    for (long i = rtp - 1; i > 0; i--) {
        FTree[i] = FTree[2*i] * FTree[2*i+1]; // parent is product of leaves
        // TODO doesn't work:
        // delete FTree[2*i];
        // delete FTree[2*i+1];
    }

    F = FTree[1];
}

// calculate h = f(g(x))
void poly_eval(ZZ_pX &h, ZZ_pX &f, ZZ_pX &g){
    ZZ_pX out(LeadCoeff(f));
    for(int i = deg(f)-1; i >= 0; i--){
        mul(out, out, g);
        add(out, out, coeff(f, i));
    }
    h = out;
}

// evaluate F at 0, rtp, ..., (prtp-1)*rtp
void evalF(vector<ZZ_p> &FVals, ZZ_pX &F, long rtp, long prtp){
    vector<ZZ_pX> FValTree(2*prtp);

    long leftmost = 1 << ((int)ceil(log2(prtp))); // bottom leftmost node in tree

    // Initialize the leaves in FValTree: [X, X-rtp, ..., X-rtp*(prtp-1)]
    for (long i = leftmost; i < 2 * prtp; i++) { // leaves on lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, -rtp*(i - leftmost));
        SetCoeff(leaf, 1, 1);
        FValTree[i] = leaf;
    }
    for (long i = prtp; i < leftmost; i++) { // leaves on second lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, -rtp*(i + prtp - leftmost));
        SetCoeff(leaf, 1, 1);
        FValTree[i] = leaf;
    }

    // Calculate the rest of the product tree FValTree
    for (long i = prtp - 1; i > 0; i--) {
        FValTree[i] = FValTree[2*i] * FValTree[2*i+1]; // parent is product of leaves
    }

    // Reduce F mod polynomials in FValTree
    rem(FValTree[1], F, FValTree[1]);
    for (long i = 1; i < prtp; i++) {
        rem(FValTree[2*i], FValTree[i], FValTree[2*i]);
        rem(FValTree[2*i+1], FValTree[i], FValTree[2*i+1]);
    }

    for (long i = leftmost; i < 2 * prtp; i++) { // leaves on lowest layer
        FVals[i - leftmost].init(mod);
        FVals[i - leftmost] = ConstTerm(FValTree[i]);
    }
    for (long i = prtp; i < leftmost; i++) { // leaves on second lowest layer
        FVals[i + prtp - leftmost].init(mod);
        FVals[i + prtp - leftmost] = ConstTerm(FValTree[i]);
    }

}


/**
 * @file sspdriver_main.cc
 * @brief NPDE homework ExtendedMUSCL code
 * @author Oliver Rietmann
 * @date 04.08.2019
 * @copyright Developed at ETH Zurich
 */

#include <Eigen/Core>
#include <cmath>
#include <iostream>

#include "extendedmuscl.h"

using namespace ExtendedMUSCL;

// Bump function, raised by 1
static auto bump = [](double x) {
  return ((x >= 0.25) && (x <= 0.75))
             ? (2.0 - std::pow(std::cos(M_PI * (2 * (x - 0.25))), 2))
             : 1.0;
};

int main() {
  // First run: Solve ODE
  // Settings for the ODE
  double T = 1.0;
  double y0 = 1.0;
  double yT_exact = std::exp(T);
  auto f = [](double y) { return y; };

  // Choose time-steps 2^(-4), ..., 2^(-10)
  Eigen::VectorXd tau(7);
  tau << 0x1p-4, 0x1p-5, 0x1p-6, 0x1p-7, 0x1p-8, 0x1p-9, 0x1p-10;

  // Compute error of approx. solution at time T for all timestep-sizes in tau
  int N = tau.size();
  Eigen::VectorXd error(N);
  for (int n = 0; n < N; ++n) {
    int steps = (int)(T / tau(n) + 0.5);
    double y = y0;
    for (int i = 0; i < steps; ++i) y = sspEvolop(f, y, tau(n));
    error(n) = std::abs(yT_exact - y);
  }

  // Print the errors at each timestep
  Eigen::MatrixXd table(3, N);
  table.row(0) = tau;
  table.row(1) = error;
  table.row(2) = error.unaryExpr([](double x) { return std::log2(x); });
  Eigen::IOFormat tableFormat(2, 0, " ", "\n", " ", " ", " ", " ");
  std::cout << "tau \t error \t log_2(error)" << std::endl;
  std::cout << table.transpose().format(tableFormat) << std::endl;

  // Second run: Write solution for bump initial data to file
  std::cout << "Writing MUSCL FV solution at t=0.2 to file 'musclsol_02.csv'"
            << std::endl;
  storeMUSCLSolution("musclsol_02.csv", bump, 0.2, 100);
  std::cout << "Generated " CURRENT_BINARY_DIR "/musclsol_02.csv" << std::endl;
  std::system("python3 " CURRENT_SOURCE_DIR
              "/plot_musclsolution.py " CURRENT_BINARY_DIR
              "/musclsol_02.csv " CURRENT_BINARY_DIR "/musclsol_02.eps");
  std::cout << "Writing MUSCL FV solution at t = 1.0 to file 'musclsol_10.csv'"
            << std::endl;
  storeMUSCLSolution("musclsol_10.csv", bump, 1.0, 100);
  std::cout << "Generated " CURRENT_BINARY_DIR "/musclsol_10.csv" << std::endl;
  std::system("python3 " CURRENT_SOURCE_DIR
              "/plot_musclsolution.py " CURRENT_BINARY_DIR
              "/musclsol_10.csv " CURRENT_BINARY_DIR "/musclsol_10.eps");

  // Third run: convergence study
  studyCvgMUSCLSolution(bump, 0.2);
  studyCvgMUSCLSolution(bump, 1.0);

  return 0;
}


// Copyright 2015-2018 Hans Dembinski
//
// 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)

//[ guide_histogram_reduction

#include <boost/histogram.hpp>
#include <cassert>

int main() {
  using namespace boost::histogram;
  // import reduce commands into local namespace to save typing
  using algorithm::rebin;
  using algorithm::shrink;
  using algorithm::slice;

  // make a 2d histogram
  auto h = make_histogram(axis::regular<>(4, 0.0, 4.0), axis::regular<>(4, -2.0, 2.0));

  h(0, -0.9);
  h(1, 0.9);
  h(2, 0.1);
  h(3, 0.1);

  // reduce takes positional commands which are applied to the axes in order
  // - shrink is applied to the first axis; the new axis range is 0.0 to 3.0
  // - rebin is applied to the second axis; pairs of adjacent bins are merged
  auto h2 = algorithm::reduce(h, shrink(0.0, 3.0), rebin(2));

  assert(h2.axis(0) == axis::regular<>(3, 0.0, 3.0));
  assert(h2.axis(1) == axis::regular<>(2, -2.0, 2.0));

  // reduce does not change the total count if the histogram has underflow/overflow bins
  assert(algorithm::sum(h) == 4 && algorithm::sum(h2) == 4);

  // One can also explicitly specify the index of the axis in the histogram on which the
  // command should act, by using this index as the the first parameter. The position of
  // the command in the argument list of reduce is then ignored. We use this to slice only
  // the second axis (axis has index 1 in the histogram) from bin index 2 to 4.
  auto h3 = algorithm::reduce(h, slice(1, 2, 4));

  assert(h3.axis(0) == h.axis(0)); // unchanged
  assert(h3.axis(1) == axis::regular<>(2, 0.0, 2.0));
}

//]


#include <boost/test/unit_test.hpp>

#include "utilitaires.h"
#include "permutation.h"

BOOST_AUTO_TEST_SUITE(test_permutation)

    BOOST_AUTO_TEST_CASE(permutation_simple) {
        const std::vector<std::vector<int>> resultat{
                {1, 2, 3},
                {1, 3, 2},
                {2, 1, 3},
                {2, 3, 1},
                {3, 1, 2},
                {3, 2, 1}
        };

        size_t index = 0;
        const std::vector<int> v{1, 2, 3};
        for (auto i: permutation::Permutation<std::vector<int>>(v)) {
            BOOST_CHECK_EQUAL_COLLECTIONS(i.begin(), i.end(),
                                          resultat[index].begin(), resultat[index].end());
            ++index;
        }
    }

    BOOST_AUTO_TEST_CASE(permutation_doublon) {
        const std::vector<std::vector<int>> resultat{
                {1, 2, 2, 4},
                {1, 2, 4, 2},
                {1, 4, 2, 2},
                {2, 1, 2, 4},
                {2, 1, 4, 2},
                {2, 2, 1, 4},
                {2, 2, 4, 1},
                {2, 4, 1, 2},
                {2, 4, 2, 1},
                {4, 1, 2, 2},
                {4, 2, 1, 2},
                {4, 2, 2, 1}
        };

        size_t index = 0;
        const std::vector<int> v{1, 2, 2, 4};
        for (auto i: permutation::Permutation<std::vector<int>>(v)) {
            BOOST_CHECK_EQUAL_COLLECTIONS(i.begin(), i.end(),
                                          resultat[index].begin(), resultat[index].end());
            ++index;
        }
    }

    BOOST_AUTO_TEST_CASE(arrangements_simple) {
        const std::vector<std::vector<int>> resultat{
                {1, 2, 3},
                {1, 3, 2},
                {2, 1, 3},
                {2, 3, 1},
                {3, 1, 2},
                {3, 2, 1},
                {1, 2, 4},
                {1, 4, 2},
                {2, 1, 4},
                {2, 4, 1},
                {4, 1, 2},
                {4, 2, 1},
                {1, 3, 4},
                {1, 4, 3},
                {3, 1, 4},
                {3, 4, 1},
                {4, 1, 3},
                {4, 3, 1},
                {2, 3, 4},
                {2, 4, 3},
                {3, 2, 4},
                {3, 4, 2},
                {4, 2, 3},
                {4, 3, 2}
        };

        size_t index = 0;

        std::vector<int> iterable{1, 2, 3, 4};
        for (auto &i: permutation::Arrangements<int>(iterable, 3)) {
            BOOST_CHECK_EQUAL_COLLECTIONS(i.begin(), i.end(),
                                          resultat[index].begin(), resultat[index].end());
            ++index;
        }
    }

    BOOST_AUTO_TEST_CASE(arrangements_doublon) {
        const std::vector<std::string> resultat{"AB", "BA", "AD", "DA", "BB", "BD", "DB"};

        size_t index = 0;

        std::vector<char> iterable{'A', 'B', 'B', 'D'};
        for (auto &i: permutation::Arrangements<char>(iterable, 2)) {
            BOOST_CHECK_EQUAL_COLLECTIONS(i.begin(), i.end(),
                                          resultat[index].begin(), resultat[index].end());
            ++index;
        }
    }

    BOOST_AUTO_TEST_CASE(combinaisons_simple) {
        const std::vector<std::vector<int>> resultat{
                {1, 2, 3},
                {1, 2, 4},
                {1, 3, 4},
                {2, 3, 4}
        };

        size_t index = 0;
        std::vector<int> iterable{1, 2, 3, 4};
        for (auto i : permutation::Combinaisons<int>(iterable, 3)) {
            BOOST_CHECK_EQUAL_COLLECTIONS(i.begin(), i.end(),
                                          resultat[index].begin(), resultat[index].end());
            ++index;
        }
    }

    BOOST_AUTO_TEST_CASE(combinaisons_doublon) {
        const std::vector<std::string> resultat{"AB", "AC", "AD", "BC", "BD", "CD"};

        size_t index = 0;
        std::vector<char> iterable{'A', 'B', 'C', 'D'};
        for (auto i : permutation::Combinaisons<char>(iterable, 2)) {
            BOOST_CHECK_EQUAL_COLLECTIONS(i.begin(), i.end(),
                                          resultat[index].begin(), resultat[index].end());
            ++index;
        }
    }

BOOST_AUTO_TEST_SUITE_END()


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

// Copyright (c) 2011-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2014 Adam Wulkiewicz, Lodz, Poland.

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

//[boost_polygon_box
//`Shows how to use Boost.Polygon rectangle_data within Boost.Geometry

#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/adapted/boost_polygon.hpp>

int main()
{
    typedef boost::polygon::rectangle_data<int> rect;

    rect b = boost::polygon::construct<rect>(1, 2, 3, 4);

    std::cout << "Area (using Boost.Geometry): "
        << boost::geometry::area(b) << std::endl;
    std::cout << "Area (using Boost.Polygon): "
        << boost::polygon::area(b) << std::endl;

    return 0;
}

//]

//[boost_polygon_box_output
/*`
Output:
[pre
Area (using Boost.Geometry): 4
Area (using Boost.Polygon): 4
]
*/
//]