// ---------------------------------------------------------
//
//  edgeflipper.cpp
//  Tyson Brochu 2011
//  Christopher Batty, Fang Da 2014
//
//  Functions supporting the "edge flip" operation: replacing non-delaunay edges with their dual edge.
//
// ---------------------------------------------------------

#include <edgeflipper.h>

#include <broadphase.h>
#include <collisionqueries.h>
#include <nondestructivetrimesh.h>
#include <runstats.h>
#include <surftrack.h>
#include <trianglequality.h>
#include <Eigen/Core>
#include <Eigen/Eigenvalues>

// ---------------------------------------------------------
//  Extern globals
// ---------------------------------------------------------

namespace LosTopos {

extern RunStats g_stats;

// ---------------------------------------------------------
// Member function definitions
// ---------------------------------------------------------

// --------------------------------------------------------
///
/// Check whether the new triangles created by flipping an edge introduce any intersection
///
// --------------------------------------------------------

bool EdgeFlipper::flip_introduces_collision( size_t edge_index,
                                            const Vec2st& new_edge,
                                            const Vec3st& new_triangle_a,
                                            const Vec3st& new_triangle_b )
{

    NonDestructiveTriMesh& m_mesh = m_surf.m_mesh;
    const std::vector<Vec3d>& xs = m_surf.get_positions();

    if ( !m_surf.m_collision_safety )
    {
        return false;
    }

    const Vec2st& old_edge = m_mesh.m_edges[edge_index];

    size_t tet_vertex_indices[4] = { old_edge[0], old_edge[1], new_edge[0], new_edge[1] };

    const Vec3d tet_vertex_positions[4] = { xs[ tet_vertex_indices[0] ],
        xs[ tet_vertex_indices[1] ],
        xs[ tet_vertex_indices[2] ],
        xs[ tet_vertex_indices[3] ] };

    Vec3d low, high;
    minmax( tet_vertex_positions[0], tet_vertex_positions[1], tet_vertex_positions[2], tet_vertex_positions[3], low, high );

    std::vector<size_t> overlapping_vertices;
    m_surf.m_broad_phase->get_potential_vertex_collisions( low, high, true, true, overlapping_vertices );

    // do point-in-tet tests
    for ( size_t i = 0; i < overlapping_vertices.size(); ++i )
    {
        if ( (overlapping_vertices[i] == old_edge[0]) || (overlapping_vertices[i] == old_edge[1]) ||
            (overlapping_vertices[i] == new_edge[0]) || (overlapping_vertices[i] == new_edge[1]) )
        {
            continue;
        }

        if ( point_tetrahedron_intersection( xs[overlapping_vertices[i]], overlapping_vertices[i],
                                            tet_vertex_positions[0], tet_vertex_indices[0],
                                            tet_vertex_positions[1], tet_vertex_indices[1],
                                            tet_vertex_positions[2], tet_vertex_indices[2],
                                            tet_vertex_positions[3], tet_vertex_indices[3] ) )
        {
            return true;
        }
    }

    //
    // Check new triangle A vs existing edges
    //

    minmax( xs[new_triangle_a[0]], xs[new_triangle_a[1]], xs[new_triangle_a[2]], low, high );
    static std::vector<size_t> overlapping_edges(10);
	overlapping_edges.clear();
    m_surf.m_broad_phase->get_potential_edge_collisions( low, high, true, true, overlapping_edges );

    for ( size_t i = 0; i < overlapping_edges.size(); ++i )
    {
        size_t overlapping_edge_index = overlapping_edges[i];
        const Vec2st& edge = m_mesh.m_edges[overlapping_edge_index];

        if ( check_edge_triangle_intersection_by_index( edge[0], edge[1],
                                                       new_triangle_a[0], new_triangle_a[1], new_triangle_a[2],
                                                       xs, m_surf.m_verbose ) )
        {
            return true;
        }
    }

    //
    // Check new triangle B vs existing edges
    //

    minmax( xs[new_triangle_b[0]], xs[new_triangle_b[1]], xs[new_triangle_b[2]], low, high );

    overlapping_edges.clear();
    m_surf.m_broad_phase->get_potential_edge_collisions( low, high, true, true, overlapping_edges );

    for ( size_t i = 0; i < overlapping_edges.size(); ++i )
    {
        size_t overlapping_edge_index = overlapping_edges[i];
        const Vec2st& edge = m_mesh.m_edges[overlapping_edge_index];

        if ( check_edge_triangle_intersection_by_index( edge[0], edge[1],
                                                       new_triangle_b[0], new_triangle_b[1], new_triangle_b[2],
                                                       xs, m_surf.m_verbose ) )
        {
            return true;
        }
    }

    //
    // Check new edge vs existing triangles
    //

    minmax( xs[new_edge[0]], xs[new_edge[1]], low, high );
    static std::vector<size_t> overlapping_triangles(10);
	overlapping_triangles.clear();
    m_surf.m_broad_phase->get_potential_triangle_collisions( low, high, true, true, overlapping_triangles );

    for ( size_t i = 0; i <  overlapping_triangles.size(); ++i )
    {
        const Vec3st& tri = m_mesh.get_triangle(overlapping_triangles[i]);

        if ( check_edge_triangle_intersection_by_index( new_edge[0], new_edge[1],
                                                       tri[0], tri[1], tri[2],
                                                       xs, m_surf.m_verbose ) )
        {
            return true;
        }
    }

    return false;

}

// --------------------------------------------------------
///
/// Flip an edge: remove the edge and its incident triangles, then add a new edge and two new triangles
///
// --------------------------------------------------------

bool EdgeFlipper::flip_edge( size_t edge,
                            size_t tri0,
                            size_t tri1,
                            size_t third_vertex_0,
                            size_t third_vertex_1 )
{

    g_stats.add_to_int( "EdgeFlipper:edge_flip_attempt", 1 );

    NonDestructiveTriMesh& m_mesh = m_surf.m_mesh;
    const std::vector<Vec3d>& xs = m_surf.get_positions();

    Vec2st& edge_vertices = m_mesh.m_edges[edge];

    // Find the vertices which will form the new edge
    Vec2st new_edge( third_vertex_0, third_vertex_1);

    // --------------

    // Control volume change
    double vol = std::fabs( signed_volume( xs[edge_vertices[0]],
                                          xs[edge_vertices[1]],
                                          xs[new_edge[0]],
                                          xs[new_edge[1]] ) );

    if ( vol > m_surf.m_max_volume_change )
    {
        g_stats.add_to_int( "EdgeFlipper:edge_flip_volume_change", 1 );
        if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: volume change = " << vol << std::endl; }
        return false;
    }

    // --------------

    // Prevent non-manifold surfaces if we're not allowing them
    if ( false == m_surf.m_allow_non_manifold )
    {
        for ( size_t i = 0; i < m_mesh.m_vertex_to_edge_map[ third_vertex_0 ].size(); ++i )
        {
            if ( ( m_mesh.m_edges[ m_mesh.m_vertex_to_edge_map[third_vertex_0][i] ][0] == third_vertex_1 ) ||
                ( m_mesh.m_edges[ m_mesh.m_vertex_to_edge_map[third_vertex_0][i] ][1] == third_vertex_1 ) )
            {
                // edge already exists
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: edge exists" << std::endl;             }

                g_stats.add_to_int( "EdgeFlipper:edge_flip_would_be_nonmanifold", 1 );

                return false;
            }
        }
    }

    // --------------

    // Don't flip edge on a degenerate tet
    if ( third_vertex_0 == third_vertex_1 )
    {
        if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: degenerate tet" << std::endl; }
        g_stats.add_to_int( "EdgeFlipper:edge_flip_on_degenerate_tet", 1 );
        return false;
    }

    // --------------

    // Create the new triangles
    // new edge winding order == winding order of old triangle0 == winding order of new triangle0
    // then we make the second new triangle match that, and set labels appropriately.

    size_t new_triangle_third_vertex_0, new_triangle_third_vertex_1;
    if ( m_mesh.oriented( m_mesh.m_edges[edge][0], m_mesh.m_edges[edge][1], m_mesh.get_triangle(tri0) ) )
    {
        new_triangle_third_vertex_0 = m_mesh.m_edges[edge][1];
        new_triangle_third_vertex_1 = m_mesh.m_edges[edge][0];
    }
    else
    {
        new_triangle_third_vertex_0 = m_mesh.m_edges[edge][0];
        new_triangle_third_vertex_1 = m_mesh.m_edges[edge][1];
    }

    //the new patch has orientations that match each other, for simplicity.
    Vec3st new_triangle0( new_edge[0], new_edge[1], new_triangle_third_vertex_0 );
    Vec3st new_triangle1( new_edge[1], new_edge[0], new_triangle_third_vertex_1 );

    if ( m_surf.m_verbose )
    {
        std::cout << "flip --- new triangle 0: " << new_triangle0 << std::endl;
        std::cout << "flip --- new triangle 1: " << new_triangle1 << std::endl;
    }

    // --------------

    // if both triangle normals agree before flipping, make sure they agree after flipping
    if(!m_surf.m_aggressive_mode) {
        if ( dot( m_surf.get_triangle_normal(tri0), m_surf.get_triangle_normal(tri1) ) > 0.0 &&
            m_mesh.oriented(m_mesh.m_edges[edge][0], m_mesh.m_edges[edge][1], m_mesh.get_triangle(tri0)) ==
            m_mesh.oriented(m_mesh.m_edges[edge][1], m_mesh.m_edges[edge][0], m_mesh.get_triangle(tri1)) )
        {
            if ( dot( m_surf.get_triangle_normal(new_triangle0), m_surf.get_triangle_normal(new_triangle1) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle0), m_surf.get_triangle_normal(tri0) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle1), m_surf.get_triangle_normal(tri1) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle0), m_surf.get_triangle_normal(tri1) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle1), m_surf.get_triangle_normal(tri0) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }
        } else if (dot(m_surf.get_triangle_normal(tri0), m_surf.get_triangle_normal(tri1)) < 0.0 &&
                   m_mesh.oriented(m_mesh.m_edges[edge][0], m_mesh.m_edges[edge][1], m_mesh.get_triangle(tri0)) !=
                   m_mesh.oriented(m_mesh.m_edges[edge][1], m_mesh.m_edges[edge][0], m_mesh.get_triangle(tri1)))
        {
            if ( dot( m_surf.get_triangle_normal(new_triangle0), m_surf.get_triangle_normal(new_triangle1) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle0), m_surf.get_triangle_normal(tri0) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle1), m_surf.get_triangle_normal(tri1) ) > 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle0), m_surf.get_triangle_normal(tri1) ) > 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }

            if ( dot( m_surf.get_triangle_normal(new_triangle1), m_surf.get_triangle_normal(tri0) ) < 0.0 )
            {
                if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: normal inversion" << std::endl; }
                g_stats.add_to_int( "EdgeFlipper:edge_flip_normal_inversion", 1 );
                return false;
            }
        }
    }

    // --------------

    // Prevent degenerate triangles
    Vec3st old_tri0 = m_mesh.get_triangle(tri0);
    Vec3st old_tri1 = m_mesh.get_triangle(tri1);
    Vec3d old_normal0 = cross(xs[old_tri0[1]] - xs[old_tri0[0]], xs[old_tri0[2]] - xs[old_tri0[0]]);
    Vec3d old_normal1 = cross(xs[old_tri1[1]] - xs[old_tri1[0]], xs[old_tri1[2]] - xs[old_tri1[0]]);
    double old_area0 = mag(old_normal0) / 2;
    double old_area1 = mag(old_normal1) / 2;
    normalize(old_normal0);
    normalize(old_normal1);
    Vec3d new_normal0 = cross(xs[new_triangle0[1]] - xs[new_triangle0[0]], xs[new_triangle0[2]] - xs[new_triangle0[0]]);
    Vec3d new_normal1 = cross(xs[new_triangle1[1]] - xs[new_triangle1[0]], xs[new_triangle1[2]] - xs[new_triangle1[0]]);
    double new_area0 = mag(new_normal0) / 2;
    double new_area1 = mag(new_normal1) / 2;
    normalize(new_normal0);
    normalize(new_normal1);

    if ( new_area0 < std::min(m_surf.m_min_triangle_area, std::min(old_area0, old_area0) * 0.5) )
    {
        if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: area0 too small" << std::endl;    }
        g_stats.add_to_int( "EdgeFlipper:edge_flip_new_area_too_small", 1 );
        return false;
    }

    if ( new_area1 < std::min(m_surf.m_min_triangle_area, std::min(old_area0, old_area0) * 0.5) )
    {
        if ( m_surf.m_verbose ) { std::cout << "edge flip rejected: area1 too small" << std::endl; }
        g_stats.add_to_int( "EdgeFlipper:edge_flip_new_area_too_small", 1 );
        return false;
    }

    // --------------

    // Control change in area

    if ( std::fabs( old_area0 + old_area1 - new_area0 - new_area1 ) > 0.1 * (old_area0 + old_area1) )
    {
        if ( m_surf.m_verbose ) {std::cout << "edge flip rejected: area change too great" << std::endl; }
        g_stats.add_to_int( "EdgeFlipper:edge_flip_area_change_too_large", 1 );
        return false;
    }

    // --------------

    // Don't flip if the quad is not planar enough
    if (m_surf.edge_is_feature(edge))
    {
        if ( m_surf.m_verbose ) {std::cout << "edge flip rejected: edge is a feature" << std::endl;  }
        g_stats.add_to_int( "EdgeFlipper:edge_flip_not_smooth", 1 );
        return false;
    }

    // Don't flip if it produces triangles with bad aspect ratio (regardless of area)
    double old_min_edge_0 = std::min(std::min(mag(xs[old_tri0[1]] - xs[old_tri0[0]]), mag(xs[old_tri0[2]] - xs[old_tri0[1]])), mag(xs[old_tri0[0]] - xs[old_tri0[2]]));
    double old_min_edge_1 = std::min(std::min(mag(xs[old_tri1[1]] - xs[old_tri1[0]]), mag(xs[old_tri1[2]] - xs[old_tri1[1]])), mag(xs[old_tri1[0]] - xs[old_tri1[2]]));
    double old_max_edge_0 = std::max(std::max(mag(xs[old_tri0[1]] - xs[old_tri0[0]]), mag(xs[old_tri0[2]] - xs[old_tri0[1]])), mag(xs[old_tri0[0]] - xs[old_tri0[2]]));
    double old_max_edge_1 = std::max(std::max(mag(xs[old_tri1[1]] - xs[old_tri1[0]]), mag(xs[old_tri1[2]] - xs[old_tri1[1]])), mag(xs[old_tri1[0]] - xs[old_tri1[2]]));
    double new_min_edge_0 = std::min(std::min(mag(xs[new_triangle0[1]] - xs[new_triangle0[0]]), mag(xs[new_triangle0[2]] - xs[new_triangle0[1]])), mag(xs[new_triangle0[0]] - xs[new_triangle0[2]]));
    double new_min_edge_1 = std::min(std::min(mag(xs[new_triangle1[1]] - xs[new_triangle1[0]]), mag(xs[new_triangle1[2]] - xs[new_triangle1[1]])), mag(xs[new_triangle1[0]] - xs[new_triangle1[2]]));
    double new_max_edge_0 = std::max(std::max(mag(xs[new_triangle0[1]] - xs[new_triangle0[0]]), mag(xs[new_triangle0[2]] - xs[new_triangle0[1]])), mag(xs[new_triangle0[0]] - xs[new_triangle0[2]]));
    double new_max_edge_1 = std::max(std::max(mag(xs[new_triangle1[1]] - xs[new_triangle1[0]]), mag(xs[new_triangle1[2]] - xs[new_triangle1[1]])), mag(xs[new_triangle1[0]] - xs[new_triangle1[2]]));
    double AR_THRESHOLD = 10.0;
    double arthreshold = AR_THRESHOLD;
    arthreshold = std::max(arthreshold, 1 / (old_area0 * 2 / (old_max_edge_0 * old_max_edge_0)));
    arthreshold = std::max(arthreshold, 1 / (old_area1 * 2 / (old_max_edge_1 * old_max_edge_1)));
    arthreshold = std::max(arthreshold, old_area0 * 2 / (old_min_edge_0 * old_min_edge_0));
    arthreshold = std::max(arthreshold, old_area1 * 2 / (old_min_edge_1 * old_min_edge_1));
    if ((new_area0 * 2 / (new_max_edge_0 * new_max_edge_0) < 1 / arthreshold || new_area0 * 2 / (new_min_edge_0 * new_min_edge_0) > arthreshold) ||
        (new_area1 * 2 / (new_max_edge_1 * new_max_edge_1) < 1 / arthreshold || new_area1 * 2 / (new_min_edge_1 * new_min_edge_1) > arthreshold))
    {
        if ( m_surf.m_verbose ) {std::cout << "edge flip rejected: flip will produce triangles with bad aspect ratio" << std::endl;  }
        g_stats.add_to_int( "EdgeFlipper:edge_flip_bad_triangle", 1 );
        return false;
    }

    // --------------

    // Don't introduce a large or small angle

    double min_angle = min_triangle_angle( xs[new_triangle0[0]], xs[new_triangle0[1]], xs[new_triangle0[2]] );
    min_angle = min( min_angle, min_triangle_angle( xs[new_triangle1[0]], xs[new_triangle1[1]], xs[new_triangle1[2]] ) );

    if ( rad2deg(min_angle) < m_surf.m_min_triangle_angle )
    {
        g_stats.add_to_int( "EdgeFlipper:edge_flip_bad_angle", 1 );
        return false;
    }

    double max_angle = max_triangle_angle( xs[new_triangle0[0]], xs[new_triangle0[1]], xs[new_triangle0[2]] );
    max_angle = max( max_angle, max_triangle_angle( xs[new_triangle1[0]], xs[new_triangle1[1]], xs[new_triangle1[2]] ) );

    if ( rad2deg(max_angle) > m_surf.m_max_triangle_angle )
    {
        g_stats.add_to_int( "EdgeFlipper:edge_flip_bad_angle", 1 );
        return false;
    }
	// --------------

   // Prevent intersection
	if (m_surf.m_collision_safety && flip_introduces_collision(edge, new_edge, new_triangle0, new_triangle1))
	{
		if (m_surf.m_verbose) { std::cout << "edge flip rejected: intersection" << std::endl; }

		g_stats.add_to_int("EdgeFlipper:edge_flip_collision", 1);
		return false;
	}

    // --------------

    // Okay, now do the actual operation

    // Start history log
    MeshUpdateEvent flip(MeshUpdateEvent::EDGE_FLIP);
    flip.m_v0 = edge_vertices[0];
    flip.m_v1 = edge_vertices[1];

    Vec2i old_label_0 = m_surf.m_mesh.get_triangle_label(tri0);
    Vec2i new_label_0 = old_label_0;

    // new_triangle1 has consistent triangle orientation as new_triangle0,
    // by construction so just set labels to match.
    Vec2i new_label_1;
    new_label_1 = old_label_0;

    assert(m_mesh.oriented(new_edge[0], new_edge[1], new_triangle0) !=
           m_mesh.oriented(new_edge[0], new_edge[1], new_triangle1));

    m_surf.remove_triangle( tri0 );
    m_surf.remove_triangle( tri1 );
    flip.m_deleted_tris.push_back(tri0);
    flip.m_deleted_tris.push_back(tri1);

    // the old label carries over to the new triangle
    size_t new_triangle_index_0 = m_surf.add_triangle( new_triangle0, new_label_0 );
    size_t new_triangle_index_1 = m_surf.add_triangle( new_triangle1, new_label_1 );
    flip.m_created_tris.push_back(new_triangle_index_0);
    flip.m_created_tris.push_back(new_triangle_index_1);
    flip.m_created_tri_data.push_back(new_triangle0);
    flip.m_created_tri_data.push_back(new_triangle1);

    flip.m_created_tri_labels.push_back(new_label_0);
    flip.m_created_tri_labels.push_back(new_label_1);

    ////////////////////////////////////////////////////////////

    if ( m_surf.m_collision_safety )
    {
        if ( m_surf.check_triangle_vs_all_triangles_for_intersection( new_triangle_index_0 ) )
        {
            std::cout << "missed an intersection.  New triangles: " << new_triangle0 << ", " << new_triangle1 << std::endl;
            std::cout << "old triangles: " << old_tri0 << ", " << old_tri1 << std::endl;
            assert(0);
        }

        if ( m_surf.check_triangle_vs_all_triangles_for_intersection( new_triangle_index_1 ) )
        {
            std::cout << "missed an intersection.  New triangles: " << new_triangle0 << ", " << new_triangle1 << std::endl;
            std::cout << "old triangles: " << old_tri0 << ", " << old_tri1 << std::endl;
            assert(0);
        }
    }

    m_surf.m_dirty_triangles.push_back( new_triangle_index_0 );
    m_surf.m_dirty_triangles.push_back( new_triangle_index_1 );

    if ( m_surf.m_verbose ) { std::cout << "edge flip: ok" << std::endl; }

    g_stats.add_to_int( "EdgeFlipper:edge_flip_success", 1 );

    m_surf.m_mesh_change_history.push_back(flip);

    if (m_surf.m_mesheventcallback)
        m_surf.m_mesheventcallback->flip(m_surf, edge);

    return true;

}

