// Copyright (C) 2010  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.

#include "tester.h"
#include <dlib/manifold_regularization.h>
#include <dlib/svm.h>
#include <dlib/rand.h>
#include <dlib/string.h>
#include <dlib/graph_utils_threaded.h>
#include <vector>
#include <sstream>
#include <ctime>

namespace
{
    using namespace test;
    using namespace dlib;
    using namespace std;
    dlib::logger dlog("test.linear_manifold_regularizer");

    template <typename hash_type, typename samples_type>
    void test_find_k_nearest_neighbors_lsh(
        const samples_type& samples
    )
    {
        std::vector<sample_pair> edges1, edges2;

        find_k_nearest_neighbors(samples, cosine_distance(), 2, edges1);
        find_k_nearest_neighbors_lsh(samples, cosine_distance(), hash_type(), 2, 6, edges2, 2);

        std::sort(edges1.begin(), edges1.end(), order_by_index<sample_pair>);
        std::sort(edges2.begin(), edges2.end(), order_by_index<sample_pair>);

        DLIB_TEST_MSG(edges1.size() == edges2.size(), edges1.size() << "    " << edges2.size());
        for (unsigned long i = 0; i < edges1.size(); ++i)
        {
            DLIB_TEST(edges1[i] == edges2[i]);
            DLIB_TEST_MSG(std::abs(edges1[i].distance() - edges2[i].distance()) < 1e-7,
                edges1[i].distance() - edges2[i].distance());
        }
    }

    template <typename scalar_type>
    void test_knn_lsh_sparse()
    {
        dlib::rand rnd;
        std::vector<std::map<unsigned long,scalar_type> > samples;
        samples.resize(20);
        for (unsigned int i = 0; i < samples.size(); ++i)
        {
            samples[i][0] = rnd.get_random_gaussian();
            samples[i][2] = rnd.get_random_gaussian();
        }

        test_find_k_nearest_neighbors_lsh<hash_similar_angles_64>(samples);
        test_find_k_nearest_neighbors_lsh<hash_similar_angles_128>(samples);
        test_find_k_nearest_neighbors_lsh<hash_similar_angles_256>(samples);
        test_find_k_nearest_neighbors_lsh<hash_similar_angles_512>(samples);
    }

    template <typename scalar_type>
    void test_knn_lsh_dense()
    {
        dlib::rand rnd;
        std::vector<matrix<scalar_type,0,1> > samples;
        samples.resize(20);
        for (unsigned int i = 0; i < samples.size(); ++i)
        {
            samples[i].set_size(2);
            samples[i](0) = rnd.get_random_gaussian();
            samples[i](1) = rnd.get_random_gaussian();
        }

        test_find_k_nearest_neighbors_lsh<hash_similar_angles_64>(samples);
        test_find_k_nearest_neighbors_lsh<hash_similar_angles_128>(samples);
        test_find_k_nearest_neighbors_lsh<hash_similar_angles_256>(samples);
        test_find_k_nearest_neighbors_lsh<hash_similar_angles_512>(samples);
    }

    class linear_manifold_regularizer_tester : public tester
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object represents a unit test.  When it is constructed
                it adds itself into the testing framework.
        !*/
    public:
        linear_manifold_regularizer_tester (
        ) :
            tester (
                "test_linear_manifold_regularizer",       // the command line argument name for this test
                "Run tests on the linear_manifold_regularizer object.", // the command line argument description
                0                     // the number of command line arguments for this test
            )
        {
            seed = 1;
        }

        dlib::rand rnd;

        unsigned long seed;

        typedef matrix<double, 0, 1> sample_type;
        typedef radial_basis_kernel<sample_type> kernel_type;

        void do_the_test()
        {
            print_spinner();
            std::vector<sample_type> samples;

            // Declare an instance of the kernel we will be using.
            const kernel_type kern(0.1);

            const unsigned long num_points = 200;

            // create a large dataset with two concentric circles.
            generate_circle(samples, 1, num_points);  // circle of radius 1
            generate_circle(samples, 5, num_points);  // circle of radius 5

            std::vector<sample_pair> edges;
            find_percent_shortest_edges_randomly(samples, squared_euclidean_distance(0.1, 4), 1, 10000, "random seed", edges);

            dlog << LTRACE << "number of edges generated: " << edges.size();

            empirical_kernel_map<kernel_type> ekm;

            ekm.load(kern, randomly_subsample(samples, 100));

            // Project all the samples into the span of our 50 basis samples
            for (unsigned long i = 0; i < samples.size(); ++i)
                samples[i] = ekm.project(samples[i]);

            // Now create the manifold regularizer.   The result is a transformation matrix that
            // embodies the manifold assumption discussed above.
            linear_manifold_regularizer<sample_type> lmr;
            lmr.build(samples, edges, use_gaussian_weights(0.1));
            matrix<double> T = lmr.get_transformation_matrix(10000);

            print_spinner();

            // generate the T matrix manually and make sure it matches.  The point of this test
            // is to make sure that the more complex version of this that happens inside the linear_manifold_regularizer
            // is correct.  It uses a tedious block of loops to do it in a way that is a lot faster for sparse
            // W matrices but isn't super straight forward.
            matrix<double> X(samples[0].size(), samples.size());
            for (unsigned long i = 0; i < samples.size(); ++i)
                set_colm(X,i) = samples[i];

            matrix<double> W(samples.size(), samples.size());
            W = 0;
            for (unsigned long i = 0; i < edges.size(); ++i)
            {
                W(edges[i].index1(), edges[i].index2()) = use_gaussian_weights(0.1)(edges[i]);
                W(edges[i].index2(), edges[i].index1()) = use_gaussian_weights(0.1)(edges[i]);
            }
            matrix<double> L = diagm(sum_rows(W)) - W;
            matrix<double> trueT = inv_lower_triangular(chol(identity_matrix<double>(X.nr()) + (10000.0/sum(lowerm(W)))*X*L*trans(X)));

            dlog << LTRACE << "T error: "<< max(abs(T - trueT));
            DLIB_TEST(max(abs(T - trueT)) < 1e-7);

            print_spinner();
            // Apply the transformation generated by the linear_manifold_regularizer to
            // all our samples.
            for (unsigned long i = 0; i < samples.size(); ++i)
                samples[i] = T*samples[i];

            // For convenience, generate a projection_function and merge the transformation
            // matrix T into it.
            projection_function<kernel_type> proj = ekm.get_projection_function();
            proj.weights = T*proj.weights;

            // Pick 2 different labeled points.  One on the inner circle and another on the outer.
            // For each of these test points we will see if using the single plane that separates
            // them is a good way to separate the concentric circles.  Also do this a bunch
            // of times with different randomly chosen points so we can see how robust the result is.
            for (int itr = 0; itr < 10; ++itr)
            {
                print_spinner();
                std::vector<sample_type> test_points;
                // generate a random point from the radius 1 circle
                generate_circle(test_points, 1, 1);
                // generate a random point from the radius 5 circle
                generate_circle(test_points, 5, 1);

                // project the two test points into kernel space.  Recall that this projection_function
                // has the manifold regularizer incorporated into it.
                const sample_type class1_point = proj(test_points[0]);
                const sample_type class2_point = proj(test_points[1]);

                double num_wrong = 0;

                // Now attempt to classify all the data samples according to which point
                // they are closest to.  The output of this program shows that without manifold
                // regularization this test will fail but with it it will perfectly classify
                // all the points.
                for (unsigned long i = 0; i < samples.size(); ++i)
                {
                    double distance_to_class1 = length(samples[i] - class1_point);
                    double distance_to_class2 = length(samples[i] - class2_point);

                    bool predicted_as_class_1 = (distance_to_class1 < distance_to_class2);

                    bool really_is_class_1 = (i < num_points);

                    // now count how many times we make a mistake
                    if (predicted_as_class_1 != really_is_class_1)
                        ++num_wrong;
                }

                DLIB_TEST_MSG(num_wrong == 0, num_wrong);
            }

        }

        void generate_circle (
            std::vector<sample_type>& samples,
            double radius,
            const long num
        )
        {
            sample_type m(2,1);

            for (long i = 0; i < num; ++i)
            {
                double sign = 1;
                if (rnd.get_random_double() < 0.5)
                    sign = -1;
                m(0) = 2*radius*rnd.get_random_double()-radius;
                m(1) = sign*sqrt(radius*radius - m(0)*m(0));

                samples.push_back(m);
            }
        }

        void test_knn1()
        {
            std::vector<matrix<double,2,1> > samples;

            matrix<double,2,1> test;

            test = 0,0;  samples.push_back(test);
            test = 1,1;  samples.push_back(test);
            test = 1,-1;  samples.push_back(test);
            test = -1,1;  samples.push_back(test);
            test = -1,-1;  samples.push_back(test);

            std::vector<sample_pair> edges;
            find_k_nearest_neighbors(samples, squared_euclidean_distance(), 1, edges);
            DLIB_TEST(edges.size() == 4);

            std::sort(edges.begin(), edges.end(), &order_by_index<sample_pair>);

            DLIB_TEST(edges[0] == sample_pair(0,1,0));
            DLIB_TEST(edges[1] == sample_pair(0,2,0));
            DLIB_TEST(edges[2] == sample_pair(0,3,0));
            DLIB_TEST(edges[3] == sample_pair(0,4,0));

            find_k_nearest_neighbors(samples, squared_euclidean_distance(), 3, edges);
            DLIB_TEST(edges.size() == 8);

            find_k_nearest_neighbors(samples, squared_euclidean_distance(3.9, 4.1), 3, edges);
            DLIB_TEST(edges.size() == 4);

            std::sort(edges.begin(), edges.end(), &order_by_index<sample_pair>);

            DLIB_TEST(edges[0] == sample_pair(1,2,0));
            DLIB_TEST(edges[1] == sample_pair(1,3,0));
            DLIB_TEST(edges[2] == sample_pair(2,4,0));
            DLIB_TEST(edges[3] == sample_pair(3,4,0));

            find_k_nearest_neighbors(samples, squared_euclidean_distance(30000, 4.1), 3, edges);
            DLIB_TEST(edges.size() == 0);
        }

        void test_knn1_approx()
        {
            std::vector<matrix<double,2,1> > samples;

            matrix<double,2,1> test;

            test = 0,0;  samples.push_back(test);
            test = 1,1;  samples.push_back(test);
            test = 1,-1;  samples.push_back(test);
            test = -1,1;  samples.push_back(test);
            test = -1,-1;  samples.push_back(test);

            std::vector<sample_pair> edges;
            find_approximate_k_nearest_neighbors(samples, squared_euclidean_distance(), 1, 10000, seed, edges);
            DLIB_TEST(edges.size() == 4);

            std::sort(edges.begin(), edges.end(), &order_by_index<sample_pair>);

            DLIB_TEST(edges[0] == sample_pair(0,1,0));
            DLIB_TEST(edges[1] == sample_pair(0,2,0));
            DLIB_TEST(edges[2] == sample_pair(0,3,0));
            DLIB_TEST(edges[3] == sample_pair(0,4,0));

            find_approximate_k_nearest_neighbors(samples, squared_euclidean_distance(), 3, 10000, seed, edges);
            DLIB_TEST(edges.size() == 8);

            find_approximate_k_nearest_neighbors(samples, squared_euclidean_distance(3.9, 4.1), 3, 10000, seed, edges);
            DLIB_TEST(edges.size() == 4);

            std::sort(edges.begin(), edges.end(), &order_by_index<sample_pair>);

            DLIB_TEST(edges[0] == sample_pair(1,2,0));
            DLIB_TEST(edges[1] == sample_pair(1,3,0));
            DLIB_TEST(edges[2] == sample_pair(2,4,0));
            DLIB_TEST(edges[3] == sample_pair(3,4,0));

            find_approximate_k_nearest_neighbors(samples, squared_euclidean_distance(30000, 4.1), 3, 10000, seed, edges);
            DLIB_TEST(edges.size() == 0);
        }

        void test_knn2()
        {
            std::vector<matrix<double,2,1> > samples;

            matrix<double,2,1> test;

            test = 1,1;  samples.push_back(test);
            test = 1,-1;  samples.push_back(test);
            test = -1,1;  samples.push_back(test);
            test = -1,-1;  samples.push_back(test);

            std::vector<sample_pair> edges;
            find_k_nearest_neighbors(samples, squared_euclidean_distance(), 2, edges);
            DLIB_TEST(edges.size() == 4);

            std::sort(edges.begin(), edges.end(), &order_by_index<sample_pair>);

            DLIB_TEST(edges[0] == sample_pair(0,1,0));
            DLIB_TEST(edges[1] == sample_pair(0,2,0));
            DLIB_TEST(edges[2] == sample_pair(1,3,0));
            DLIB_TEST(edges[3] == sample_pair(2,3,0));

            find_k_nearest_neighbors(samples, squared_euclidean_distance(), 200, edges);
            DLIB_TEST(edges.size() == 4*3/2);
        }

        void test_knn2_approx()
        {
            std::vector<matrix<double,2,1> > samples;

            matrix<double,2,1> test;

            test = 1,1;  samples.push_back(test);
            test = 1,-1;  samples.push_back(test);
            test = -1,1;  samples.push_back(test);
            test = -1,-1;  samples.push_back(test);

            std::vector<sample_pair> edges;
            // For this simple graph and high number of samples we will do we should obtain the exact
            // knn solution.
            find_approximate_k_nearest_neighbors(samples, squared_euclidean_distance(), 2, 10000, seed,  edges);
            DLIB_TEST(edges.size() == 4);

            std::sort(edges.begin(), edges.end(), &order_by_index<sample_pair>);

            DLIB_TEST(edges[0] == sample_pair(0,1,0));
            DLIB_TEST(edges[1] == sample_pair(0,2,0));
            DLIB_TEST(edges[2] == sample_pair(1,3,0));
            DLIB_TEST(edges[3] == sample_pair(2,3,0));

            find_approximate_k_nearest_neighbors(samples, squared_euclidean_distance(), 200, 10000, seed,  edges);
            DLIB_TEST(edges.size() == 4*3/2);
        }

        void perform_test (
        )
        {
            for (int i = 0; i < 5; ++i)
            {
                do_the_test();

                ++seed;
                test_knn1_approx();
                test_knn2_approx();
            }
            test_knn1();
            test_knn2();
            test_knn_lsh_sparse<double>();
            test_knn_lsh_sparse<float>();
            test_knn_lsh_dense<double>();
            test_knn_lsh_dense<float>();

        }
    };

    // Create an instance of this object.  Doing this causes this test
    // to be automatically inserted into the testing framework whenever this cpp file
    // is linked into the project.  Note that since we are inside an unnamed-namespace
    // we won't get any linker errors about the symbol a being defined multiple times.
    linear_manifold_regularizer_tester a;

}