void EdgeFlipper::getQuadric(size_t vertex, Mat33d& A) {

    const NonDestructiveTriMesh& mesh = m_surf.m_mesh;
    const std::vector<size_t>& incident_triangles = mesh.m_vertex_to_triangle_map[vertex];

    std::vector< Vec3d > N;
    std::vector< double > W;

    for ( size_t i = 0; i < incident_triangles.size(); ++i )
    {
        size_t triangle_index = incident_triangles[i];
        N.push_back( m_surf.get_triangle_normal(triangle_index) );
        W.push_back( m_surf.get_triangle_area(triangle_index) );
    }

    zero(A);

    // Compute A = N^T W N
    // where N are normals and W are area weights.
    for ( size_t i = 0; i < N.size(); ++i )
    {
        A(0,0) += N[i][0] * W[i] * N[i][0];
        A(1,0) += N[i][1] * W[i] * N[i][0];
        A(2,0) += N[i][2] * W[i] * N[i][0];

        A(0,1) += N[i][0] * W[i] * N[i][1];
        A(1,1) += N[i][1] * W[i] * N[i][1];
        A(2,1) += N[i][2] * W[i] * N[i][1];

        A(0,2) += N[i][0] * W[i] * N[i][2];
        A(1,2) += N[i][1] * W[i] * N[i][2];
        A(2,2) += N[i][2] * W[i] * N[i][2];
    }
}

bool EdgeFlipper::is_Delaunay_anisotropic( size_t edge, size_t tri0, size_t tri1, size_t third_vertex_0, size_t third_vertex_1 )
{
    //std::cout << "Checking Delaunay criteria.\n";
    // per Jiao et al. 2010, "4.3 Anisotropic edge flipping"
    // section 4.3, Anisotropic edge flipping

    //compute the quadric tensors at the four vertices
    Mat33d mat0, mat1, mat2, mat3;
    getQuadric(m_surf.m_mesh.m_edges[edge][0], mat0);
    getQuadric(m_surf.m_mesh.m_edges[edge][1], mat1);
    getQuadric(third_vertex_0, mat2);
    getQuadric(third_vertex_1, mat3);

    //sum them to get a combined quadric tensor A for the patch
    Mat33d A = mat0 + mat1 + mat2 + mat3;

    Eigen::Matrix3d A_Eigen;
    A_Eigen << A(0, 0), A(0, 1), A(0, 2), A(1, 0), A(1, 1), A(1, 2), A(2, 0), A(2, 1), A(2, 2);

    Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es;
    es.compute(A_Eigen, Eigen::ComputeEigenvectors);
    if (es.info() != Eigen::Success) assert(0);
    auto eigenvalues_Eigen = es.eigenvalues();
    auto eigenvectors_Eigen = es.eigenvectors();

    double eig_max = std::max(eigenvalues_Eigen[0], std::max(eigenvalues_Eigen[1], eigenvalues_Eigen[2]));
    double eig_min = std::min(eigenvalues_Eigen[0], std::min(eigenvalues_Eigen[1], eigenvalues_Eigen[2]));
    double eig_mid = std::max(min(eigenvalues_Eigen[0], eigenvalues_Eigen[1]), min(max(eigenvalues_Eigen[0], eigenvalues_Eigen[1]), eigenvalues_Eigen[2]));

    //Note that this returns the eigenvalues in ascending order, as opposed to descending order described by Jiao et al.

    //compute the metric tensor M_Q, with Q = Identity
    //Mat22d M( eigenvalues[1] / eigenvalues[2], 0, 0, eigenvalues[0] / eigenvalues[2]);
    Mat22d M(eig_mid / eig_max, 0, 0, eig_min / eig_max);

    //clamp the eigenvalues for safety
    M(0,0) = clamp(M(0,0), 0.005, 0.07);
    M(1,1) = clamp(M(1,1), 0.005, 0.07);

    //convert the relevant vertices from Cartesian coords into the coordinate system of the local frame (defined by the eigenvectors)
    Mat33d conversion_matrix;
    conversion_matrix = A; //note that A is now holding the eigenvectors, not the original quadric tensor.

    conversion_matrix = inverse(conversion_matrix);
    Vec3d v0 = m_surf.get_position(m_surf.m_mesh.m_edges[edge][0]);
    Vec3d v1 = m_surf.get_position(m_surf.m_mesh.m_edges[edge][1]);
    Vec3d v2 = m_surf.get_position(third_vertex_0);
    Vec3d v3 = m_surf.get_position(third_vertex_1);

    Vec3d nv0 = conversion_matrix*v0;
    Vec3d nv1 = conversion_matrix*v1;
    Vec3d nv2 = conversion_matrix*v2;
    Vec3d nv3 = conversion_matrix*v3;

    //only bother looking at the 2D coordinates in the tangent plane
    Vec2d n0_2d(nv0[0], nv0[1]);
    Vec2d n1_2d(nv1[0], nv1[1]);
    Vec2d n2_2d(nv2[0], nv2[1]);
    Vec2d n3_2d(nv3[0], nv3[1]);

    ////warp the vertices into the normalized space to account for anisotropy, via M
    n0_2d = M * n0_2d;
    n1_2d = M * n1_2d;
    n2_2d = M * n2_2d;
    n3_2d = M * n3_2d;

    //check the Delaunay criterion (sum of opposite angles < 180) in the modified space
    Vec2d off0 = n0_2d - n2_2d, off1 = n1_2d - n2_2d;
    double angle0 = acos(dot(off0,off1) / mag(off0) / mag(off1));

    Vec2d off2 = n0_2d - n3_2d, off3 = n1_2d - n3_2d;
    double angle1 = acos(dot(off2, off3) / mag(off2) / mag(off3));

    return angle0 + angle1 < M_PI;

}