#include "FWCore/MessageLogger/interface/MessageLogger.h"
#include "TrackstersPCA.h"
#include "TPrincipal.h"

#include <iostream>

#include <Eigen/Core>
#include <Eigen/Dense>

void ticl::assignPCAtoTracksters(std::vector<Trackster> &tracksters,
                                 const std::vector<reco::CaloCluster> &layerClusters,
                                 double z_limit_em,
                                 bool energyWeight) {
  LogDebug("TrackstersPCA_Eigen") << "------- Eigen -------" << std::endl;

  for (auto &trackster : tracksters) {
    Eigen::Vector3d point;
    point << 0., 0., 0.;
    Eigen::Vector3d barycenter;
    barycenter << 0., 0., 0.;

    auto fillPoint = [&](const reco::CaloCluster &c, const float weight = 1.f) {
      point[0] = weight * c.x();
      point[1] = weight * c.y();
      point[2] = weight * c.z();
    };

    // Initialize this trackster with default, dummy values
    trackster.setRawEnergy(0.f);
    trackster.setRawEmEnergy(0.f);
    trackster.setRawPt(0.f);
    trackster.setRawEmPt(0.f);

    size_t N = trackster.vertices().size();
    float weight = 1.f / N;
    float weights2_sum = 0.f;
    Eigen::Vector3d sigmas;
    sigmas << 0., 0., 0.;
    Eigen::Vector3d sigmasEigen;
    sigmasEigen << 0., 0., 0.;
    Eigen::Matrix3d covM = Eigen::Matrix3d::Zero();

    for (size_t i = 0; i < N; ++i) {
      auto fraction = 1.f / trackster.vertex_multiplicity(i);
      trackster.addToRawEnergy(layerClusters[trackster.vertices(i)].energy() * fraction);
      if (std::abs(layerClusters[trackster.vertices(i)].z()) <= z_limit_em)
        trackster.addToRawEmEnergy(layerClusters[trackster.vertices(i)].energy() * fraction);

      // Compute the weighted barycenter.
      if (energyWeight)
        weight = layerClusters[trackster.vertices(i)].energy() * fraction;
      fillPoint(layerClusters[trackster.vertices(i)], weight);
      for (size_t j = 0; j < 3; ++j)
        barycenter[j] += point[j];
    }
    if (energyWeight && trackster.raw_energy())
      barycenter /= trackster.raw_energy();

    // Compute the Covariance Matrix and the sum of the squared weights, used
    // to compute the correct normalization.
    // The barycenter has to be known.
    for (size_t i = 0; i < N; ++i) {
      fillPoint(layerClusters[trackster.vertices(i)]);
      if (energyWeight && trackster.raw_energy())
        weight =
            (layerClusters[trackster.vertices(i)].energy() / trackster.vertex_multiplicity(i)) / trackster.raw_energy();
      weights2_sum += weight * weight;
      for (size_t x = 0; x < 3; ++x)
        for (size_t y = 0; y <= x; ++y) {
          covM(x, y) += weight * (point[x] - barycenter[x]) * (point[y] - barycenter[y]);
          covM(y, x) = covM(x, y);
        }
    }
    covM *= 1. / (1. - weights2_sum);

    // Perform the actual decomposition
    Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d>::RealVectorType eigenvalues_fromEigen;
    Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d>::EigenvectorsType eigenvectors_fromEigen;
    Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(covM);
    if (eigensolver.info() != Eigen::Success) {
      eigenvalues_fromEigen = eigenvalues_fromEigen.Zero();
      eigenvectors_fromEigen = eigenvectors_fromEigen.Zero();
    } else {
      eigenvalues_fromEigen = eigensolver.eigenvalues();
      eigenvectors_fromEigen = eigensolver.eigenvectors();
    }

    // Compute the spread in the both spaces.
    for (size_t i = 0; i < N; ++i) {
      fillPoint(layerClusters[trackster.vertices(i)]);
      sigmas += weight * (point - barycenter).cwiseAbs2();
      Eigen::Vector3d point_transformed = eigenvectors_fromEigen * (point - barycenter);
      if (energyWeight && trackster.raw_energy())
        weight =
            (layerClusters[trackster.vertices(i)].energy() / trackster.vertex_multiplicity(i)) / trackster.raw_energy();
      sigmasEigen += weight * (point_transformed.cwiseAbs2());
    }
    sigmas /= (1. - weights2_sum);
    sigmasEigen /= (1. - weights2_sum);

    // Add trackster attributes
    trackster.setBarycenter(ticl::Trackster::Vector(barycenter));
    trackster.fillPCAVariables(
        eigenvalues_fromEigen, eigenvectors_fromEigen, sigmas, sigmasEigen, 3, ticl::Trackster::PCAOrdering::ascending);

    LogDebug("TrackstersPCA") << "Use energy weighting: " << energyWeight << std::endl;
    LogDebug("TrackstersPCA") << "\nTrackster characteristics: " << std::endl;
    LogDebug("TrackstersPCA") << "Size: " << N << std::endl;
    LogDebug("TrackstersPCA") << "Energy: " << trackster.raw_energy() << std::endl;
    LogDebug("TrackstersPCA") << "raw_pt: " << trackster.raw_pt() << std::endl;
    LogDebug("TrackstersPCA") << "Means:          " << barycenter[0] << ", " << barycenter[1] << ", " << barycenter[2]
                              << std::endl;
    LogDebug("TrackstersPCA") << "EigenValues from Eigen/Tr(cov): " << eigenvalues_fromEigen[2] / covM.trace() << ", "
                              << eigenvalues_fromEigen[1] / covM.trace() << ", "
                              << eigenvalues_fromEigen[0] / covM.trace() << std::endl;
    LogDebug("TrackstersPCA") << "EigenValues from Eigen:         " << eigenvalues_fromEigen[2] << ", "
                              << eigenvalues_fromEigen[1] << ", " << eigenvalues_fromEigen[0] << std::endl;
    LogDebug("TrackstersPCA") << "EigenVector 3 from Eigen: " << eigenvectors_fromEigen(0, 2) << ", "
                              << eigenvectors_fromEigen(1, 2) << ", " << eigenvectors_fromEigen(2, 2) << std::endl;
    LogDebug("TrackstersPCA") << "EigenVector 2 from Eigen: " << eigenvectors_fromEigen(0, 1) << ", "
                              << eigenvectors_fromEigen(1, 1) << ", " << eigenvectors_fromEigen(2, 1) << std::endl;
    LogDebug("TrackstersPCA") << "EigenVector 1 from Eigen: " << eigenvectors_fromEigen(0, 0) << ", "
                              << eigenvectors_fromEigen(1, 0) << ", " << eigenvectors_fromEigen(2, 0) << std::endl;
    LogDebug("TrackstersPCA") << "Original sigmas:          " << sigmas[0] << ", " << sigmas[1] << ", " << sigmas[2]
                              << std::endl;
    LogDebug("TrackstersPCA") << "SigmasEigen in PCA space: " << sigmasEigen[2] << ", " << sigmasEigen[1] << ", "
                              << sigmasEigen[0] << std::endl;
    LogDebug("TrackstersPCA") << "covM:     \n" << covM << std::endl;
  }
}


// Boost.Geometry Index
//
// R-tree quadratic split algorithm implementation
//
// Copyright (c) 2011-2013 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)