// --------------------------------------------------------
///
/// Flip all non-delaunay edges
///
// --------------------------------------------------------

bool EdgeFlipper::flip_pass( )
{

    if ( m_surf.m_verbose )
    {
        std::cout << "---------------------- EdgeFlipper: flipping ----------------------" << std::endl;
    }

    //   if ( m_surf.m_collision_safety )
    //   {
    //      m_surf.check_continuous_broad_phase_is_up_to_date();
    //   }

    m_surf.m_dirty_triangles.clear();

    bool flip_occurred_ever = false;          // A flip occurred in this function call
    bool flip_occurred = true;                // A flip occurred in the current loop iteration

    static unsigned int MAX_NUM_FLIP_PASSES = 5;
    unsigned int num_flip_passes = 0;

    NonDestructiveTriMesh& m_mesh = m_surf.m_mesh;
    const std::vector<Vec3d>& xs = m_surf.get_positions();

    //
    // Each "pass" is once over the entire set of edges (ignoring edges created during the current pass)
    //

    //this is a cache to store incident edge-counts at vertices, to save on looking up the neighborhood
    //it is indexed per vertex, and it stores the number of edges counts and a corresponding region pair
    //consistent with a call to "edge_count_bordering_region_pair" (which is a bit costly)
    std::vector<std::pair<int, Vec2i>> edge_counts_by_region_pair(m_surf.get_num_vertices(), std::make_pair(-1, Vec2i(-1,-1)));

	//a cache storing which vertices are nonmanifold, consistent with is_vertex_nonmanifold function call, that is a bit costly
	std::vector<bool> is_vertex_nonmanifold(m_surf.get_num_vertices(), false);
	for (int v = 0; v < m_surf.get_num_vertices(); ++v)
		is_vertex_nonmanifold[v] = m_mesh.is_vertex_nonmanifold(v);

	while ( flip_occurred && num_flip_passes++ < MAX_NUM_FLIP_PASSES )
    {
        if ( m_surf.m_verbose )
        {
            std::cout << "---------------------- Los Topos: flipping ";
            std::cout << "pass " << num_flip_passes << "/" << MAX_NUM_FLIP_PASSES;
            std::cout << "----------------------" << std::endl;
        }

        flip_occurred = false;

        size_t number_of_edges = m_mesh.m_edges.size();      // don't work on newly created edges

        for( size_t i = 0; i < number_of_edges; i++ )
        {
            if ( m_mesh.m_edges[i][0] == m_mesh.m_edges[i][1] )   { continue; }
            if ( m_mesh.m_edge_to_triangle_map[i].size() > 4 || m_mesh.m_edge_to_triangle_map[i].size() < 2 )   { continue; }

            if ( m_mesh.m_edge_to_triangle_map[i].size() == 3) continue; //don't try flipping Y-junction non-manifold edges.

            //if ( m_mesh.m_is_boundary_vertex[ m_mesh.m_edges[i][0] ] || m_mesh.m_is_boundary_vertex[ m_mesh.m_edges[i][1] ] )  { continue; }  // skip boundary vertices
            //NOTE: This check disables flipping on edges where either endpoint is on the boundary.
            //Future work: extend to the cases where meshes have open boundaries

            size_t triangle_a = (size_t)~0, triangle_b =(size_t)~0;

            if ( m_mesh.m_edge_to_triangle_map[i].size() == 2 )
            {
                triangle_a = m_mesh.m_edge_to_triangle_map[i][0];
                triangle_b = m_mesh.m_edge_to_triangle_map[i][1];
            }
            else if ( m_mesh.m_edge_to_triangle_map[i].size() == 4 )
            {
                // non manifold edge: disable flipping
               //TODO is this good or bad?
                continue;

                triangle_a = m_mesh.m_edge_to_triangle_map[i][0];

                // Find first triangle with orientation opposite triangle_a's orientation
                unsigned int j = 1;
                for ( ; j < 4; ++j )
                {
                    triangle_b = m_mesh.m_edge_to_triangle_map[i][j];
                    if (    m_mesh.oriented( m_mesh.m_edges[i][0], m_mesh.m_edges[i][1], m_mesh.get_triangle(triangle_a) )
                        != m_mesh.oriented( m_mesh.m_edges[i][0], m_mesh.m_edges[i][1], m_mesh.get_triangle(triangle_b) ) )
                    {
                        break;
                    }
                }
                assert ( j < 4 );
            }
            else
            {
                std::cout << m_mesh.m_edge_to_triangle_map[i].size() << " triangles incident to an edge" << std::endl;
                assert(0);
            }

            //don't flip if one of the faces is all solid
            if(m_surf.triangle_is_all_solid(triangle_a) || m_surf.triangle_is_all_solid(triangle_b)) {
                continue;
            }

            // Don't flip edge on a degenerate/deleted triangles
            if ( m_mesh.triangle_is_deleted(triangle_a) || m_mesh.triangle_is_deleted(triangle_b) )
                continue;

            const Vec3st& tri_a = m_mesh.get_triangle( triangle_a );
            const Vec3st& tri_b = m_mesh.get_triangle( triangle_b );

            size_t third_vertex_0 = m_mesh.get_third_vertex( m_mesh.m_edges[i][0], m_mesh.m_edges[i][1], tri_a );
            size_t third_vertex_1 = m_mesh.get_third_vertex( m_mesh.m_edges[i][0], m_mesh.m_edges[i][1], tri_b );

            if ( third_vertex_0 == third_vertex_1 ) {
                continue;
            }

            size_t vert_0 = m_mesh.m_edges[i][0];
            size_t vert_1 = m_mesh.m_edges[i][1];

            bool flipped = false;

            bool flip_required = false;

            if(m_use_Delaunay_criterion) {

                //compute the angles that oppose the edge
                Vec3d pos_3rd_0 = m_surf.get_position(third_vertex_0);
                Vec3d pos_3rd_1 = m_surf.get_position(third_vertex_1);
                Vec3d pos_vert_0 = m_surf.get_position(vert_0);
                Vec3d pos_vert_1 = m_surf.get_position(vert_1);

                Vec3d off0 = pos_vert_0 - pos_3rd_0;
                Vec3d off1 = pos_vert_1 - pos_3rd_0;
                double m0 = mag(off0), m1 = mag(off1);
                if(m0 == 0 || m1 == 0) continue;
                double angle0 = acos( dot(off0,off1) / (m0*m1) );

                Vec3d off2 = pos_vert_0 - pos_3rd_1;
                Vec3d off3 = pos_vert_1 - pos_3rd_1;
                double m2 = mag(off2), m3 = mag(off3);
                if(m2 == 0 || m3 == 0) continue;
                double angle1 = acos( dot(off2, off3) / (m2*m3) );

                if(m_surf.m_aggressive_mode) {

                    Vec2d angles_cos0, angles_cos1;
                    min_and_max_triangle_angle_cosines(pos_vert_0, pos_vert_1, pos_3rd_0, angles_cos0);
                    min_and_max_triangle_angle_cosines(pos_vert_0, pos_vert_1, pos_3rd_1, angles_cos1);
                    double min_cos, max_cos;
                    min_cos = min(angles_cos0[0], angles_cos1[0]);
                    max_cos = max(angles_cos0[1], angles_cos1[1]);

                    //skip processing any triangles that don't have fairly bad angles.
                    bool angles_are_fine_cos = min_cos > m_surf.m_min_angle_cosine || max_cos < m_surf.m_max_angle_cosine;

                    if (angles_are_fine_cos)
                        continue;

                }

                //if the sum of the opposing angles exceeds 180, then we should flip (according to the Delaunay criterion)
                //Delaunay apparently maximizes the minimum angle in the triangulation
                flip_required = angle0 + angle1 > M_PI;
            }
            else {
                //Flip based on valences instead.
                //per e.g. "A Remeshing Approach to Multiresolution Modeling"

                //Here we treat non-manifold vertices as being on boundaries, and boundaries as boundaries.
                //so their optimal valence is 4 instead of 6.
                //See e.g. https://code.google.com/p/stacker/source/browse/trunk/GraphicsLibrary/Remeshing/LaplacianRemesher.h

				//this is the direct computation, but we think it's slow.
                /*int opt_val_a = m_mesh.is_vertex_nonmanifold(third_vertex_0)?4:(m_mesh.m_is_boundary_vertex[third_vertex_0]?4:6),
                opt_val_b = m_mesh.is_vertex_nonmanifold(third_vertex_1)?4:(m_mesh.m_is_boundary_vertex[third_vertex_1]?4:6),
                opt_val_0 = m_mesh.is_vertex_nonmanifold(vert_0)?4:(m_mesh.m_is_boundary_vertex[vert_0]?4:6),
                opt_val_1 = m_mesh.is_vertex_nonmanifold(vert_1)?4:(m_mesh.m_is_boundary_vertex[vert_1]?4:6);*/

				//this is using the cache for speed.
				int opt_val_a = is_vertex_nonmanifold[third_vertex_0] ? 4 : (m_mesh.m_is_boundary_vertex[third_vertex_0] ? 4 : 6),
					opt_val_b = is_vertex_nonmanifold[third_vertex_1] ? 4 : (m_mesh.m_is_boundary_vertex[third_vertex_1] ? 4 : 6),
					opt_val_0 = is_vertex_nonmanifold[vert_0] ? 4 : (m_mesh.m_is_boundary_vertex[vert_0] ? 4 : 6),
					opt_val_1 = is_vertex_nonmanifold[vert_1] ? 4 : (m_mesh.m_is_boundary_vertex[vert_1] ? 4 : 6);
				/*assert(opt_val_a == opt_val_b_new);
				assert(opt_val_b == opt_val_b_new);
				assert(opt_val_0 == opt_val_0_new);
				assert(opt_val_1 == opt_val_1_new);*/

                int val_a, val_b, val_0, val_1;
                Vec2i region_pair = m_mesh.get_triangle_label(triangle_a); //doesn't matter which triangle we consider.

                //do the computation for the edge counts (if necessary), and store the result in our simple cache for speed!
                //There's otherwise a fair amount of redundance in smooth single-region patches
                val_0 = edge_counts_by_region_pair[vert_0].second         == region_pair ? edge_counts_by_region_pair[vert_0].first   : edge_count_bordering_region_pair(vert_0, region_pair);
                edge_counts_by_region_pair[vert_0].first = val_0; edge_counts_by_region_pair[vert_0].second = region_pair;

                val_1 = edge_counts_by_region_pair[vert_1].second         == region_pair ? edge_counts_by_region_pair[vert_1].first   : edge_count_bordering_region_pair(vert_1, region_pair);
                edge_counts_by_region_pair[vert_1].first = val_1; edge_counts_by_region_pair[vert_1].second = region_pair;

                val_a = edge_counts_by_region_pair[third_vertex_0].second == region_pair ? edge_counts_by_region_pair[third_vertex_0].first : edge_count_bordering_region_pair(third_vertex_0, region_pair);
                edge_counts_by_region_pair[third_vertex_0].first = val_a; edge_counts_by_region_pair[third_vertex_0].second = region_pair;

                val_b = edge_counts_by_region_pair[third_vertex_1].second == region_pair ? edge_counts_by_region_pair[third_vertex_1].first : edge_count_bordering_region_pair(third_vertex_1, region_pair);
                edge_counts_by_region_pair[third_vertex_1].first = val_b; edge_counts_by_region_pair[third_vertex_1].second = region_pair;

                /*assert(val_0 == edge_count_bordering_region_pair(vert_0, region_pair));
                assert(val_1 == edge_count_bordering_region_pair(vert_1, region_pair));
                assert(val_a == edge_count_bordering_region_pair(third_vertex_0, region_pair));
                assert(val_b == edge_count_bordering_region_pair(third_vertex_1, region_pair));*/

               /*
               //(The below is what the above 8 lines are doing, but accelerated a bit by a cache.)
                val_0 = edge_count_bordering_region_pair(vert_0, region_pair);
                val_1 = edge_count_bordering_region_pair(vert_1, region_pair);
                val_a = edge_count_bordering_region_pair(third_vertex_0, region_pair);
                val_b = edge_count_bordering_region_pair(third_vertex_1, region_pair);*/

                int score_before = sqr(val_a-opt_val_a) + sqr(val_b-opt_val_b)  + sqr(val_0-opt_val_0) + sqr(val_1-opt_val_1);

                //now work out the valences after
                val_a++; val_b++;
                val_0--; val_1--;

                int score_after = sqr(val_a-opt_val_a) + sqr(val_b-opt_val_b)  + sqr(val_0-opt_val_0) + sqr(val_1-opt_val_1);

                flip_required = score_before > score_after;

                double current_length = mag( xs[m_mesh.m_edges[i][1]] - xs[m_mesh.m_edges[i][0]] );
                double potential_length = mag( xs[third_vertex_1] - xs[third_vertex_0] );
            }

            if ( flip_required )
            {
                flipped = flip_edge( i, triangle_a, triangle_b, third_vertex_0, third_vertex_1 );
            }

            if (flipped && !m_use_Delaunay_criterion) {
               //a flip happened, so update our caches
               Vec2i region_pair = m_mesh.get_triangle_label(triangle_a);
               int val_0 = edge_count_bordering_region_pair(vert_0, region_pair);
               int val_1 = edge_count_bordering_region_pair(vert_1, region_pair);
               int val_a = edge_count_bordering_region_pair(third_vertex_0, region_pair);
               int val_b = edge_count_bordering_region_pair(third_vertex_1, region_pair);

               edge_counts_by_region_pair[vert_0].first = val_0; edge_counts_by_region_pair[vert_0].second = region_pair;
               edge_counts_by_region_pair[vert_1].first = val_1; edge_counts_by_region_pair[vert_1].second = region_pair;
               edge_counts_by_region_pair[third_vertex_0].first = val_a; edge_counts_by_region_pair[third_vertex_0].second = region_pair;
               edge_counts_by_region_pair[third_vertex_1].first = val_b; edge_counts_by_region_pair[third_vertex_1].second = region_pair;

			   is_vertex_nonmanifold[third_vertex_0] = m_mesh.is_vertex_nonmanifold(third_vertex_0);
			   is_vertex_nonmanifold[third_vertex_1] = m_mesh.is_vertex_nonmanifold(third_vertex_1);
			   is_vertex_nonmanifold[vert_0] = m_mesh.is_vertex_nonmanifold(vert_0);
			   is_vertex_nonmanifold[vert_1] = m_mesh.is_vertex_nonmanifold(vert_1);
            }

            flip_occurred |= flipped;
        }

        flip_occurred_ever |= flip_occurred;
    }

    if ( flip_occurred_ever )
    {
        m_surf.trim_degeneracies( m_surf.m_dirty_triangles );
    }

    return flip_occurred_ever;

}