#ifndef BOOST_GEOMETRY_INDEX_DETAIL_RTREE_QUADRATIC_REDISTRIBUTE_ELEMENTS_HPP
#define BOOST_GEOMETRY_INDEX_DETAIL_RTREE_QUADRATIC_REDISTRIBUTE_ELEMENTS_HPP

#include <algorithm>

#include <boost/geometry/index/detail/algorithms/content.hpp>
#include <boost/geometry/index/detail/algorithms/union_content.hpp>

#include <boost/geometry/index/detail/rtree/node/node.hpp>
#include <boost/geometry/index/detail/rtree/visitors/insert.hpp>
#include <boost/geometry/index/detail/rtree/visitors/is_leaf.hpp>

namespace boost { namespace geometry { namespace index {

namespace detail { namespace rtree {

namespace quadratic {

template <typename Elements, typename Parameters, typename Translator, typename Box>
struct pick_seeds
{
    typedef typename Elements::value_type element_type;
    typedef typename rtree::element_indexable_type<element_type, Translator>::type indexable_type;
    typedef typename index::detail::traits::coordinate_type<indexable_type>::type coordinate_type;
    typedef Box box_type;
    typedef typename index::detail::default_content_result<box_type>::type content_type;

    static inline void apply(Elements const& elements,
                             Parameters const& parameters,
                             Translator const& tr,
                             size_t & seed1,
                             size_t & seed2)
    {
        const size_t elements_count = parameters.get_max_elements() + 1;
        BOOST_GEOMETRY_INDEX_ASSERT(elements.size() == elements_count, "wrong number of elements");
        BOOST_GEOMETRY_INDEX_ASSERT(2 <= elements_count, "unexpected number of elements");

        content_type greatest_free_content = 0;
        seed1 = 0;
        seed2 = 1;

        for ( size_t i = 0 ; i < elements_count - 1 ; ++i )
        {
            for ( size_t j = i + 1 ; j < elements_count ; ++j )
            {
                indexable_type const& ind1 = rtree::element_indexable(elements[i], tr);
                indexable_type const& ind2 = rtree::element_indexable(elements[j], tr);

                box_type enlarged_box;
                geometry::convert(ind1, enlarged_box);
                geometry::expand(enlarged_box, ind2);

                content_type free_content = (index::detail::content(enlarged_box) - index::detail::content(ind1)) - index::detail::content(ind2);

                if ( greatest_free_content < free_content )
                {
                    greatest_free_content = free_content;
                    seed1 = i;
                    seed2 = j;
                }
            }
        }

        BOOST_GEOMETRY_INDEX_DETAIL_USE_PARAM(parameters)
    }
};

} // namespace quadratic

template <typename Value, typename Options, typename Translator, typename Box, typename Allocators>
struct redistribute_elements<Value, Options, Translator, Box, Allocators, quadratic_tag>
{
    typedef typename Options::parameters_type parameters_type;

    typedef typename rtree::node<Value, parameters_type, Box, Allocators, typename Options::node_tag>::type node;
    typedef typename rtree::internal_node<Value, parameters_type, Box, Allocators, typename Options::node_tag>::type internal_node;
    typedef typename rtree::leaf<Value, parameters_type, Box, Allocators, typename Options::node_tag>::type leaf;

    typedef typename index::detail::default_content_result<Box>::type content_type;

    template <typename Node>
    static inline void apply(Node & n,
                             Node & second_node,
                             Box & box1,
                             Box & box2,
                             parameters_type const& parameters,
                             Translator const& translator,
                             Allocators & allocators)
    {
        typedef typename rtree::elements_type<Node>::type elements_type;
        typedef typename elements_type::value_type element_type;
        typedef typename rtree::element_indexable_type<element_type, Translator>::type indexable_type;
        typedef typename index::detail::traits::coordinate_type<indexable_type>::type coordinate_type;

        elements_type & elements1 = rtree::elements(n);
        elements_type & elements2 = rtree::elements(second_node);

        BOOST_GEOMETRY_INDEX_ASSERT(elements1.size() == parameters.get_max_elements() + 1, "unexpected elements number");

        // copy original elements
        elements_type elements_copy(elements1);                                                             // MAY THROW, STRONG (alloc, copy)
        elements_type elements_backup(elements1);                                                           // MAY THROW, STRONG (alloc, copy)

        // calculate initial seeds
        size_t seed1 = 0;
        size_t seed2 = 0;
        quadratic::pick_seeds<
            elements_type,
            parameters_type,
            Translator,
            Box
        >::apply(elements_copy, parameters, translator, seed1, seed2);

        // prepare nodes' elements containers
        elements1.clear();
        BOOST_GEOMETRY_INDEX_ASSERT(elements2.empty(), "second node's elements container should be empty");

        BOOST_TRY
        {
            // add seeds
            elements1.push_back(elements_copy[seed1]);                                                      // MAY THROW, STRONG (copy)
            elements2.push_back(elements_copy[seed2]);                                                      // MAY THROW, STRONG (alloc, copy)

            // calculate boxes
            geometry::convert(rtree::element_indexable(elements_copy[seed1], translator), box1);
            geometry::convert(rtree::element_indexable(elements_copy[seed2], translator), box2);

            // remove seeds
            if (seed1 < seed2)
            {
                rtree::move_from_back(elements_copy, elements_copy.begin() + seed2);                        // MAY THROW, STRONG (copy)
                elements_copy.pop_back();
                rtree::move_from_back(elements_copy, elements_copy.begin() + seed1);                        // MAY THROW, STRONG (copy)
                elements_copy.pop_back();
            }
            else
            {
                rtree::move_from_back(elements_copy, elements_copy.begin() + seed1);                        // MAY THROW, STRONG (copy)
                elements_copy.pop_back();
                rtree::move_from_back(elements_copy, elements_copy.begin() + seed2);                        // MAY THROW, STRONG (copy)
                elements_copy.pop_back();
            }

            // initialize areas
            content_type content1 = index::detail::content(box1);
            content_type content2 = index::detail::content(box2);

            size_t remaining = elements_copy.size();

            // redistribute the rest of the elements
            while ( !elements_copy.empty() )
            {
                typename elements_type::reverse_iterator el_it = elements_copy.rbegin();
                bool insert_into_group1 = false;

                size_t elements1_count = elements1.size();
                size_t elements2_count = elements2.size();

                // if there is small number of elements left and the number of elements in node is lesser than min_elems
                // just insert them to this node
                if ( elements1_count + remaining <= parameters.get_min_elements() )
                {
                    insert_into_group1 = true;
                }
                else if ( elements2_count + remaining <= parameters.get_min_elements() )
                {
                    insert_into_group1 = false;
                }
                // insert the best element
                else
                {
                    // find element with minimum groups areas increses differences
                    content_type content_increase1 = 0;
                    content_type content_increase2 = 0;
                    el_it = pick_next(elements_copy.rbegin(), elements_copy.rend(),
                                      box1, box2, content1, content2, translator,
                                      content_increase1, content_increase2);

                    if ( content_increase1 < content_increase2 ||
                         ( content_increase1 == content_increase2 && ( content1 < content2 ||
                           ( content1 == content2 && elements1_count <= elements2_count ) )
                         ) )
                    {
                        insert_into_group1 = true;
                    }
                    else
                    {
                        insert_into_group1 = false;
                    }
                }

                // move element to the choosen group
                element_type const& elem = *el_it;
                indexable_type const& indexable = rtree::element_indexable(elem, translator);

                if ( insert_into_group1 )
                {
                    elements1.push_back(elem);                                                              // MAY THROW, STRONG (copy)
                    geometry::expand(box1, indexable);
                    content1 = index::detail::content(box1);
                }
                else
                {
                    elements2.push_back(elem);                                                              // MAY THROW, STRONG (alloc, copy)
                    geometry::expand(box2, indexable);
                    content2 = index::detail::content(box2);
                }

                BOOST_GEOMETRY_INDEX_ASSERT(!elements_copy.empty(), "expected more elements");
                typename elements_type::iterator el_it_base = el_it.base();
                rtree::move_from_back(elements_copy, --el_it_base);                                         // MAY THROW, STRONG (copy)
                elements_copy.pop_back();

                BOOST_GEOMETRY_INDEX_ASSERT(0 < remaining, "expected more remaining elements");
                --remaining;
            }
        }
        BOOST_CATCH(...)
        {
            //elements_copy.clear();
            elements1.clear();
            elements2.clear();

            rtree::destroy_elements<Value, Options, Translator, Box, Allocators>::apply(elements_backup, allocators);
            //elements_backup.clear();

            BOOST_RETHROW                                                                                // RETHROW, BASIC
        }
        BOOST_CATCH_END
    }

    // TODO: awulkiew - change following function to static member of the pick_next class?

    template <typename It>
    static inline It pick_next(It first, It last,
                               Box const& box1, Box const& box2,
                               content_type const& content1, content_type const& content2,
                               Translator const& translator,
                               content_type & out_content_increase1, content_type & out_content_increase2)
    {
        typedef typename boost::iterator_value<It>::type element_type;
        typedef typename rtree::element_indexable_type<element_type, Translator>::type indexable_type;

        content_type greatest_content_incrase_diff = 0;
        It out_it = first;
        out_content_increase1 = 0;
        out_content_increase2 = 0;

        // find element with greatest difference between increased group's boxes areas
        for ( It el_it = first ; el_it != last ; ++el_it )
        {
            indexable_type const& indexable = rtree::element_indexable(*el_it, translator);

            // calculate enlarged boxes and areas
            Box enlarged_box1(box1);
            Box enlarged_box2(box2);
            geometry::expand(enlarged_box1, indexable);
            geometry::expand(enlarged_box2, indexable);
            content_type enlarged_content1 = index::detail::content(enlarged_box1);
            content_type enlarged_content2 = index::detail::content(enlarged_box2);

            content_type content_incrase1 = (enlarged_content1 - content1);
            content_type content_incrase2 = (enlarged_content2 - content2);

            content_type content_incrase_diff = content_incrase1 < content_incrase2 ?
                content_incrase2 - content_incrase1 : content_incrase1 - content_incrase2;

            if ( greatest_content_incrase_diff < content_incrase_diff )
            {
                greatest_content_incrase_diff = content_incrase_diff;
                out_it = el_it;
                out_content_increase1 = content_incrase1;
                out_content_increase2 = content_incrase2;
            }
        }

        return out_it;
    }
};

}} // namespace detail::rtree

}}} // namespace boost::geometry::index

#endif // BOOST_GEOMETRY_INDEX_DETAIL_RTREE_QUADRATIC_REDISTRIBUTE_ELEMENTS_HPP


#include <vector>
#include <algorithm>
#include <set>
#include <iostream>
#include <armadillo>

#include "Util.h"
#include "PostCalMultiPop.h"

using namespace arma;

void printGSLPrint(mat &A, int row, int col) {
	for(int i = 0; i < row; i++) {
		for(int j = 0; j < col; j++)
			printf("%g ", A(i, j));
		printf("\n");
	}
}

string PostCalMultiPop::convertConfig2String(int * config, int size) {
	string result = "0";
	for(int i = 0; i < size; i++)
		if(config[i]==1)
			result+= "_" + convertInt(i);
	return result;
}

// We compute dmvnorm(Zcc, mean=rep(0,nrow(Rcc)), Rcc + Rcc %*% Rcc) / dmvnorm(Zcc, rep(0, nrow(Rcc)), Rcc))
// // togheter to avoid numerical over flow
double PostCalMultiPop::fracdmvnorm(mat Z, mat mean, mat R, mat diagC, int pop, double NCP) {
        mat newR = R + R * diagC  * R;
        mat ZcenterMean = Z - mean;
        mat res1 = trans(ZcenterMean) * inv(R) * (ZcenterMean);
        mat res2 = trans(ZcenterMean) * inv(newR) *  (ZcenterMean);

        double v1 = res1(0,0)/2-res2(0,0)/2-baseValue[pop]/2;
        return(exp(v1)/sqrt(det(newR))* sqrt(det(R)));
}

double PostCalMultiPop::dmvnorm(mat Z, mat mean, mat R) {
        mat ZcenterMean = Z - mean;
        mat res = trans(ZcenterMean) * inv(R) * (ZcenterMean);
        double v1 = res(0,0);
        double v2 = log(sqrt(det(R)));
        return (exp(-v1/2-v2));
}

// cc=causal SNPs
// Rcc = LD of causal SNPs
// Zcc = Z-score of causal SNPs
// dmvnorm(Zcc, mean=rep(0,nrow(Rcc)), Rcc + Rcc %*% Rcc) / dmvnorm(Zcc, rep(0, nrow(Rcc)), Rcc))
//
double PostCalMultiPop::fastLikelihood(int * configure, int pop, double NCP) {
	int causalCount = 0;
	vector <int> causalIndex;

	for(int i = 0; i < snpCount; i++) {
		causalCount += configure[i];
		if(configure[i] == 1)
			causalIndex.push_back(i);
	}

	if (causalCount == 0) {
		int maxVal = 0;
		for(int i = 0; i < snpCount; i++) {
			if (maxVal < abs(stat[pop][i]))
				maxVal = stat[pop][i];
		}
		baseValue[pop] = maxVal * maxVal;
	}

	mat Rcc(causalCount, causalCount, fill::zeros);
	mat Zcc(causalCount, 1, fill::zeros);
	mat mean(causalCount, 1, fill::zeros);
	mat diagC(causalCount, causalCount, fill::zeros);

	for (int i = 0; i < causalCount; i++){
		for(int j = 0; j < causalCount; j++) {
			Rcc(i,j) = sigmaMatrix[pop](causalIndex[i], causalIndex[j]);
		}
		Zcc(i,0) = stat[pop][causalIndex[i]];
		diagC(i,i) = NCP;
	}
	return fracdmvnorm(Zcc, mean, Rcc, diagC,pop, NCP);
}

int PostCalMultiPop::nextBinary(int * data, int size) {
	int i = 0;
	int total_one = 0;
	int index = size-1;
        int one_countinus_in_end = 0;

        while(index >= 0 && data[index] == 1) {
                index = index - 1;
                one_countinus_in_end = one_countinus_in_end + 1;
	}
	if(index >= 0) {
        	while(index >= 0 && data[index] == 0) {
               	 index = index - 1;
		}
	}
        if(index == -1) {
                while(i <  one_countinus_in_end+1 && i < size) {
                        data[i] = 1;
                        i=i+1;
		}
                i = 0;
                while(i < size-one_countinus_in_end-1) {
                        data[i+one_countinus_in_end+1] = 0;
                        i=i+1;
		}
	}
        else if(one_countinus_in_end == 0) {
                data[index] = 0;
                data[index+1] = 1;
	} else {
                data[index] = 0;
                while(i < one_countinus_in_end + 1) {
                        data[i+index+1] = 1;
			if(i+index+1 >= size)
				printf("ERROR3 %d\n", i+index+1);
                        i=i+1;
		}
                i = 0;
                while(i < size - index - one_countinus_in_end - 2) {
                        data[i+index+one_countinus_in_end+2] = 0;
			if(i+index+one_countinus_in_end+2 >= size) {
				printf("ERROR4 %d\n", i+index+one_countinus_in_end+2);
			}
                        i=i+1;
		}
	}
	i = 0;
	total_one = 0;
	for(i = 0; i < size; i++)
		if(data[i] == 1)
			total_one = total_one + 1;

	return(total_one);
}