int EdgeFlipper::edge_count_bordering_region_pair(size_t vertex, Vec2i region_pair) {
    int count = 0;

    //TODO Can this be sped up by "walking the loop" from the central tri
    //around the vertex 'til we get back again? Or is this current approach faster?

    Vec2i flipped_pair(region_pair[1], region_pair[0]);

    //consider all incident edges
    for(size_t i = 0; i < m_surf.m_mesh.m_vertex_to_edge_map[vertex].size(); ++i) {
        size_t edge = m_surf.m_mesh.m_vertex_to_edge_map[vertex][i];

        //consider all triangles on the edge
        for(size_t j = 0; j < m_surf.m_mesh.m_edge_to_triangle_map[edge].size(); ++j) {
            size_t tri = m_surf.m_mesh.m_edge_to_triangle_map[edge][j];
            const Vec2i& labels = m_surf.m_mesh.m_triangle_labels[tri];
            //if one of the triangles matches the requested manifold region (label pair), we're done.
            if(labels == region_pair || labels == flipped_pair) {
                count++;
                break;
            }
        }

    }
    return count;
}

}


/*
    Copyright 2005-2007 Adobe Systems Incorporated

    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).

    See http://opensource.adobe.com/gil for most recent version including documentation.
*/

/*************************************************************************************************/

/// \file
/// \brief Example file to demonstrate a way to compute histogram
/// \author Lubomir Bourdev and Hailin Jin
/// \date February 27, 2007

#include <algorithm>
#include <fstream>
#include <boost/gil/image.hpp>
#include <boost/gil/typedefs.hpp>
#include <boost/gil/color_convert.hpp>
#include <boost/gil/extension/io/jpeg_io.hpp>

using namespace boost::gil;

template <typename GrayView, typename R>
void gray_image_hist(const GrayView& img_view, R& hist) {
//    for_each_pixel(img_view,++lambda::var(hist)[lambda::_1]);
    for (typename GrayView::iterator it=img_view.begin(); it!=img_view.end(); ++it)
        ++hist[*it];
}

template <typename V, typename R>
void get_hist(const V& img_view, R& hist) {
    gray_image_hist(color_converted_view<gray8_pixel_t>(img_view), hist);
}

int main() {
    rgb8_image_t img;
    jpeg_read_image("test.jpg",img);

    int histogram[256];
    std::fill(histogram,histogram+256,0);
    get_hist(const_view(img),histogram);

    std::fstream histo_file("out-histogram.txt",std::ios::out);
    for(std::size_t ii=0;ii<256;++ii)
        histo_file << histogram[ii] << std::endl;
    histo_file.close();

    return 0;
}


/*
 Copyright (C) 2018 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/test/unit_test.hpp>
#include <oret/toplevelfixture.hpp>

#include <boost/algorithm/string/replace.hpp>
#include <boost/make_shared.hpp>
#include <ored/configuration/conventions.hpp>
#include <ql/currencies/all.hpp>
#include <ql/time/calendars/all.hpp>
#include <ql/time/daycounters/all.hpp>
#include <qle/calendars/ice.hpp>

using namespace std;
using namespace boost::unit_test_framework;
using namespace QuantExt;
using namespace QuantLib;
using namespace ore::data;
using boost::algorithm::replace_all_copy;

namespace {

void testIborIndexConvention(const string& id, const string& fixingCalendar, const string& dayCounter,
    const Size settlementDays, const string& businessDayConvention, const bool endOfMonth,
    const string& internalId = "") {
    // Check construction raises no errors
    boost::shared_ptr<IborIndexConvention> convention;
    BOOST_CHECK_NO_THROW(
        convention = boost::make_shared<IborIndexConvention>(id, fixingCalendar, dayCounter,
            settlementDays, businessDayConvention, endOfMonth));

    // Check object
    BOOST_CHECK_EQUAL(convention->id(), internalId.empty() ? id : internalId);
    BOOST_CHECK_EQUAL(convention->fixingCalendar(), fixingCalendar);
    BOOST_CHECK_EQUAL(convention->dayCounter(), dayCounter);
    BOOST_CHECK_EQUAL(convention->settlementDays(), settlementDays);
    BOOST_CHECK_EQUAL(convention->businessDayConvention(), businessDayConvention);
    BOOST_CHECK_EQUAL(convention->endOfMonth(), endOfMonth);
}

} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(ConventionsTests)

BOOST_AUTO_TEST_CASE(testCrossCcyFixFloatSwapConventionConstruction) {

    BOOST_TEST_MESSAGE("Testing cross currency fix float convention construction");

    // Check construction raises no errors
    boost::shared_ptr<CrossCcyFixFloatSwapConvention> convention;
    BOOST_CHECK_NO_THROW(
        convention = boost::make_shared<CrossCcyFixFloatSwapConvention>("USD-TRY-XCCY-FIX-FLOAT", "2", "US,UK,TRY", "F",
                                                                        "TRY", "Annual", "F", "A360", "USD-LIBOR-3M"));

    // Check object
    BOOST_CHECK_EQUAL(convention->id(), "USD-TRY-XCCY-FIX-FLOAT");
    BOOST_CHECK_EQUAL(convention->settlementDays(), 2);
    BOOST_CHECK_EQUAL(convention->settlementCalendar(), JointCalendar(UnitedStates(), UnitedKingdom(), Turkey()));
    BOOST_CHECK_EQUAL(convention->settlementConvention(), Following);
    BOOST_CHECK_EQUAL(convention->fixedCurrency(), TRYCurrency());
    BOOST_CHECK_EQUAL(convention->fixedFrequency(), Annual);
    BOOST_CHECK_EQUAL(convention->fixedConvention(), Following);
    BOOST_CHECK_EQUAL(convention->fixedDayCounter(), Actual360());
    BOOST_CHECK_EQUAL(convention->index()->name(), "USDLibor3M Actual/360");
    BOOST_CHECK(!convention->eom());

    // Check end of month when not default
    BOOST_CHECK_NO_THROW(
        convention = boost::make_shared<CrossCcyFixFloatSwapConvention>(
            "USD-TRY-XCCY-FIX-FLOAT", "2", "US,UK,TRY", "F", "TRY", "Annual", "F", "A360", "USD-LIBOR-3M", "false"));
    BOOST_CHECK(!convention->eom());

    BOOST_CHECK_NO_THROW(
        convention = boost::make_shared<CrossCcyFixFloatSwapConvention>(
            "USD-TRY-XCCY-FIX-FLOAT", "2", "US,UK,TRY", "F", "TRY", "Annual", "F", "A360", "USD-LIBOR-3M", "true"));
    BOOST_CHECK(convention->eom());
}

BOOST_AUTO_TEST_CASE(testCrossCcyFixFloatSwapConventionFromXml) {

    BOOST_TEST_MESSAGE("Testing parsing of cross currency fix float convention from XML");

    // XML string convention
    string xml;
    xml.append("<CrossCurrencyFixFloat>");
    xml.append("  <Id>USD-TRY-XCCY-FIX-FLOAT</Id>");
    xml.append("  <SettlementDays>2</SettlementDays>");
    xml.append("  <SettlementCalendar>US,UK,TRY</SettlementCalendar>");
    xml.append("  <SettlementConvention>F</SettlementConvention>");
    xml.append("  <FixedCurrency>TRY</FixedCurrency>");
    xml.append("  <FixedFrequency>Annual</FixedFrequency>");
    xml.append("  <FixedConvention>F</FixedConvention>");
    xml.append("  <FixedDayCounter>A360</FixedDayCounter>");
    xml.append("  <Index>USD-LIBOR-3M</Index>");
    xml.append("</CrossCurrencyFixFloat>");

    // Parse convention from XML
    boost::shared_ptr<CrossCcyFixFloatSwapConvention> convention = boost::make_shared<CrossCcyFixFloatSwapConvention>();
    BOOST_CHECK_NO_THROW(convention->fromXMLString(xml));

    // Check parsed object
    BOOST_CHECK_EQUAL(convention->id(), "USD-TRY-XCCY-FIX-FLOAT");
    BOOST_CHECK_EQUAL(convention->settlementDays(), 2);
    BOOST_CHECK_EQUAL(convention->settlementCalendar(), JointCalendar(UnitedStates(), UnitedKingdom(), Turkey()));
    BOOST_CHECK_EQUAL(convention->settlementConvention(), Following);
    BOOST_CHECK_EQUAL(convention->fixedCurrency(), TRYCurrency());
    BOOST_CHECK_EQUAL(convention->fixedFrequency(), Annual);
    BOOST_CHECK_EQUAL(convention->fixedConvention(), Following);
    BOOST_CHECK_EQUAL(convention->fixedDayCounter(), Actual360());
    BOOST_CHECK_EQUAL(convention->index()->name(), "USDLibor3M Actual/360");
    BOOST_CHECK(!convention->eom());

    // Check end of month also
    xml = replace_all_copy(xml, "</CrossCurrencyFixFloat>", "<EOM>false</EOM></CrossCurrencyFixFloat>");
    BOOST_TEST_MESSAGE("xml is: " << xml);
    convention->fromXMLString(xml);
    BOOST_CHECK(!convention->eom());

    xml = replace_all_copy(xml, "<EOM>false</EOM>", "<EOM>true</EOM>");
    convention->fromXMLString(xml);
    BOOST_CHECK(convention->eom());
}

BOOST_AUTO_TEST_CASE(testCrossCcyFixFloatSwapConventionToXml) {

    BOOST_TEST_MESSAGE("Testing writing of cross currency fix float convention to XML");

    // Construct the convention
    boost::shared_ptr<CrossCcyFixFloatSwapConvention> convention;
    BOOST_CHECK_NO_THROW(
        convention = boost::make_shared<CrossCcyFixFloatSwapConvention>("USD-TRY-XCCY-FIX-FLOAT", "2", "US,UK,TRY", "F",
                                                                        "TRY", "Annual", "F", "A360", "USD-LIBOR-3M"));

    // Write the convention to a string
    string xml = convention->toXMLString();

    // Read the convention back from the string using fromXMLString
    boost::shared_ptr<CrossCcyFixFloatSwapConvention> readConvention =
        boost::make_shared<CrossCcyFixFloatSwapConvention>();
    BOOST_CHECK_NO_THROW(readConvention->fromXMLString(xml));

    // The read convention should equal the original convention
    BOOST_CHECK_EQUAL(convention->id(), readConvention->id());
    BOOST_CHECK_EQUAL(convention->settlementDays(), readConvention->settlementDays());
    BOOST_CHECK_EQUAL(convention->settlementCalendar(), readConvention->settlementCalendar());
    BOOST_CHECK_EQUAL(convention->settlementConvention(), readConvention->settlementConvention());
    BOOST_CHECK_EQUAL(convention->fixedCurrency(), readConvention->fixedCurrency());
    BOOST_CHECK_EQUAL(convention->fixedFrequency(), readConvention->fixedFrequency());
    BOOST_CHECK_EQUAL(convention->fixedConvention(), readConvention->fixedConvention());
    BOOST_CHECK_EQUAL(convention->fixedDayCounter(), readConvention->fixedDayCounter());
    BOOST_CHECK_EQUAL(convention->index()->name(), readConvention->index()->name());
    BOOST_CHECK_EQUAL(convention->eom(), readConvention->eom());
}

BOOST_AUTO_TEST_CASE(testDayOfMonthCommodityFutureConventionConstruction) {

    BOOST_TEST_MESSAGE("Testing commodity future convention construction with day of month based anchor day");

    // Check construction raises no errors
    using PE = CommodityFutureConvention::ProhibitedExpiry;
    set<PE> prohibitedExpiries;
    prohibitedExpiries.insert(PE(Date(31, Dec, 2020)));
    prohibitedExpiries.insert(PE(Date(31, Dec, 2021)));
    prohibitedExpiries.insert(PE(Date(30, Dec, 2022)));

    boost::shared_ptr<CommodityFutureConvention> convention;
    CommodityFutureConvention::DayOfMonth dayOfMonth("31");
    BOOST_CHECK_NO_THROW(convention = boost::make_shared<CommodityFutureConvention>(
                             "ICE:B", dayOfMonth, "Monthly", "ICE_FuturesEU", "UK", 2, "Jan", "0", "Preceding", true,
                             false, "3", prohibitedExpiries));

    // Check object
    BOOST_CHECK_EQUAL(convention->id(), "ICE:B");
    BOOST_CHECK(convention->anchorType() == CommodityFutureConvention::AnchorType::DayOfMonth);
    BOOST_CHECK_EQUAL(convention->dayOfMonth(), 31);
    BOOST_CHECK_EQUAL(convention->contractFrequency(), Monthly);
    BOOST_CHECK_EQUAL(convention->calendar(), ICE(ICE::FuturesEU));
    BOOST_CHECK_EQUAL(convention->expiryCalendar(), UnitedKingdom());
    BOOST_CHECK_EQUAL(convention->expiryMonthLag(), 2);
    BOOST_CHECK_EQUAL(convention->oneContractMonth(), Jan);
    BOOST_CHECK_EQUAL(convention->offsetDays(), 0);
    BOOST_CHECK_EQUAL(convention->businessDayConvention(), Preceding);
    BOOST_CHECK(convention->adjustBeforeOffset());
    BOOST_CHECK(!convention->isAveraging());
    BOOST_CHECK_EQUAL(convention->optionExpiryOffset(), 3);

    set<Date> expExpiries{Date(31, Dec, 2020), Date(31, Dec, 2021), Date(30, Dec, 2022)};
    const auto& proExps = convention->prohibitedExpiries();
    BOOST_CHECK_EQUAL(proExps.size(), expExpiries.size());
    for (const Date& expExpiry : expExpiries) {
        auto it = proExps.find(PE(expExpiry));
        BOOST_REQUIRE_MESSAGE(it != proExps.end(), "Expected date " << io::iso_date(expExpiry) <<
            " not found in Prohibited Expiries");
        BOOST_CHECK_EQUAL(it->expiry(), expExpiry);
        BOOST_CHECK(it->forFuture());
        BOOST_CHECK_EQUAL(it->futureBdc(), Preceding);
        BOOST_CHECK(it->forOption());
        BOOST_CHECK_EQUAL(it->optionBdc(), Preceding);
    }
}

BOOST_AUTO_TEST_CASE(testDayOfMonthCommodityFutureConventionFromXml) {

    BOOST_TEST_MESSAGE("Testing parsing of commodity future convention with day of month based anchor day from XML");

    // XML string convention
    string xml;
    xml.append("<CommodityFuture>");
    xml.append("  <Id>ICE:B</Id>");
    xml.append("  <AnchorDay>");
    xml.append("    <DayOfMonth>31</DayOfMonth>");
    xml.append("  </AnchorDay>");
    xml.append("  <ContractFrequency>Monthly</ContractFrequency>");
    xml.append("  <Calendar>ICE_FuturesEU</Calendar>");
    xml.append("  <ExpiryCalendar>UK</ExpiryCalendar>");
    xml.append("  <ExpiryMonthLag>2</ExpiryMonthLag>");
    xml.append("  <IsAveraging>false</IsAveraging>");
    xml.append("  <OptionExpiryOffset>3</OptionExpiryOffset>");
    xml.append("  <ProhibitedExpiries>");
    xml.append("    <Dates>");
    xml.append("      <Date>2020-12-31</Date>");
    xml.append("      <Date>2021-12-31</Date>");
    xml.append("      <Date>2022-12-30</Date>");
    xml.append("    </Dates>");
    xml.append("  </ProhibitedExpiries>");
    xml.append("</CommodityFuture>");

    // Parse convention from XML
    boost::shared_ptr<CommodityFutureConvention> convention = boost::make_shared<CommodityFutureConvention>();
    BOOST_CHECK_NO_THROW(convention->fromXMLString(xml));

    // Check parsed object
    BOOST_CHECK_EQUAL(convention->id(), "ICE:B");
    BOOST_CHECK(convention->anchorType() == CommodityFutureConvention::AnchorType::DayOfMonth);
    BOOST_CHECK_EQUAL(convention->dayOfMonth(), 31);
    BOOST_CHECK_EQUAL(convention->contractFrequency(), Monthly);
    BOOST_CHECK_EQUAL(convention->calendar(), ICE(ICE::FuturesEU));
    BOOST_CHECK_EQUAL(convention->expiryCalendar(), UnitedKingdom());
    BOOST_CHECK_EQUAL(convention->expiryMonthLag(), 2);
    BOOST_CHECK_EQUAL(convention->oneContractMonth(), Jan);
    BOOST_CHECK_EQUAL(convention->offsetDays(), 0);
    BOOST_CHECK_EQUAL(convention->businessDayConvention(), Preceding);
    BOOST_CHECK(convention->adjustBeforeOffset());
    BOOST_CHECK(!convention->isAveraging());
    BOOST_CHECK_EQUAL(convention->optionExpiryOffset(), 3);

    using PE = CommodityFutureConvention::ProhibitedExpiry;
    set<Date> expExpiries{ Date(31, Dec, 2020), Date(31, Dec, 2021), Date(30, Dec, 2022) };
    const auto& proExps = convention->prohibitedExpiries();
    BOOST_CHECK_EQUAL(proExps.size(), expExpiries.size());
    for (const Date& expExpiry : expExpiries) {
        auto it = proExps.find(PE(expExpiry));
        BOOST_REQUIRE_MESSAGE(it != proExps.end(), "Expected date " << io::iso_date(expExpiry) <<
            " not found in Prohibited Expiries");
        BOOST_CHECK_EQUAL(it->expiry(), expExpiry);
        BOOST_CHECK(it->forFuture());
        BOOST_CHECK_EQUAL(it->futureBdc(), Preceding);
        BOOST_CHECK(it->forOption());
        BOOST_CHECK_EQUAL(it->optionBdc(), Preceding);
    }
}

BOOST_AUTO_TEST_CASE(testDayOfMonthCommodityFutureConventionToXml) {

    BOOST_TEST_MESSAGE("Testing writing of commodity future convention with day of month based anchor day to XML");

    // Construct the convention
    using PE = CommodityFutureConvention::ProhibitedExpiry;
    set<PE> prohibitedExpiries;
    prohibitedExpiries.insert(PE(Date(31, Dec, 2020), true, Following, false, ModifiedFollowing));
    prohibitedExpiries.insert(PE(Date(31, Dec, 2021), false, Preceding, true, ModifiedPreceding));
    prohibitedExpiries.insert(PE(Date(31, Dec, 2021), false, Following));

    boost::shared_ptr<CommodityFutureConvention> convention;
    CommodityFutureConvention::DayOfMonth dayOfMonth("31");
    BOOST_CHECK_NO_THROW(convention = boost::make_shared<CommodityFutureConvention>(
                             "ICE:B", dayOfMonth, "Monthly", "ICE_FuturesEU", "UK", 2, "Jan", "0", "Preceding", true,
                             false, "3", prohibitedExpiries));

    // Write the convention to a string
    string xml = convention->toXMLString();

    // Read the convention back from the string using fromXMLString
    boost::shared_ptr<CommodityFutureConvention> readConvention = boost::make_shared<CommodityFutureConvention>();
    BOOST_CHECK_NO_THROW(readConvention->fromXMLString(xml));

    // The read convention should equal the original convention
    BOOST_CHECK_EQUAL(convention->id(), readConvention->id());
    BOOST_CHECK(convention->anchorType() == readConvention->anchorType());
    BOOST_CHECK_EQUAL(convention->dayOfMonth(), readConvention->dayOfMonth());
    BOOST_CHECK_EQUAL(convention->contractFrequency(), readConvention->contractFrequency());
    BOOST_CHECK_EQUAL(convention->calendar(), readConvention->calendar());
    BOOST_CHECK_EQUAL(convention->expiryMonthLag(), readConvention->expiryMonthLag());
    BOOST_CHECK_EQUAL(convention->oneContractMonth(), readConvention->oneContractMonth());
    BOOST_CHECK_EQUAL(convention->offsetDays(), readConvention->offsetDays());
    BOOST_CHECK_EQUAL(convention->businessDayConvention(), readConvention->businessDayConvention());
    BOOST_CHECK_EQUAL(convention->adjustBeforeOffset(), readConvention->adjustBeforeOffset());
    BOOST_CHECK_EQUAL(convention->isAveraging(), readConvention->isAveraging());

    const auto& proExps = convention->prohibitedExpiries();
    const auto& readProExps = readConvention->prohibitedExpiries();
    BOOST_CHECK_EQUAL(proExps.size(), readProExps.size());
    for (const auto& pe : proExps) {
        auto it = readProExps.find(pe);
        BOOST_REQUIRE_MESSAGE(it != readProExps.end(), "Expected date " << io::iso_date(pe.expiry()) <<
            " not found in Prohibited Expiries");
        BOOST_CHECK_EQUAL(it->expiry(), pe.expiry());
        BOOST_CHECK_EQUAL(it->forFuture(), pe.forFuture());
        BOOST_CHECK_EQUAL(it->futureBdc(), pe.futureBdc());
        BOOST_CHECK_EQUAL(it->forOption(), pe.forOption());
        BOOST_CHECK_EQUAL(it->optionBdc(), pe.optionBdc());
    }
}

BOOST_AUTO_TEST_CASE(testIborConventionConstructionWithTenor) {

    BOOST_TEST_MESSAGE("Testing Ibor Index convention construction with Tenor");
    testIborIndexConvention("AED-EIBOR-3M", "AED", "ACT/360", 2, "MF", false);
}

BOOST_AUTO_TEST_CASE(testIborConventionConstructionWithoutTenor) {

    BOOST_TEST_MESSAGE("Testing Ibor Index convention construction with Tenor");
    testIborIndexConvention("AED-EIBOR", "AED", "ACT/360", 2, "MF", false);
}

BOOST_AUTO_TEST_CASE(testIborConventionConstruction7D) {
    BOOST_TEST_MESSAGE("Testing Ibor Index convention construction with Tenor");
    testIborIndexConvention("CNY-REPO-7D", "CNY", "A365F", 2, "MF", true, "CNY-REPO-1W");
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


/*
    Copyright 2008 Adobe Systems Incorporated
    Distributed under the MIT License (see accompanying file LICENSE_1_0_0.txt
    or a copy at http://stlab.adobe.com/licenses.html)
*/