double PostCalMultiPop::computeTotalLikelihood(double NCP) {
	int num = 0;
	double sumLikelihood = 0;
	double tmp_likelihood = 1;
	long int total_iteration = 0 ;
	int * configure = (int *) malloc (snpCount * sizeof(int *)); // original data

	for(long int i = 0; i <= maxCausalSNP; i++)
		total_iteration = total_iteration + nCr(snpCount, i);
	cout << snpCount << endl;
	cout << "Max Causal=" << maxCausalSNP << endl;
	cout << "Total="      << total_iteration << endl;
	for(long int i = 0; i < snpCount; i++)
		configure[i] = 0;
	for(long int i = 0; i < total_iteration; i++) {
		tmp_likelihood = 1;
		for (int pop =0 ; pop < popNum; pop++)
                	tmp_likelihood = tmp_likelihood * fastLikelihood(configure, pop, NCP) * (pow(gamma, num))*(pow(1-gamma, snpCount-num));
		sumLikelihood += tmp_likelihood;
		for(int j = 0; j < snpCount; j++) {
                        postValues[j] = postValues[j] + tmp_likelihood * configure[j];
		}
		histValues[num] = histValues[num] + tmp_likelihood;
                num = nextBinary(configure, snpCount);
       		if(i % 100000 == 0)
			cout << i << " "  << sumLikelihood << endl;
	}
	for(int i = 0; i <= maxCausalSNP; i++)
		histValues[i] = histValues[i]/sumLikelihood;
        free(configure);
        return(sumLikelihood);
}

/*
	stat is the z-scpres
	sigma is the correaltion matrix
	G is the map between snp and the gene (snp, gene)
*/
double PostCalMultiPop::findOptimalSetGreedy(double NCP, char * pcausalSet, int *rank,  double inputRho, string outputFileName) {
	int index = 0;
        double rho = 0;
        double total_post = 0;

        totalLikeLihoodLOG = computeTotalLikelihood(NCP);

	export2File(outputFileName+".log", totalLikeLihoodLOG); //Output the total likelihood to the log File
	for(int i = 0; i < snpCount; i++)
		total_post += postValues[i];
	printf("Total Likelihood= %e SNP=%d \n", total_post, snpCount);

        std::vector<data> items;
        std::set<int>::iterator it;
	//output the poster to files
        for(int i = 0; i < snpCount; i++) {
             //printf("%d==>%e ",i, postValues[i]/total_likelihood);
             items.push_back(data(postValues[i]/total_post, i, 0));
        }
        printf("\n");
        std::sort(items.begin(), items.end(), by_number());
        for(int i = 0; i < snpCount; i++)
                rank[i] = items[i].index1;

        for(int i = 0; i < snpCount; i++)
                pcausalSet[i] = '0';
        do{
                rho += postValues[rank[index]]/total_post;
                pcausalSet[rank[index]] = '1';
                printf("%d %e\n", rank[index], rho);
                index++;
        } while( rho < inputRho);

        printf("\n");
	return(0);
}


#include <boost/gil.hpp>
using namespace boost::gil;

template <typename Out>
struct halfdiff_cast_channels {
    template <typename T> Out operator()(const T& in1, const T& in2) const {
        return Out((in1-in2)/2);
    }
};

template <typename SrcView, typename DstView>
void x_gradient(const SrcView& src, const DstView& dst) {
    typedef typename channel_type<DstView>::type dst_channel_t;

    for (int y=0; y<src.height(); ++y) {
        typename SrcView::x_iterator src_it = src.row_begin(y);
        typename DstView::x_iterator dst_it = dst.row_begin(y);

        for (int x=1; x<src.width()-1; ++x)
            static_transform(src_it[x-1], src_it[x+1], dst_it[x],
                               halfdiff_cast_channels<dst_channel_t>());
    }
}

void ComputeXGradientGray8(const unsigned char* src_pixels, ptrdiff_t src_row_bytes, int w, int h,
                                   signed char* dst_pixels, ptrdiff_t dst_row_bytes) {
    gray8c_view_t src = interleaved_view(w, h, (const gray8_pixel_t*)src_pixels,src_row_bytes);
    gray8s_view_t dst = interleaved_view(w, h, (     gray8s_pixel_t*)dst_pixels,dst_row_bytes);
    x_gradient(src,dst);
}

int main() {
    return 0;
}


#pragma once

// Polyvec
#include <polyvec/core/macros.hpp>
#include <polyvec/polygon-tracer/boundary-graph.hpp>
#include <polyvec/pipeline_helper.hpp>
#include <polyvec/shortest-path/dijkstra.hpp>
#include <polyvec/curve-tracer/spline.hpp>
#include <polyvec/regularity/symmetry.hpp>

// Eigen
#include <Eigen/Core>

// libc++
#include <unordered_map>
#include <vector>
#include <memory>

NAMESPACE_BEGIN(polyvec)

// Forward declarations
class FitClassifier;

//
class ImageBoundary {
public:
    ImageBoundary(
        const Eigen::Matrix2Xi& points,
        const int polygon_id,
        const Eigen::Vector4d& color
    );

    // Returns true if the boundary vertex 'v' is flat, if that is the case, edges will not be
    // constructed starting/from or to it
    bool is_flat(const int v) const;

    // Constructs the boundary graph allowing for flat vertices
    void initialize_graph_with_flat_vertices(const Eigen::VectorXi& B_flat);

    // Fits a polygon with regularization
    void trace_polygon(
        const std::unordered_set<int>& kopf_junctions = std::unordered_set<int>()
    );

    // Fits a polygon without regularization
    void trace_polygon_simple();

    void compute_regularities();

    // Resets the graph to its original state preserving the fitting results
    void reset_graph();

    // Adds an edge to the boundary graph, validating the indices
    void add_edge(const polyfit::BoundaryGraph::Edge& e);

    void remove_edges_crossing_vertices(const std::vector<int> V);

    double bounding_box_area()const;

    // Prunes the boundary graph so that the polygon will go through the specified corners.
    // It does not trace a new polygon.
    // The subpath *will* exist regardless of the current state of the boundary graph, but
    // a polygon is not guaranteed to exist.
    // It validates the subpath indices against the boundary
    void force_graph_subpath(const Eigen::VectorXi& subpath);

    // Returns the list of vertices in the polygon contained in [v_src, v_dst]
    Eigen::VectorXi get_polygon_subpath(const int v_src, const int v_dst) const;

    // Same as get_polygon_subpath but returns the index of the first and last vertex in the closed boundary interval [v_src, v_dst]
    std::pair<int, int> get_polygon_subpath_bounds(const int v_src, const int v_dst) const;

    // Returns the sum of the edge cost for all polygon edges contained within [v_first, v_last]
    double calculate_subpath_energy(const int v_first, const int v_last) const;

    // Returns the index of the polygon vertex matching the specified boundary vertex or -1 if it doesn't exist.
    int find_polygon_vertex_for_boundary_vertex(const int v)const;

    // assumes that v is a C^0 corner
    int find_line_before_polygon_vertex(const int v)const;
    int find_line_after_polygon_vertex(const int v)const;

    // Obtains a tangent classification for each corner. A polygon must have been traced
    // No curves are fit
    void classify_corners(FitClassifier& classifier);

    // Initializes the curves with their initial guesses
    void fit_curves_initial_guess();

    void flip_coordinate_system(const polyfit::BoundaryGraph::EdgeID edge);

    std::pair<int, int> get_primitives_interval_open   (const int v_first, const int v_last, const int direction) const;
    std::pair<int, int> get_primitives_interval_closed (const int v_first, const int v_last, const int direction) const;

    std::vector<CurvePrimitive> clone_curves_at_corner_reversed (const int v_P) const;

    // angle at *polygon* vertex
    double angle_at_vertex(const int v);

    // -----------------------------------------------
    // Getters
    int id() const { return _id; }
    const Eigen::Vector4d& color() const { return _color; }

    // Input raster
    const Eigen::Matrix2Xi& raster_points() const { return _points; }
    const Eigen::Matrix2Xd& raster_points_as_double()const { return _points_as_double; }

    // Boundary
    const Eigen::Matrix2Xd& boundary_points() const { return _polygon.B; }
    const polyfit::BoundaryGraph::AccuracyMap& accuracy_map()const { return _accuracy_map; }
    const std::vector<polyfit::BoundaryGraph::Edge>& edges() const { return _polygon.E; }
    std::vector<polyfit::BoundaryGraph::Edge>& edges() { return _polygon.E; }
    const std::vector<polyfit::BoundaryGraph::Edge>& edges_original() const { return _E_original; }
    std::vector<size_t>& E_delete() { return _E_delete; }
    const std::vector<int>& convexities() const { return _boundary_convexities; } // inside
    std::vector<polyfit::Symmetry::SubPath>& raster_symmetries() { return _polygon.raster_symmetries; }
    std::vector<polyfit::Symmetry::SubPath>& raster_symmetries_local() { return _polygon.raster_symmetries_local; }
    const std::vector<polyfit::Symmetry::SubPath>& raster_symmetries() const { return _polygon.raster_symmetries; }

    // Polygon
    Eigen::VectorXi& polygon_vertices() { return _polygon.P; }
    const Eigen::VectorXi& polygon_vertices() const { return _polygon.P; }
    Eigen::Matrix2Xd& polygon_points() { return _polygon.PP; }
    const Eigen::Matrix2Xd& polygon_points() const { return _polygon.PP; }
    std::vector<Eigen::Index>& midpoints() { return _polygon.M; }
    const std::vector<Eigen::Index>& midpoints() const { return _polygon.M; }
    std::vector<bool>& midpoints_bits() { return _polygon.midpoints; }
    const std::vector<bool>& midpoints_bits() const { return _polygon.midpoints; }
    const std::vector<int>& polygon_convexities_inside() const { return _convexities_inside; }
    const std::vector<int>& polygon_convexities_outside() const { return _convexities_outside; }
    const polyfit::Regularity::RegularityInformation& regularity_graph() const { return _polygon.RE; }
	polyfit::Regularity::RegularityInformation& regularity_graph() { return _polygon.RE; }

    // Smooth fits
    std::vector<TangentFitType>& tangents_fits() { return _tangents; }
    const std::vector<TangentFitType>& tangents_fits() const { return _tangents; }
    CurvePrimitiveSequence& spline() { return _spline; }
    const CurvePrimitiveSequence& spline()const { return _spline; }
    const std::vector<polyvec::AttemptInfo>& fitting_attempts() const { return _fitting_attempts; }

private:
    void _initialize_curve_fitter();
    void _update_polygon();
    void _update_convexities();
    bool _is_polygon_data_consistent_and_exists();
    void _initialize_graph_if_necessary();

    // Initialization
    const int _id;
    const Eigen::Vector4d _color;

    // Raster
    Eigen::Matrix2Xi _points;
    Eigen::Matrix2Xd _points_as_double;
    std::vector<int> _boundary_convexities; // w.r.t. _polygon.B
    std::vector<bool> _B_flat;

    // Polygon
    PolygonData                               _polygon;
    std::vector<polyfit::BoundaryGraph::Edge> _E_original;
    polyfit::BoundaryGraph::AccuracyMap                _accuracy_map;
    polyfit::ShortestPath::State              _shortest_path_state;
    std::vector<size_t>                       _E_delete;

    //
    std::vector<int>                          _convexities_inside;
    std::vector<int>                          _convexities_outside;

    // Curves
    bool _curves_dirty;
    std::unique_ptr<CurveSequenceFitter> _spline_fitter;
    CurvePrimitiveSequence _spline;
    std::vector<TangentFitType> _tangents;
    std::vector<polyvec::AttemptInfo> _fitting_attempts;

    std::vector<bool> _edge_coordinate_systems;
};

NAMESPACE_END(polyvec)


//---------------------------------------------------------------------------//
// Copyright (c) 2020-2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020-2021 Nikita Kaskov <nbering@nil.foundation>
// Copyright (c) 2020-2021 Ilias Khairullin <ilias@nil.foundation>
// Copyright (c) 2022 Ekaterina Chukavina <kate@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.
//---------------------------------------------------------------------------//

#define BOOST_TEST_MODULE algebra_short_weierstrass_coordinates_test

#include <iostream>
#include <type_traits>

#include <boost/test/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/data/monomorphic.hpp>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include <nil/crypto3/algebra/curves/secp_r1.hpp>

#include <nil/crypto3/algebra/random_element.hpp>

#include <nil/crypto3/multiprecision/cpp_int.hpp>

using namespace nil::crypto3::algebra;

template<typename FieldParams>
void print_field_element(std::ostream &os, const typename fields::detail::element_fp<FieldParams> &e) {
    std::cout << e.data << std::endl;
}

template<typename CurveParams, typename Form>
void print_curve_point(std::ostream &os,
                       const curves::detail::curve_element<CurveParams, Form, curves::coordinates::affine> &p) {
    os << "( X: [";
    print_field_element(os, p.X);
    os << "], Y: [";
    print_field_element(os, p.Y);
    os << "] )" << std::endl;
}

template<typename CurveParams, typename Form, typename Coordinates>
typename std::enable_if<std::is_same<Coordinates, curves::coordinates::jacobian_with_a4_minus_3>::value||
                        std::is_same<Coordinates, curves::coordinates::jacobian>::value||
                        std::is_same<Coordinates, curves::coordinates::projective_with_a4_minus_3>::value>::type
    print_curve_point(std::ostream &os, const curves::detail::curve_element<CurveParams, Form, Coordinates> &p) {
    os << "( X: [";
    print_field_element(os, p.X);
    os << "], Y: [";
    print_field_element(os, p.Y);
    os << "], Z:[";
    print_field_element(os, p.Z);
    os << "] )" << std::endl;
}

namespace boost {
    namespace test_tools {
        namespace tt_detail {
            template<typename CurveParams, typename Form, typename Coordinates>
            struct print_log_value<curves::detail::curve_element<CurveParams, Form, Coordinates>> {
                void operator()(std::ostream &os,
                                curves::detail::curve_element<CurveParams, Form, Coordinates> const &p) {
                    print_curve_point(os, p);
                }
            };

            template<template<typename, typename> class P, typename K, typename V>
            struct print_log_value<P<K, V>> {
                void operator()(std::ostream &, P<K, V> const &) {
                }
            };