/**************************************************************************************************/

#ifndef ADOBE_FUNCTIONAL_IS_MEMBER_HPP
#define ADOBE_FUNCTIONAL_IS_MEMBER_HPP

#include <adobe/config.hpp>

#include <boost/iterator/iterator_traits.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/const_iterator.hpp>
#include <boost/range/end.hpp>

#include <adobe/algorithm/binary_search.hpp>
#include <adobe/functional/operator.hpp>

/**************************************************************************************************/

namespace adobe {

/**************************************************************************************************/

/*! \addtogroup misc_functional
@{
*/

template <typename I,        // I ForwardIterator
          typename O = less> // O modles StrictWeakOrdering
struct is_member {
    typedef bool result_type;

    is_member(I f, I l, O o = O()) : first(f), last(l), compare(o) {}

    bool operator()(const typename boost::iterator_value<I>::type& x) const {
        return binary_search(first, last, x, compare) != last;
    }

    I first;
    I last;
    O compare;
};

template <typename I> // I models ForwardIterator
is_member<I, less> make_is_member(I f, I l) {
    return is_member<I, less>(f, l);
}

template <typename I, // I models ForwardIterator
          typename O>
// O modles StrictWeakOrdering
is_member<I, O> make_is_member(I f, I l, O o) {
    return is_member<I, O>(f, l, o);
}

template <typename I> // I models ForwardRange
is_member<typename boost::range_const_iterator<I>::type, less> make_is_member(const I& r) {
    return make_is_member(begin(r), end(r));
}

template <typename I, // I models ForwardRange
          typename O>
// O modles StrictWeakOrdering
is_member<typename boost::range_const_iterator<I>::type, O> make_is_member(const I& r, O o) {
    return make_is_member(begin(r), end(r), o);
}

//!@}

/**************************************************************************************************/

} // namespace adobe

/**************************************************************************************************/

#endif

/**************************************************************************************************/


/**
 * @file backward_visitor.hpp
 * @author Marcus Edel
 *
 * This file provides an abstraction for the Backward() function for different
 * layers and automatically directs any parameter to the right layer type.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#ifndef MLPACK_METHODS_ANN_VISITOR_BACKWARD_VISITOR_HPP
#define MLPACK_METHODS_ANN_VISITOR_BACKWARD_VISITOR_HPP

#include <mlpack/methods/ann/layer/layer_traits.hpp>
#include <mlpack/methods/ann/layer/layer_types.hpp>

#include <boost/variant.hpp>

namespace mlpack {
namespace ann {

/**
 * BackwardVisitor executes the Backward() function given the input, error and
 * delta parameter.
 */
class BackwardVisitor : public boost::static_visitor<void>
{
 public:
  //! Execute the Backward() function given the input, error and delta
  //! parameter.
  BackwardVisitor(arma::mat&& input, arma::mat&& error, arma::mat&& delta);

  //! Execute the Backward() function.
  template<typename LayerType>
  void operator()(LayerType* layer) const;

 private:
  //! The input parameter set.
  arma::mat&& input;

  //! The error parameter.
  arma::mat&& error;

  //! The delta parameter.
  arma::mat&& delta;
};

} // namespace ann
} // namespace mlpack

// Include implementation.
#include "backward_visitor_impl.hpp"

#endif


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_CONSTANTS_30_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_CONSTANTS_30_HPP_INCLUDED

#include <nt2/include/functor.hpp>
#include <boost/simd/constant/hierarchy.hpp>
#include <boost/simd/constant/register.hpp>

namespace nt2
{
  namespace tag
  {
   /*!
     @brief _30 generic tag

     Represents the _30 constant in generic contexts.

     @par Models:
        Hierarchy
   **/
    BOOST_SIMD_CONSTANT_REGISTER( _30, double
                                , 30, 0x41f00000UL
                                , 0x403e000000000000ULL
                                )
  }
  namespace ext
  {
   template<class Site>
   BOOST_FORCEINLINE generic_dispatcher<tag::_30, Site> dispatching__30(adl_helper, boost::dispatch::meta::unknown_<Site>, ...)
   {
     return generic_dispatcher<tag::_30, Site>();
   }
   template<class... Args>
   struct impl__30;
  }

  /*!
    Constant 30.

    @par Semantic:

    For type T0:

    @code
    T0 r = _30<T0>();
    @endcode

    is similar to:

    @code
    T0 r = 30;
    @endcode

    @return a value of type T0
  **/
  BOOST_SIMD_CONSTANT_IMPLEMENTATION(tag::_30, _30);
}

#endif


#ifndef LOOKUP_TABLE_HH__
#define LOOKUP_TABLE_HH__

#include <vector>

#include <boost/math/distributions/chi_squared.hpp>

class LookupTable
{
public:

  /** Creates a new lookup table for the given marginals */
  LookupTable( unsigned n, unsigned n1 );

  /**
     Returns the minimum attainable $p$-value of a given marginal
     value.
  */

  long double operator[]( unsigned rs ) const noexcept
  {
    return _values[rs];
  }

  // Attributes --------------------------------------------------------

  unsigned n()  const noexcept { return _n;  }
  unsigned n1() const noexcept { return _n1; }

private:
  unsigned _n  = 0;
  unsigned _n1 = 0;

  /** Shared distribution for calculating $p$-values */
  static boost::math::chi_squared_distribution<long double> _chi2;

  /** Maps marginals to $p$-values */
  std::vector<long double> _values;
};

#endif


#include "../Common.h"

#include <carl/numbers/numbers.h>

#include <boost/concept_check.hpp>
#include <gtest/gtest.h>

template<typename T>
struct HashCheck {
private:
	T lhs;
public:
	BOOST_CONCEPT_USAGE(HashCheck) {
		require_type<std::size_t>(std::hash<T>()(lhs));
	}
};

template<typename T>
class NumberHash: public testing::Test {};

TYPED_TEST_CASE(NumberHash, NumberTypes);

TYPED_TEST(NumberHash, Hash) {
	BOOST_CONCEPT_ASSERT((HashCheck<TypeParam>));
}


//define USE_INDEPENDENT_FIELDS to throw in fields which are not dependent on
//others to test stream concurrency using nvvp or some other method.  Not
//necessary to test synchronization is working in general.
//#define USE_INDEPENDENT_FIELDS

#include <spatialops/Nebo.h>
#include <test/TestHelper.h>
#include <spatialops/structured/FieldHelper.h>
#include <spatialops/structured/FieldComparisons.h>

#include <iostream>

#include <boost/program_options.hpp>
namespace po = boost::program_options;

using namespace SpatialOps;
using std::cout;
using std::endl;

/**
 * @brief Sets up testing framework based off of user input and runs multiple
 * GPU kernals that depend on each other to test that the final calculation is
 * not affected by a race condition.
 *
 */
int main( int iarg, char* carg[] )
{
    int nx, ny, nz;
    IntVec bcplus ( false, false, false );
    IntVec bcminus( false, false, false );

    po::options_description desc("Supported Options");
    desc.add_options()
        ( "help", "print help message\n" )
        ( "nx",   po::value<int>(&nx)->default_value(10), "number of points in x-dir for base mesh" )
        ( "ny",   po::value<int>(&ny)->default_value(10), "number of points in y-dir for base mesh" )
        ( "nz",   po::value<int>(&nz)->default_value(10), "number of points in z-dir for base mesh" )
        ( "bcx-",  "physical boundary on -x side?" )
        ( "bcx+",  "physical boundary on +x side?" )
        ( "bcy-",  "physical boundary on -y side?" )
        ( "bcy+",  "physical boundary on +y side?" )
        ( "bcz-",  "physical boundary on -z side?" )
        ( "bcz+",  "physical boundary on +z side?" );

    po::variables_map args;
    po::store( po::parse_command_line(iarg,carg,desc), args );
    po::notify(args);

    if( args.count("bcx-") ) bcminus[0] = true;
    if( args.count("bcy-") ) bcminus[1] = true;
    if( args.count("bcz-") ) bcminus[2] = true;
    if( args.count("bcx+") ) bcplus [0] = true;
    if( args.count("bcy+") ) bcplus [1] = true;
    if( args.count("bcz+") ) bcplus [2] = true;

    if( args.count("help") ){
      cout << desc << endl
           << "Examples:" << endl
           << " test_nebo --nx 5 --ny 10 --nz 3 --bcx" << endl
           << " test_nebo --bcx --bcy --bcz" << endl
           << " test_nebo --nx 50 --bcz" << endl
           << endl;
      return -1;
    }

    TestHelper status(true);

    typedef SVolField Field;

    GhostData ghost(1);
    BoundaryCellInfo bcinfo(BoundaryCellInfo::build<Field>(bcminus,bcplus));
    MemoryWindow window(get_window_with_ghost(IntVec(nx, ny, nz), ghost, bcinfo));

    size_t allFields_i = 0;
    static const size_t DEPENDENT_FIELDS = 10000;
#ifdef USE_INDEPENDENT_FIELDS
    static const size_t INDEPENDENT_FIELDS = 300;
    static const size_t ALL_FIELDS = DEPENDENT_FIELDS + INDEPENDENT_FIELDS;
#else
    static const size_t ALL_FIELDS = DEPENDENT_FIELDS;
#endif
    Field* allFields[ALL_FIELDS];

    //Set up fields
    Field* dependentFields[DEPENDENT_FIELDS];
    for(int i = 0; i < DEPENDENT_FIELDS; i++)
    {
      dependentFields[i] = new Field(window, bcinfo, ghost, NULL, InternalStorage, GPU_INDEX);
      *dependentFields[i] <<= 0;

      allFields[allFields_i++] = dependentFields[i];
    }
#ifdef USE_INDEPENDENT_FIELDS
    Field* independentFields[INDEPENDENT_FIELDS];
    for(int i = 0; i < INDEPENDENT_FIELDS; i++)
    {
      independentFields[i] = new Field(window, bcinfo, ghost, NULL, InternalStorage, GPU_INDEX);
      *independentFields[i] <<= i;

      allFields[allFields_i++] = independentFields[i];
    }
#endif

    //Assign streams
    cudaStream_t streams[ALL_FIELDS];
    for(int i = 0; i < ALL_FIELDS; i++)
    {
      cudaStreamCreate(&streams[i]);
      allFields[i]->set_stream(streams[i]);
    }

    //Run calculations
#ifdef USE_INDEPENDENT_FIELDS
    for(int i = 0; i < INDEPENDENT_FIELDS; i += 3)
    {
      *independentFields[i+2] <<= *independentFields[i+1] / *independentFields[i];
    }
#endif
    *dependentFields[0] <<= 1;
    for(int i = 0; i < DEPENDENT_FIELDS-1; i++)
    {
      *dependentFields[i+1] <<= *dependentFields[i] + 1;
    }

    //Compare answer
    status(field_equal(DEPENDENT_FIELDS, *dependentFields[DEPENDENT_FIELDS-1]));

    //Cleanup
    for(int i = 0; i < ALL_FIELDS; i++)
    {
      cudaStreamDestroy(streams[i]);
      delete allFields[i];
    }

    if( status.ok() ) {
      cout << "ALL TESTS PASSED :)" << endl;
      return 0;
    }
    else {
        cout << "******************************" << endl
             << " At least one test FAILED! :(" << endl
             << "******************************" << endl;
        return -1;
    };
}


#include "DanceMove.h"

#include <AdventOfCodeCommon/Utils.h>

#include <AdventOfCodeCommon/DisableLibraryWarningsMacros.h>

__BEGIN_LIBRARIES_DISABLE_WARNINGS
#include <boost/lexical_cast.hpp>
__END_LIBRARIES_DISABLE_WARNINGS

namespace AdventOfCode
{
namespace Year2017
{
namespace Day16
{

DanceMove::SharedPtr DanceMove::sharedPtrFromString(const std::string& danceMoveString)
{
    if (danceMoveString.empty())
    {
        throw std::runtime_error("Move string is empty.");
    }

    const char moveID = danceMoveString.front();
    const std::string argsString = danceMoveString.substr(1);

    if (moveID == 's')
    {
        unsigned offset = boost::lexical_cast<unsigned>(argsString);

        return std::make_shared<SpinMove>(offset);
    }
    else if (moveID == 'x')
    {
        std::vector<unsigned> args;
        Utils::splitStringIntoTypedVector(argsString, args, '/');
        if (args.size() != 2)
        {
            throw std::runtime_error("Two arguments expected for Exchange move.");
        }

        return std::make_shared<ExchangeMove>(args[0], args[1]);
    }
    else if (moveID == 'p')
    {
        std::vector<std::string> args;
        Utils::splitStringIntoTypedVector(argsString, args, '/');
        if (args.size() != 2)
        {
            throw std::runtime_error("Two arguments expected for Partner move.");
        }

        return std::make_shared<PartnerMove>(args[0], args[1]);
    }
    else
    {
        throw std::runtime_error("Unknown move identifier.");
    }
}

SpinMove::SpinMove(unsigned offset) noexcept
    : m_offset{offset}
{

}

void SpinMove::execute(std::string& nameOrderString) const
{
    if (nameOrderString.empty())
    {
        return;
    }

    auto namesInOrderOriginal{nameOrderString};

    for (size_t i = 0; i < nameOrderString.size(); ++i)
    {
        nameOrderString[(i + m_offset) % nameOrderString.size()] = namesInOrderOriginal[i];
    }
}

ExchangeMove::ExchangeMove(unsigned pos1, unsigned pos2) noexcept
    : m_pos1{pos1}
    , m_pos2{pos2}
{

}

void ExchangeMove::execute(std::string& nameOrderString) const
{
    if (nameOrderString.size() < std::max(m_pos1, m_pos2))
    {
        throw std::runtime_error("Too few elements.");
    }

    std::swap(nameOrderString[m_pos1], nameOrderString[m_pos2]);
}

PartnerMove::PartnerMove(std::string name1, std::string name2) noexcept
    : m_name1{std::move(name1)}
    , m_name2{std::move(name2)}
{

}

void PartnerMove::execute(std::string& nameOrderString) const
{
    const size_t name1Pos = nameOrderString.find(m_name1);
    const size_t name2Pos = nameOrderString.find(m_name2);

    if (name1Pos == std::string::npos || name2Pos == std::string::npos)
    {
        throw std::runtime_error("Both named elements need to be present.");
    }

    std::swap(nameOrderString[name1Pos], nameOrderString[name2Pos]);
}

}
}
}


//optical flow

#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/mat.hpp>
#include <opencv2/video/tracking.hpp>
#include <boost/program_options.hpp>
#include <exception>
#include <iostream>
#include "opticalflow.h"

using std::exception;

// TO DO:
// - seperate header and cpp
// add jetson nano  support

namespace po = boost::program_options;

int main(int argc, char** argv )
{

    std::string image1, image2;
    std::string video;
    std::string mode;

    try {

    po::options_description desc("Allowed options");
    desc.add_options()
        ("help ", "produce help message")
        ("mode ",        po::value<std::string>(&mode)->default_value("image"), "select optical flow mode")
        ("video ",       po::value<std::string>(&video)->default_value("null"),  "video path")
        ("image_1",      po::value<std::string>(&image1)->default_value("null"), "image 1")
        ("image_2 ",     po::value<std::string>(&image2)->default_value("null"), "image 2");

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify( vm);

    if (vm.count("help")) {
        std::cout << desc << "\n";
        return 0;
     }
    }

    catch(exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
    return 1;
    }

    if(mode=="image")
    {
         //first frame
    cv::Mat image;
    image = cv::imread( image1, 1 );
    if ( !image.data )
    {
        printf("No image data \n");
        return -1;
    }

    //second frame
    cv::Mat imageNext;
    imageNext = cv::imread( image2, 1 );
    if ( !image.data )
    {
        printf("No image data \n");
        return -1;
    }

    cv::Mat grayImage;
    cv::Mat grayImage_1;

    cv::cvtColor(image, grayImage, cv::COLOR_RGB2GRAY);
    cv::cvtColor(imageNext, grayImage_1, cv::COLOR_RGB2GRAY);
    cv::namedWindow("Base Image", cv::WINDOW_AUTOSIZE );
    cv::namedWindow("Next Image", cv::WINDOW_AUTOSIZE );
    cv::namedWindow("Base Corners", cv::WINDOW_AUTOSIZE );
    cv::namedWindow("Next Image Corners", cv::WINDOW_AUTOSIZE );
    cv::imshow("Base Image", image);
    cv::imshow("Next Image", imageNext);

    cv::RNG rng(12345);
    std::vector<cv::Point2f> corners;
    std::vector<cv::Point2f> corners_1;
    //find counter on first frame
    //grayimage=first frame
    cv::goodFeaturesToTrack(grayImage, corners, 10, 0.2, 3, cv::noArray(), 5, 5);

    std::cout << "number of corners detected: " << corners << std::endl;
    int radius=7;
    cv::Mat copy = imageNext.clone();
    for(size_t i=0; i<corners.size(); i++){
        cv::circle(image, corners[i],radius,cv::Scalar(124,252,0), cv::FILLED);
    }

    cv::goodFeaturesToTrack(grayImage_1, corners_1, 10, 0.2, 3, cv::noArray(), 5, 5);
    for(size_t i=0; i<corners_1.size(); i++){
        cv::circle(imageNext, corners_1[i],radius,cv::Scalar(124,252,0), cv::FILLED);
    }

    //cv::imshow("Gray Image", grayImage);
    cv::imshow("Base Corners", image);
    cv::imshow("Next Image Corners", imageNext);

    //calculate optical flow
    std::vector<uchar> status;
    std::vector<float> err;
    cv::TermCriteria criteria = cv::TermCriteria((cv::TermCriteria::COUNT) + (cv::TermCriteria::EPS), 10, 0.03);
    cv::calcOpticalFlowPyrLK(grayImage, grayImage_1, corners, corners_1, status, err, cv::Size(5,5), 2, criteria);
    cv::Mat old_frame = copy.clone();
    cv::Mat mask = cv::Mat::zeros(old_frame.size(), old_frame.type());

    std::vector<cv::Point2f> good_new;

     for(uint i = 0; i < corners.size(); i++)
        {
            // Select good points
                good_new.push_back(corners_1[i]);
                // draw the tracks
                cv::line(mask,corners_1[i], corners[i], cv::Scalar(124,252,0), 5);
                cv::circle(copy, corners_1[i], 5, cv::Scalar(124,252,0), -1);

        }

        cv::Mat img;
        add(copy, mask, img);
        imshow("Frame", img);

    cv::waitKey(0);

    }
    else if(mode=="motion")
    {
        Opticalflow opt;
        std::cout << "selected mode " << mode << std::endl;
        cv::Mat image;
        //first image
        opt.oldFrame = cv::imread( image1, 1 );
        if ( !image.data )
        {
            printf("No image data \n");
        return -1;
        }
        //second frame
        opt.newFrame = cv::imread( image2, 1 );
        if ( !image.data )
        {
        printf("No image data \n");
        return -1;
        }
        //grayscale
        // dont convert to grayscale! read as grayscale might be sensible
        cv::cvtColor(opt.oldFrame, opt.oldGray, cv::COLOR_RGB2GRAY);
        cv::cvtColor(opt.newFrame, opt.newGray, cv::COLOR_RGB2GRAY);
        int nChannels_1 = opt.oldGray.channels();
        int nChannels_2 = opt.newGray.channels();
        int row_1 = opt.oldGray.rows;
        int row_2 = opt.newGray.rows;
        int nCols_1 = opt.oldGray.cols * nChannels_1;
        int nCols_2 = opt.newGray.cols * nChannels_1;
        std::cout << "test!"<< std::endl;
        std::cout << "row 1 > " << row_1 << std::endl;
        std::cout << "row 2 > " << row_2 << std::endl;
        std::cout << "column 1 > " << nCols_1 << std::endl;
        std::cout << "column 2 > " << nCols_2 << std::endl;
        cv::Mat binaryImage = cv::Mat::zeros(opt.oldGray.size(), opt.oldGray.type());
        std::cout << "test 1.0"<< std::endl;
        binaryImage = opt.createBinarySubImage(opt.oldGray,opt.newGray);
        std::cout << "end of the second iteration" << std::endl;
        cv::namedWindow("Binary Image", cv::WINDOW_AUTOSIZE );
        cv::imshow("Binary Image", binaryImage);
        cv::waitKey(0);
    }else if(mode=="motion video"){
        Opticalflow opt;
        std::cout << "selected mode ->  " << mode << std::endl;
        opt.cap.open(0,cv::CAP_ANY);
        if(!opt.cap.isOpened()){
            std::cerr << "ERROR! Unable to open camera"<< std::endl;
            goto done;
        }
        double fps = opt.cap.get(cv::CAP_PROP_FPS);
        std::cout << "Frames Per Seconds : " << fps << std::endl;
        std::cout << "CAPTURE" << std::endl;

        opt.cap >> opt.oldFrame;
        cv::cvtColor(opt.oldFrame, opt.oldGray, cv::COLOR_BGR2GRAY);
        for (;;)
        {
            // wait for a new frame from camera and store it into 'frame'
            opt.cap >> opt.newFrame;
            // check if we succeeded
            if (opt.newFrame.empty()) {
                std::cerr << "ERROR! blank frame grabbed\n";
                break;
                }
            cv::cvtColor(opt.newFrame, opt.newGray, cv::COLOR_BGR2GRAY);
            cv::Mat binaryImage = cv::Mat::zeros(opt.newGray.size(), opt.newGray.type());
            cv::Mat baseImage = cv::Mat::zeros(opt.newGray.size(), opt.newGray.type());
            binaryImage=opt.createBinarySubImage(opt.oldGray,opt.newGray);
            baseImage=binaryImage.clone();
            // show live and wait for a key with timeout long enough to show images
            if(opt.cumulatif==true){

                //add all images
                cv::addWeighted( baseImage, 0.2, binaryImage, 0.9, 0.0, opt.dst);
                baseImage=opt.dst.clone();
                imshow("Live", opt.dst);

            }else{
                //change old image and show difference between tmp images
                opt.oldGray=opt.newGray.clone();
                imshow("Live", binaryImage);
            }

            if (cv::waitKey(10) == 27)
            {
                std::cout << "Esc key is pressed by user. Stoppig the video" << std::endl;
                break;
                }
        }
    }

    else{
        std::cout << "new mode" << std::endl;

    }

    done :
    return 0;
}


#include "MaterialTemplate.hh"

#include <stdexcept>
#include <boost/lexical_cast.hpp>
#include <G4Material.hh>
#include <G4NistManager.hh>

using namespace boost;
using namespace g4dicom;
using namespace std;
using namespace CLHEP;

/**
  * @short Linear interpolation of values
  */
double interpolate(double x1, double y1, double x2, double y2, double x)
{
    return y1 + (x - x1) / (x2 - x1) * (y2 - y1);
}

/**
  * @short Create material from amounts (int => compound, double => mixture).
  *
  * Template takes advantage of G4Material::AddElement being overloaded.
  */
template<typename T> G4Material* createMaterial(const string& name, double density, map<int, T> amounts)
{
    G4NistManager* nistManager = G4NistManager::Instance();

    G4Material* material = new G4Material(name, density, amounts.size());
    for (auto it = amounts.begin(); it != amounts.end(); it++)
    {
        G4Element* element = nistManager->FindOrBuildElement(it->first);
        if (!element)
        {
            throw runtime_error("Cannot add element with z=" + lexical_cast<string>(it->first));
        }
        material->AddElement(element, it->second);
    }
    return material;
}