            template<typename FieldParams>
            struct print_log_value<typename fields::detail::element_fp<FieldParams>> {
                void operator()(std::ostream &os, typename fields::detail::element_fp<FieldParams> const &e) {
                    print_field_element(os, e);
                }
            };
        }    // namespace tt_detail
    }        // namespace test_tools
}    // namespace boost

const char *test_data = "../../../../libs/algebra/test/data/coordinates.json";

boost::property_tree::ptree string_data(std::string test_name) {
    boost::property_tree::ptree string_data;
    boost::property_tree::read_json(test_data, string_data);

    return string_data.get_child(test_name);
}

enum curve_operation_test_constants : std::size_t { C1, C2 };

enum curve_operation_test_points : std::size_t {
    p1,
    p2,
    p1_plus_p2,
    p1_minus_p2,
    p1_mul_C1,
    p2_mul_C1_plus_p2_mul_C2,
    p1_dbl
};

template<typename CurveGroup>
void check_curve_operations(const std::vector<typename CurveGroup::value_type> &points,
                            const std::vector<std::size_t> &constants) {
    using nil::crypto3::multiprecision::cpp_int;

    BOOST_CHECK_EQUAL(points[p1] + points[p2], points[p1_plus_p2]);
    BOOST_CHECK_EQUAL(points[p1] - points[p2], points[p1_minus_p2]);
    BOOST_CHECK_EQUAL(points[p1].doubled(), points[p1_dbl]);
    BOOST_CHECK_EQUAL(points[p1] * static_cast<cpp_int>(constants[C1]), points[p1_mul_C1]);
    BOOST_CHECK_EQUAL((points[p2] * static_cast<cpp_int>(constants[C1])) +
                          (points[p2] * static_cast<cpp_int>(constants[C2])),
                      points[p2_mul_C1_plus_p2_mul_C2]);
    BOOST_CHECK_EQUAL((points[p2] * static_cast<cpp_int>(constants[C1])) +
                          (points[p2] * static_cast<cpp_int>(constants[C2])),
                      points[p2] * static_cast<cpp_int>(constants[C1] + constants[C2]));
}

template<typename FpCurveGroup, typename TestSet>
void fp_curve_test_init(std::vector<typename FpCurveGroup::value_type> &points,
                        std::vector<std::size_t> &constants,
                        const TestSet &test_set) {
    typedef typename FpCurveGroup::field_type::value_type field_value_type;
    std::array<field_value_type, 3> coordinates;

    for (auto &point : test_set.second.get_child("point_coordinates")) {
        auto i = 0;
        for (auto &coordinate : point.second) {
            coordinates[i++] = field_value_type(typename field_value_type::integral_type(coordinate.second.data()));
        }
        points.emplace_back(typename FpCurveGroup::value_type(coordinates[0], coordinates[1], coordinates[2]));
    }

    for (auto &constant : test_set.second.get_child("constants")) {
        constants.emplace_back(std::stoul(constant.second.data()));
    }
}

template<typename CurveGroup, typename TestSet>
void curve_operation_test(const TestSet &test_set,
                          void (&test_init)(std::vector<typename CurveGroup::value_type> &,
                                            std::vector<std::size_t> &,
                                            const TestSet &)) {

    std::vector<typename CurveGroup::value_type> points;
    std::vector<std::size_t> constants;

    test_init(points, constants, test_set);

    check_curve_operations<CurveGroup>(points, constants);
}

BOOST_AUTO_TEST_SUITE(curves_manual_tests)

BOOST_DATA_TEST_CASE(curve_operation_test_jacobian_minus_3, string_data("curve_operation_test_jacobian_minus_3"), data_set) {
    using policy_type = curves::secp_r1<256>::g1_type< curves::coordinates::jacobian_with_a4_minus_3,  curves::forms::short_weierstrass>;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_jacobian, string_data("curve_operation_test_jacobian"), data_set) {
    using policy_type = curves::secp_r1<256>::g1_type< curves::coordinates::jacobian,  curves::forms::short_weierstrass>;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_projective_with_a4_minus_3, string_data("curve_operation_test_projective_with_a4_minus_3"), data_set) {
    using policy_type = curves::secp_r1<256>::g1_type< curves::coordinates::projective_with_a4_minus_3,  curves::forms::short_weierstrass>;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}
BOOST_AUTO_TEST_SUITE_END()


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2004, 2007 StatPro Italia srl

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#include <ql/instruments/dividendvanillaoption.hpp>
#include <ql/instruments/impliedvolatility.hpp>
#include <ql/pricingengines/vanilla/analyticdividendeuropeanengine.hpp>
#include <ql/pricingengines/vanilla/fdblackscholesvanillaengine.hpp>
#include <ql/utilities/dataformatters.hpp>
#include <ql/cashflows/cashflowvectors.hpp>
#include <ql/exercise.hpp>
#include <boost/scoped_ptr.hpp>

namespace QuantLib {

    DividendVanillaOption::DividendVanillaOption(
                           const ext::shared_ptr<StrikedTypePayoff>& payoff,
                           const ext::shared_ptr<Exercise>& exercise,
                           const std::vector<Date>& dividendDates,
                           const std::vector<Real>& dividends)
    : OneAssetOption(payoff, exercise),
      cashFlow_(DividendVector(dividendDates, dividends)) {}

    Volatility DividendVanillaOption::impliedVolatility(
             Real targetValue,
             const ext::shared_ptr<GeneralizedBlackScholesProcess>& process,
             Real accuracy,
             Size maxEvaluations,
             Volatility minVol,
             Volatility maxVol) const {

        QL_REQUIRE(!isExpired(), "option expired");

        ext::shared_ptr<SimpleQuote> volQuote(new SimpleQuote);

        ext::shared_ptr<GeneralizedBlackScholesProcess> newProcess =
            detail::ImpliedVolatilityHelper::clone(process, volQuote);

        // engines are built-in for the time being
        boost::scoped_ptr<PricingEngine> engine;
        switch (exercise_->type()) {
          case Exercise::European:
            engine.reset(new AnalyticDividendEuropeanEngine(newProcess));
            break;
          case Exercise::American:
            engine.reset(new FdBlackScholesVanillaEngine(newProcess));
            break;
          case Exercise::Bermudan:
            QL_FAIL("engine not available for Bermudan option with dividends");
            break;
          default:
            QL_FAIL("unknown exercise type");
        }

        return detail::ImpliedVolatilityHelper::calculate(*this,
                                                          *engine,
                                                          *volQuote,
                                                          targetValue,
                                                          accuracy,
                                                          maxEvaluations,
                                                          minVol, maxVol);
    }

    void DividendVanillaOption::setupArguments(
                                       PricingEngine::arguments* args) const {
        OneAssetOption::setupArguments(args);

        DividendVanillaOption::arguments* arguments =
            dynamic_cast<DividendVanillaOption::arguments*>(args);
        QL_REQUIRE(arguments != 0, "wrong engine type");

        arguments->cashFlow = cashFlow_;
    }

    void DividendVanillaOption::arguments::validate() const {
        OneAssetOption::arguments::validate();

        Date exerciseDate = exercise->lastDate();

        for (Size i = 0; i < cashFlow.size(); i++) {
            QL_REQUIRE(cashFlow[i]->date() <= exerciseDate,
                       "the " << io::ordinal(i+1) << " dividend date ("
                       << cashFlow[i]->date()
                       << ") is later than the exercise date ("
                       << exerciseDate << ")");
        }
    }

}