void MaterialTemplate::Validate() const
{
    if (minHU > maxHU) throw runtime_error("Max HU must be > min HU.");
    // Three ways of specification. How many are used?
    int matSpecs = !!(g4material.size()) + !!(mixtureElements.size()) + !!(compoundElements.size());
    if (matSpecs != 1)
    {
        throw runtime_error("Must specify exactly one ("
            + lexical_cast<string>(matSpecs)
            + " used): G4 material name; element fractions; number of atoms.");
    }
    if (densities.begin()->first != minHU) {
        throw runtime_error("First density entry must be equal to minimum HU ("
            + lexical_cast<string>(minHU)
            + ")");
    }
    if (densities.rbegin()->first != maxHU) {
        throw runtime_error("Last density entry must be equal to maximum HU ("
            + lexical_cast<string>(minHU)
            + ")");
    }
}

map<int, G4Material*> MaterialTemplate::CreateMaterials(int step) const
{
    Validate();

    G4NistManager* nistManager = G4NistManager::Instance();
    map<int, G4Material*> result;

    for (int hu = minHU; hu <= maxHU; hu += step)
    {
        string newName = name + "_";
        newName += lexical_cast<string>(hu);

        double density = GetDensity(hu) * g/cm3;

        if (g4material.size())
        {
            result[hu] = nistManager->BuildMaterialWithNewDensity(newName, g4material, density);
        }
        else if (mixtureElements.size())
        {
            result[hu] = createMaterial(newName, density, mixtureElements);
        }
        else if (compoundElements.size())
        {
            result[hu] = createMaterial(newName, density, compoundElements);
        }
        /* G4cout << "Created material " << newName << "." << G4endl; */
    }
    return result;
}

double MaterialTemplate::GetDensity(int hu) const
{
    auto it = densities.lower_bound(hu);  // >= hu
    if (it == densities.end())            // more than maximum HU
    {
        throw domain_error("Density cannot be obtained for HU=" + lexical_cast<string>(hu));
    }
    else if (it == densities.begin())
    {
        if (it->first == minHU)           // we hit exactly the minimum HU
        {
            return it->second;
        }
        else                              // below minimum HU
        {
            throw domain_error("Density cannot be obtained for HU=" + lexical_cast<string>(hu));
        }
    }
    else
    {
        double x2 = it->first;
        double y2 = it->second;
        it--;
        double x1 = it->first;
        double y1 = it->second;

        return interpolate(x1, y1, x2, y2, hu);
    }
}

void MaterialTemplate::SetUniformDensity(double density)
{
    densities.clear();
    densities[minHU] = density;
    densities[maxHU] = density;
}


#include <benchmark/benchmark.h>
#include <sstream>
#include <random>
#include <cmath>
#include <charconv>
#include <absl/strings/numbers.h>
#include <absl/strings/charconv.h>
#include <boost/lexical_cast.hpp>
#include <google/protobuf/stubs/strutil.h>

std::string tag = "1234567890123456";
std::string tagf = std::to_string(M_PI);
template<typename S, typename T>
void StringToNumber(const std::string &str, T &t) {
    S oss(str);
    oss >> t;
}

template<typename S, typename T>
static void BenchStreamStringTo(benchmark::State &state) {

    T r = 0;
    for (auto _ : state) {
        StringToNumber<S, T>(tag, r);
        benchmark::DoNotOptimize(r);
    }
}

template<typename S, typename T>
static void BenchStreamStringToF(benchmark::State &state) {
    T r = 0;
    for (auto _ : state) {
        StringToNumber<S, T>(tagf, r);
        benchmark::DoNotOptimize(r);
    }
}

BENCHMARK_TEMPLATE(BenchStreamStringTo, std::stringstream, uint64_t);
BENCHMARK_TEMPLATE(BenchStreamStringTo, std::istringstream, uint64_t);

BENCHMARK_TEMPLATE(BenchStreamStringToF, std::stringstream, float);
BENCHMARK_TEMPLATE(BenchStreamStringToF, std::istringstream, float);

template<typename T>
static void BenchStdAtol(benchmark::State &state) {
    for (auto _ : state) {
        T r = std::atoll(tag.c_str());
        benchmark::DoNotOptimize(r);
    }
}
BENCHMARK_TEMPLATE(BenchStdAtol, uint64_t);

template<typename T>
static void BenchStdAtof(benchmark::State &state) {
    for (auto _ : state) {
        T r = std::atof(tagf.c_str());
        benchmark::DoNotOptimize(r);
    }
}
BENCHMARK_TEMPLATE(BenchStdAtof, float);

template<typename T>
static void BenchStdStol(benchmark::State &state) {
    for (auto _ : state) {
        T r = std::stoull(tag);
        benchmark::DoNotOptimize(r);
    }
}
BENCHMARK_TEMPLATE(BenchStdStol, uint64_t);

template<typename T>
static void BenchStdStof(benchmark::State &state) {
    for (auto _ : state) {
        T r = std::stof(tagf);
        benchmark::DoNotOptimize(r);
    }
}
BENCHMARK_TEMPLATE(BenchStdStof, float);

template<typename T>
static void BenchStdCharConvInt(benchmark::State &state) {
    for (auto _ : state) {
        T r{};
        std::from_chars(tag.data(), tag.data() + tag.size(), r, 10);
        benchmark::DoNotOptimize(r);
    }
}

BENCHMARK_TEMPLATE(BenchStdCharConvInt, uint64_t);

template<typename T>
static void BenchABCharConvIntF(benchmark::State &state) {
    for (auto _ : state) {
        T r{};
        absl::from_chars(tagf.data(), tagf.data() + tagf.size(), r);
        benchmark::DoNotOptimize(r);
    }
}

BENCHMARK_TEMPLATE(BenchABCharConvIntF, float);

template<typename T>
static void BenchBoostStringTo(benchmark::State &state) {
    for (auto _ : state) {
        T r = boost::lexical_cast<T>(tag);
        benchmark::DoNotOptimize(r);
    }
}
template<typename T>
static void BenchBoostStringTof(benchmark::State &state) {
    for (auto _ : state) {
        T r = boost::lexical_cast<T>(tagf);
        benchmark::DoNotOptimize(r);
    }
}

BENCHMARK_TEMPLATE(BenchBoostStringTo, uint64_t);

BENCHMARK_TEMPLATE(BenchBoostStringTof, float);

template<typename T>
static void BenchAbSimpleToInt(benchmark::State &state) {
    T r = 0;
    for (auto _ : state) {
        if (absl::SimpleAtoi(tag, &r)) {
        }
        benchmark::DoNotOptimize(r);
    }
}
BENCHMARK_TEMPLATE(BenchAbSimpleToInt, uint64_t);

template<typename T>
static void BenchAbSimpleToFloat(benchmark::State &state) {
    T r = 0;
    for (auto _ : state) {
        if (absl::SimpleAtof(tagf, &r)) {

        }
        benchmark::DoNotOptimize(r);
    }
}
BENCHMARK_TEMPLATE(BenchAbSimpleToFloat, float);

template<typename T>
static void BenchPBStrAppend(benchmark::State &state) {
    for (auto _ : state) {
        std::string s;
        google::protobuf::StrAppend(&s, tag);
        benchmark::DoNotOptimize(s);
    }
}
BENCHMARK_TEMPLATE(BenchPBStrAppend, uint64_t);

template<typename T>
static void BenchPBStrAppendF(benchmark::State &state) {
    for (auto _ : state) {
        std::string s;
        google::protobuf::StrAppend(&s, tagf);
        benchmark::DoNotOptimize(s);
    }
}
BENCHMARK_TEMPLATE(BenchPBStrAppendF, float);

int main(int argc, char **argv) {

    tag = std::to_string(std::mt19937_64{std::random_device{}()}());
    tagf = std::to_string(std::mt19937_64{std::random_device{}()}());
    benchmark::Initialize(&argc, argv);
    benchmark::RunSpecifiedBenchmarks();
    benchmark::Shutdown();
    return 0;
}


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * 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.
 */

#include "write_numpy.hpp"

#include <fstream>

#include <boost/algorithm/string/join.hpp>
#include <boost/format.hpp>

#include <reversible/pauli_tags.hpp>
#include <reversible/target_tags.hpp>

namespace cirkit
{

/******************************************************************************
 * Types                                                                      *
 ******************************************************************************/

/******************************************************************************
 * Private functions                                                          *
 ******************************************************************************/

std::string identity_padding( const std::string& matrix, unsigned from, unsigned to, unsigned lines )
{
  std::vector<std::string> gates;

  if ( from > 0u )
  {
    gates.push_back( boost::str( boost::format( "np.identity(%d)" ) % ( 1 << from ) ) );
  }

  gates.push_back( matrix );

  if ( to + 1u < lines )
  {
    gates.push_back( boost::str( boost::format( "np.identity(%d)" ) % ( 1 << ( lines - to - 1u ) ) ) );
  }

  switch ( gates.size() )
  {
  case 1u:
    return gates.front();

  case 2u:
    return boost::str( boost::format( "np.kron(%s, %s)" ) % gates[0u] % gates[1u] );

  default:
    return boost::str( boost::format( "nkron(%s)" ) % boost::join( gates, ", " ) );
  }
}

/******************************************************************************
 * Public functions                                                           *
 ******************************************************************************/

void write_numpy( const circuit& circ, std::ostream& os )
{
  const auto n = circ.lines();

  os << "#!/usr/bin/env python3" << std::endl << std::endl;
  os << "import numpy as np" << std::endl;
  os << "from functools import reduce" << std::endl << std::endl;

  os << "X = np.array([[0, 1], [1, 0]])" << std::endl;
  os << "H = 1 / np.sqrt(2) * np.array([[1, 1], [1, -1]])" << std::endl;
  os << "T = np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]])" << std::endl;
  os << "Tdag = np.array([[1, 0], [0, -np.exp(1j * np.pi / 4)]])" << std::endl;
  os << "zc = np.array([[1, 0], [0, 0]])" << std::endl;
  os << "oc = np.array([[0, 0], [0, 1]])" << std::endl;
  os << std::endl;

  os << "def nkron(*args):" << std::endl;
  os << "  return reduce(np.kron, args)" << std::endl;
  os << std::endl;

  os << "gates = []" << std::endl << std::endl;

  for ( const auto& g : circ )
  {
    const auto target = g.targets().front();

    if ( is_toffoli( g ) && g.controls().size() == 1u && g.controls().front().polarity() )
    {
      const auto control = g.controls().front().line();

      if ( control < target )
      {
        const auto act = target - control;
        const auto gate = boost::str( boost::format( "np.kron(zc, np.identity(%d)) + nkron(oc, np.identity(%d), X)" ) % ( 1 << act ) % ( 1 << ( act - 1 ) ) );
        os << boost::format( "gates.append(%s)" ) % identity_padding( gate, control, target, n ) << std::endl;
      }
      else
      {
        const auto act = control - target;
        const auto gate = boost::str( boost::format( "np.kron(np.identity(%d), zc) + nkron(X, np.identity(%d), oc)" ) % ( 1 << act ) % ( 1 << ( act - 1 ) ) );
        os << boost::format( "gates.append(%s)" ) % identity_padding( gate, target, control, n ) << std::endl;
      }
    }
    else if ( is_hadamard( g ) )
    {
      os << boost::format( "gates.append(%s)" ) % identity_padding( "H", target, target, n ) << std::endl;
    }
    else if ( is_pauli( g ) )
    {
      const auto pauli = boost::any_cast<pauli_tag>( g.type() );
      switch ( pauli.axis )
      {
      case pauli_axis::Z:
        if ( pauli.root == 4u )
        {
          os << boost::format( "gates.append(%s)" ) % identity_padding( pauli.adjoint ? "Tdag" : "T", target, target, n ) << std::endl;
        }
        else
        {
          os << "# unsupported gate" << std::endl;
        }
        break;
      default:
        os << "# unsupported gate" << std::endl;
        break;
      }
    }
    else
    {
      os << "# unsupported gate" << std::endl;
    }
  }

  os << std::endl;
  os << "circuit = reduce(np.dot, reversed(gates))" << std::endl;
  os << "print(np.round(circuit))" << std::endl;
}

void write_numpy( const circuit& circ, const std::string& filename )
{
  std::ofstream os( filename.c_str(), std::ofstream::out );

  write_numpy( circ, os );

  os.close();
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End: