// r_c_shortest_paths.hpp header file

// Copyright Michael Drexl 2005, 2006.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GRAPH_R_C_SHORTEST_PATHS_HPP
#define BOOST_GRAPH_R_C_SHORTEST_PATHS_HPP

#include <map>
#include <queue>
#include <vector>
#include <list>

#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/iteration_macros.hpp>
#include <boost/property_map/property_map.hpp>

namespace boost
{

// r_c_shortest_paths_label struct
template < class Graph, class Resource_Container >
struct r_c_shortest_paths_label
: public boost::enable_shared_from_this<
      r_c_shortest_paths_label< Graph, Resource_Container > >
{
    r_c_shortest_paths_label(const unsigned long n,
        const Resource_Container& rc = Resource_Container(),
        const boost::shared_ptr<
            r_c_shortest_paths_label< Graph, Resource_Container > >
            pl
        = boost::shared_ptr<
            r_c_shortest_paths_label< Graph, Resource_Container > >(),
        const typename graph_traits< Graph >::edge_descriptor& ed
        = graph_traits< Graph >::edge_descriptor(),
        const typename graph_traits< Graph >::vertex_descriptor& vd
        = graph_traits< Graph >::vertex_descriptor())
    : num(n)
    , cumulated_resource_consumption(rc)
    , p_pred_label(pl)
    , pred_edge(ed)
    , resident_vertex(vd)
    , b_is_dominated(false)
    , b_is_processed(false)
    {
    }

    r_c_shortest_paths_label& operator=(const r_c_shortest_paths_label& other)
    {
        if (this == &other)
            return *this;
        this->~r_c_shortest_paths_label();
        new (this) r_c_shortest_paths_label(other);
        return *this;
    }
    const unsigned long num;
    Resource_Container cumulated_resource_consumption;
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >
        p_pred_label;
    const typename graph_traits< Graph >::edge_descriptor pred_edge;
    const typename graph_traits< Graph >::vertex_descriptor resident_vertex;
    bool b_is_dominated;
    bool b_is_processed;
}; // r_c_shortest_paths_label

template < class Graph, class Resource_Container >
inline bool operator==(
    const r_c_shortest_paths_label< Graph, Resource_Container >& l1,
    const r_c_shortest_paths_label< Graph, Resource_Container >& l2)
{
    return l1.cumulated_resource_consumption
        == l2.cumulated_resource_consumption;
}

template < class Graph, class Resource_Container >
inline bool operator!=(
    const r_c_shortest_paths_label< Graph, Resource_Container >& l1,
    const r_c_shortest_paths_label< Graph, Resource_Container >& l2)
{
    return !(l1 == l2);
}

template < class Graph, class Resource_Container >
inline bool operator<(
    const r_c_shortest_paths_label< Graph, Resource_Container >& l1,
    const r_c_shortest_paths_label< Graph, Resource_Container >& l2)
{
    return l1.cumulated_resource_consumption
        < l2.cumulated_resource_consumption;
}

template < class Graph, class Resource_Container >
inline bool operator>(
    const r_c_shortest_paths_label< Graph, Resource_Container >& l1,
    const r_c_shortest_paths_label< Graph, Resource_Container >& l2)
{
    return l2.cumulated_resource_consumption
        < l1.cumulated_resource_consumption;
}

template < class Graph, class Resource_Container >
inline bool operator<=(
    const r_c_shortest_paths_label< Graph, Resource_Container >& l1,
    const r_c_shortest_paths_label< Graph, Resource_Container >& l2)
{
    return l1 < l2 || l1 == l2;
}

template < class Graph, class Resource_Container >
inline bool operator>=(
    const r_c_shortest_paths_label< Graph, Resource_Container >& l1,
    const r_c_shortest_paths_label< Graph, Resource_Container >& l2)
{
    return l2 < l1 || l1 == l2;
}

template < typename Graph, typename Resource_Container >
inline bool operator<(
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& t,
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& u)
{
    return *t < *u;
}

template < typename Graph, typename Resource_Container >
inline bool operator<=(
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& t,
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& u)
{
    return *t <= *u;
}

template < typename Graph, typename Resource_Container >
inline bool operator>(
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& t,
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& u)
{
    return *t > *u;
}

template < typename Graph, typename Resource_Container >
inline bool operator>=(
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& t,
    const boost::shared_ptr<
        r_c_shortest_paths_label< Graph, Resource_Container > >& u)
{
    return *t >= *u;
}

namespace detail
{

    // r_c_shortest_paths_dispatch function (body/implementation)
    template < class Graph, class VertexIndexMap, class EdgeIndexMap,
        class Resource_Container, class Resource_Extension_Function,
        class Dominance_Function, class Label_Allocator, class Visitor >
    void r_c_shortest_paths_dispatch(const Graph& g,
        const VertexIndexMap& vertex_index_map,
        const EdgeIndexMap& /*edge_index_map*/,
        typename graph_traits< Graph >::vertex_descriptor s,
        typename graph_traits< Graph >::vertex_descriptor t,
        // each inner vector corresponds to a pareto-optimal path
        std::vector<
            std::vector< typename graph_traits< Graph >::edge_descriptor > >&
            pareto_optimal_solutions,
        std::vector< Resource_Container >& pareto_optimal_resource_containers,
        bool b_all_pareto_optimal_solutions,
        // to initialize the first label/resource container
        // and to carry the type information
        const Resource_Container& rc, Resource_Extension_Function& ref,
        Dominance_Function& dominance,
        // to specify the memory management strategy for the labels
        Label_Allocator /*la*/, Visitor vis)
    {
        pareto_optimal_resource_containers.clear();
        pareto_optimal_solutions.clear();

        size_t i_label_num = 0;
#if defined(BOOST_NO_CXX11_ALLOCATOR)
        typedef typename Label_Allocator::template rebind<
            r_c_shortest_paths_label< Graph, Resource_Container > >::other
            LAlloc;
#else
        typedef typename std::allocator_traits< Label_Allocator >::
            template rebind_alloc<
                r_c_shortest_paths_label< Graph, Resource_Container > >
                LAlloc;
        typedef std::allocator_traits< LAlloc > LTraits;
#endif
        LAlloc l_alloc;
        typedef boost::shared_ptr<
            r_c_shortest_paths_label< Graph, Resource_Container > >
            Splabel;
        std::priority_queue< Splabel, std::vector< Splabel >,
            std::greater< Splabel > >
            unprocessed_labels;

        bool b_feasible = true;
        Splabel splabel_first_label = boost::allocate_shared<
            r_c_shortest_paths_label< Graph, Resource_Container > >(l_alloc,
            i_label_num++, rc,
            boost::shared_ptr<
                r_c_shortest_paths_label< Graph, Resource_Container > >(),
            typename graph_traits< Graph >::edge_descriptor(), s);

        unprocessed_labels.push(splabel_first_label);
        std::vector< std::list< Splabel > > vec_vertex_labels_data(
            num_vertices(g));
        iterator_property_map<
            typename std::vector< std::list< Splabel > >::iterator,
            VertexIndexMap >
            vec_vertex_labels(vec_vertex_labels_data.begin(), vertex_index_map);
        vec_vertex_labels[s].push_back(splabel_first_label);
        typedef std::vector< typename std::list< Splabel >::iterator >
            vec_last_valid_positions_for_dominance_data_type;
        vec_last_valid_positions_for_dominance_data_type
            vec_last_valid_positions_for_dominance_data(num_vertices(g));
        iterator_property_map<
            typename vec_last_valid_positions_for_dominance_data_type::iterator,
            VertexIndexMap >
            vec_last_valid_positions_for_dominance(
                vec_last_valid_positions_for_dominance_data.begin(),
                vertex_index_map);
        BGL_FORALL_VERTICES_T(v, g, Graph)
        {
            put(vec_last_valid_positions_for_dominance, v,
                vec_vertex_labels[v].begin());
        }
        std::vector< size_t > vec_last_valid_index_for_dominance_data(
            num_vertices(g), 0);
        iterator_property_map< std::vector< size_t >::iterator, VertexIndexMap >
            vec_last_valid_index_for_dominance(
                vec_last_valid_index_for_dominance_data.begin(),
                vertex_index_map);
        std::vector< bool > b_vec_vertex_already_checked_for_dominance_data(
            num_vertices(g), false);
        iterator_property_map< std::vector< bool >::iterator, VertexIndexMap >
            b_vec_vertex_already_checked_for_dominance(
                b_vec_vertex_already_checked_for_dominance_data.begin(),
                vertex_index_map);

        while (!unprocessed_labels.empty()
            && vis.on_enter_loop(unprocessed_labels, g))
        {
            Splabel cur_label = unprocessed_labels.top();
            unprocessed_labels.pop();
            vis.on_label_popped(*cur_label, g);
            // an Splabel object in unprocessed_labels and the respective
            // Splabel object in the respective list<Splabel> of
            // vec_vertex_labels share their embedded r_c_shortest_paths_label
            // object to avoid memory leaks, dominated r_c_shortest_paths_label
            // objects are marked and deleted when popped from
            // unprocessed_labels, as they can no longer be deleted at the end
            // of the function; only the Splabel object in unprocessed_labels
            // still references the r_c_shortest_paths_label object this is also
            // for efficiency, because the else branch is executed only if there
            // is a chance that extending the label leads to new undominated
            // labels, which in turn is possible only if the label to be
            // extended is undominated
            if (!cur_label->b_is_dominated)
            {
                typename boost::graph_traits< Graph >::vertex_descriptor
                    i_cur_resident_vertex
                    = cur_label->resident_vertex;
                std::list< Splabel >& list_labels_cur_vertex
                    = get(vec_vertex_labels, i_cur_resident_vertex);
                if (list_labels_cur_vertex.size() >= 2
                    && vec_last_valid_index_for_dominance[i_cur_resident_vertex]
                        < list_labels_cur_vertex.size())
                {
                    typename std::list< Splabel >::iterator outer_iter
                        = list_labels_cur_vertex.begin();
                    bool b_outer_iter_at_or_beyond_last_valid_pos_for_dominance
                        = false;
                    while (outer_iter != list_labels_cur_vertex.end())
                    {
                        Splabel cur_outer_splabel = *outer_iter;
                        typename std::list< Splabel >::iterator inner_iter
                            = outer_iter;
                        if (!b_outer_iter_at_or_beyond_last_valid_pos_for_dominance
                            && outer_iter
                                == get(vec_last_valid_positions_for_dominance,
                                    i_cur_resident_vertex))
                            b_outer_iter_at_or_beyond_last_valid_pos_for_dominance
                                = true;
                        if (!get(b_vec_vertex_already_checked_for_dominance,
                                i_cur_resident_vertex)
                            || b_outer_iter_at_or_beyond_last_valid_pos_for_dominance)
                        {
                            ++inner_iter;
                        }
                        else
                        {
                            inner_iter
                                = get(vec_last_valid_positions_for_dominance,
                                    i_cur_resident_vertex);
                            ++inner_iter;
                        }
                        bool b_outer_iter_erased = false;
                        while (inner_iter != list_labels_cur_vertex.end())
                        {
                            Splabel cur_inner_splabel = *inner_iter;
                            if (dominance(cur_outer_splabel
                                              ->cumulated_resource_consumption,
                                    cur_inner_splabel
                                        ->cumulated_resource_consumption))
                            {
                                typename std::list< Splabel >::iterator buf
                                    = inner_iter;
                                ++inner_iter;
                                list_labels_cur_vertex.erase(buf);
                                if (cur_inner_splabel->b_is_processed)
                                {
                                    cur_inner_splabel.reset();
                                }
                                else
                                    cur_inner_splabel->b_is_dominated = true;
                                continue;
                            }
                            else
                                ++inner_iter;
                            if (dominance(cur_inner_splabel
                                              ->cumulated_resource_consumption,
                                    cur_outer_splabel
                                        ->cumulated_resource_consumption))
                            {
                                typename std::list< Splabel >::iterator buf
                                    = outer_iter;
                                ++outer_iter;
                                list_labels_cur_vertex.erase(buf);
                                b_outer_iter_erased = true;
                                if (cur_outer_splabel->b_is_processed)
                                {
                                    cur_outer_splabel.reset();
                                }
                                else
                                    cur_outer_splabel->b_is_dominated = true;
                                break;
                            }
                        }
                        if (!b_outer_iter_erased)
                            ++outer_iter;
                    }
                    if (list_labels_cur_vertex.size() > 1)
                        put(vec_last_valid_positions_for_dominance,
                            i_cur_resident_vertex,
                            (--(list_labels_cur_vertex.end())));
                    else
                        put(vec_last_valid_positions_for_dominance,
                            i_cur_resident_vertex,
                            list_labels_cur_vertex.begin());
                    put(b_vec_vertex_already_checked_for_dominance,
                        i_cur_resident_vertex, true);
                    put(vec_last_valid_index_for_dominance,
                        i_cur_resident_vertex,
                        list_labels_cur_vertex.size() - 1);
                }
            }
            if (!b_all_pareto_optimal_solutions
                && cur_label->resident_vertex == t)
            {
                // the devil don't sleep
                if (cur_label->b_is_dominated)
                {
                    cur_label.reset();
                }
                while (unprocessed_labels.size())
                {
                    Splabel l = unprocessed_labels.top();
                    unprocessed_labels.pop();
                    // delete only dominated labels, because nondominated labels
                    // are deleted at the end of the function
                    if (l->b_is_dominated)
                    {
                        l.reset();
                    }
                }
                break;
            }
            if (!cur_label->b_is_dominated)
            {
                cur_label->b_is_processed = true;
                vis.on_label_not_dominated(*cur_label, g);
                typename graph_traits< Graph >::vertex_descriptor cur_vertex
                    = cur_label->resident_vertex;
                typename graph_traits< Graph >::out_edge_iterator oei, oei_end;
                for (boost::tie(oei, oei_end) = out_edges(cur_vertex, g);
                     oei != oei_end; ++oei)
                {
                    b_feasible = true;
                    Splabel new_label = boost::allocate_shared<
                        r_c_shortest_paths_label< Graph, Resource_Container > >(
                        l_alloc, i_label_num++,
                        cur_label->cumulated_resource_consumption, cur_label,
                        *oei, target(*oei, g));
                    b_feasible = ref(g,
                        new_label->cumulated_resource_consumption,
                        new_label->p_pred_label->cumulated_resource_consumption,
                        new_label->pred_edge);

                    if (!b_feasible)
                    {
                        vis.on_label_not_feasible(*new_label, g);
                        new_label.reset();
                    }
                    else
                    {
                        vis.on_label_feasible(*new_label, g);
                        vec_vertex_labels[new_label->resident_vertex].push_back(
                            new_label);
                        unprocessed_labels.push(new_label);
                    }
                }
            }
            else
            {
                vis.on_label_dominated(*cur_label, g);
                cur_label.reset();
            }
        }
        std::list< Splabel > dsplabels = get(vec_vertex_labels, t);
        typename std::list< Splabel >::const_iterator csi = dsplabels.begin();
        typename std::list< Splabel >::const_iterator csi_end = dsplabels.end();
        // if d could be reached from o
        if (!dsplabels.empty())
        {
            for (; csi != csi_end; ++csi)
            {
                std::vector< typename graph_traits< Graph >::edge_descriptor >
                    cur_pareto_optimal_path;
                boost::shared_ptr<
                    r_c_shortest_paths_label< Graph, Resource_Container > >
                    p_cur_label = *csi;
                pareto_optimal_resource_containers.push_back(
                    p_cur_label->cumulated_resource_consumption);
                while (p_cur_label->num != 0)
                {
                    cur_pareto_optimal_path.push_back(p_cur_label->pred_edge);
                    p_cur_label = p_cur_label->p_pred_label;

                    // assertion b_is_valid beyond this point is not correct if
                    // the domination function requires resource levels to be
                    // strictly greater than existing values
                    //
                    // Example
                    // Customers
                    // id   min_arrival   max_departure
                    //  2             0             974
                    //  3             0             972
                    //  4             0             964
                    //  5           678             801
                    //
                    // Path A: 2-3-4-5 (times: 0-16-49-84-678)
                    // Path B: 3-2-4-5 (times: 0-18-51-62-678)
                    // The partial path 3-2-4 dominates the other partial path
                    // 2-3-4, though the path 3-2-4-5 does not strictly dominate
                    // the path 2-3-4-5
                }
                pareto_optimal_solutions.push_back(cur_pareto_optimal_path);
                if (!b_all_pareto_optimal_solutions)
                    break;
            }
        }

        BGL_FORALL_VERTICES_T(i, g, Graph)
        {
            std::list< Splabel >& list_labels_cur_vertex = vec_vertex_labels[i];
            typename std::list< Splabel >::iterator si
                = list_labels_cur_vertex.begin();
            const typename std::list< Splabel >::iterator si_end
                = list_labels_cur_vertex.end();
            for (; si != si_end; ++si)
            {
                (*si).reset();
            }
        }
    } // r_c_shortest_paths_dispatch

} // detail

// default_r_c_shortest_paths_visitor struct
struct default_r_c_shortest_paths_visitor
{
    template < class Label, class Graph >
    void on_label_popped(const Label&, const Graph&)
    {
    }
    template < class Label, class Graph >
    void on_label_feasible(const Label&, const Graph&)
    {
    }
    template < class Label, class Graph >
    void on_label_not_feasible(const Label&, const Graph&)
    {
    }
    template < class Label, class Graph >
    void on_label_dominated(const Label&, const Graph&)
    {
    }
    template < class Label, class Graph >
    void on_label_not_dominated(const Label&, const Graph&)
    {
    }
    template < class Queue, class Graph >
    bool on_enter_loop(const Queue& queue, const Graph& graph)
    {
        return true;
    }
}; // default_r_c_shortest_paths_visitor

// default_r_c_shortest_paths_allocator
typedef std::allocator< int > default_r_c_shortest_paths_allocator;
// default_r_c_shortest_paths_allocator

// r_c_shortest_paths functions (handle/interface)
// first overload:
// - return all pareto-optimal solutions
// - specify Label_Allocator and Visitor arguments
template < class Graph, class VertexIndexMap, class EdgeIndexMap,
    class Resource_Container, class Resource_Extension_Function,
    class Dominance_Function, class Label_Allocator, class Visitor >
void r_c_shortest_paths(const Graph& g, const VertexIndexMap& vertex_index_map,
    const EdgeIndexMap& edge_index_map,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t,
    // each inner vector corresponds to a pareto-optimal path
    std::vector<
        std::vector< typename graph_traits< Graph >::edge_descriptor > >&
        pareto_optimal_solutions,
    std::vector< Resource_Container >& pareto_optimal_resource_containers,
    // to initialize the first label/resource container
    // and to carry the type information
    const Resource_Container& rc, const Resource_Extension_Function& ref,
    const Dominance_Function& dominance,
    // to specify the memory management strategy for the labels
    Label_Allocator la, Visitor vis)
{
    r_c_shortest_paths_dispatch(g, vertex_index_map, edge_index_map, s, t,
        pareto_optimal_solutions, pareto_optimal_resource_containers, true, rc,
        ref, dominance, la, vis);
}

// second overload:
// - return only one pareto-optimal solution
// - specify Label_Allocator and Visitor arguments
template < class Graph, class VertexIndexMap, class EdgeIndexMap,
    class Resource_Container, class Resource_Extension_Function,
    class Dominance_Function, class Label_Allocator, class Visitor >
void r_c_shortest_paths(const Graph& g, const VertexIndexMap& vertex_index_map,
    const EdgeIndexMap& edge_index_map,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t,
    std::vector< typename graph_traits< Graph >::edge_descriptor >&
        pareto_optimal_solution,
    Resource_Container& pareto_optimal_resource_container,
    // to initialize the first label/resource container
    // and to carry the type information
    const Resource_Container& rc, const Resource_Extension_Function& ref,
    const Dominance_Function& dominance,
    // to specify the memory management strategy for the labels
    Label_Allocator la, Visitor vis)
{
    // each inner vector corresponds to a pareto-optimal path
    std::vector<
        std::vector< typename graph_traits< Graph >::edge_descriptor > >
        pareto_optimal_solutions;
    std::vector< Resource_Container > pareto_optimal_resource_containers;
    r_c_shortest_paths_dispatch(g, vertex_index_map, edge_index_map, s, t,
        pareto_optimal_solutions, pareto_optimal_resource_containers, false, rc,
        ref, dominance, la, vis);
    if (!pareto_optimal_solutions.empty())
    {
        pareto_optimal_solution = pareto_optimal_solutions[0];
        pareto_optimal_resource_container
            = pareto_optimal_resource_containers[0];
    }
}

// third overload:
// - return all pareto-optimal solutions
// - use default Label_Allocator and Visitor
template < class Graph, class VertexIndexMap, class EdgeIndexMap,
    class Resource_Container, class Resource_Extension_Function,
    class Dominance_Function >
void r_c_shortest_paths(const Graph& g, const VertexIndexMap& vertex_index_map,
    const EdgeIndexMap& edge_index_map,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t,
    // each inner vector corresponds to a pareto-optimal path
    std::vector<
        std::vector< typename graph_traits< Graph >::edge_descriptor > >&
        pareto_optimal_solutions,
    std::vector< Resource_Container >& pareto_optimal_resource_containers,
    // to initialize the first label/resource container
    // and to carry the type information
    const Resource_Container& rc, const Resource_Extension_Function& ref,
    const Dominance_Function& dominance)
{
    r_c_shortest_paths_dispatch(g, vertex_index_map, edge_index_map, s, t,
        pareto_optimal_solutions, pareto_optimal_resource_containers, true, rc,
        ref, dominance, default_r_c_shortest_paths_allocator(),
        default_r_c_shortest_paths_visitor());
}

// fourth overload:
// - return only one pareto-optimal solution
// - use default Label_Allocator and Visitor
template < class Graph, class VertexIndexMap, class EdgeIndexMap,
    class Resource_Container, class Resource_Extension_Function,
    class Dominance_Function >
void r_c_shortest_paths(const Graph& g, const VertexIndexMap& vertex_index_map,
    const EdgeIndexMap& edge_index_map,
    typename graph_traits< Graph >::vertex_descriptor s,
    typename graph_traits< Graph >::vertex_descriptor t,
    std::vector< typename graph_traits< Graph >::edge_descriptor >&
        pareto_optimal_solution,
    Resource_Container& pareto_optimal_resource_container,
    // to initialize the first label/resource container
    // and to carry the type information
    const Resource_Container& rc, const Resource_Extension_Function& ref,
    const Dominance_Function& dominance)
{
    // each inner vector corresponds to a pareto-optimal path
    std::vector<
        std::vector< typename graph_traits< Graph >::edge_descriptor > >
        pareto_optimal_solutions;
    std::vector< Resource_Container > pareto_optimal_resource_containers;
    r_c_shortest_paths_dispatch(g, vertex_index_map, edge_index_map, s, t,
        pareto_optimal_solutions, pareto_optimal_resource_containers, false, rc,
        ref, dominance, default_r_c_shortest_paths_allocator(),
        default_r_c_shortest_paths_visitor());
    if (!pareto_optimal_solutions.empty())
    {
        pareto_optimal_solution = pareto_optimal_solutions[0];
        pareto_optimal_resource_container
            = pareto_optimal_resource_containers[0];
    }
}
// r_c_shortest_paths

// check_r_c_path function
template < class Graph, class Resource_Container,
    class Resource_Extension_Function >
void check_r_c_path(const Graph& g,
    const std::vector< typename graph_traits< Graph >::edge_descriptor >&
        ed_vec_path,
    const Resource_Container& initial_resource_levels,
    // if true, computed accumulated final resource levels must
    // be equal to desired_final_resource_levels
    // if false, computed accumulated final resource levels must
    // be less than or equal to desired_final_resource_levels
    bool b_result_must_be_equal_to_desired_final_resource_levels,
    const Resource_Container& desired_final_resource_levels,
    Resource_Container& actual_final_resource_levels,
    const Resource_Extension_Function& ref, bool& b_is_a_path_at_all,
    bool& b_feasible, bool& b_correctly_extended,
    typename graph_traits< Graph >::edge_descriptor& ed_last_extended_arc)
{
    size_t i_size_ed_vec_path = ed_vec_path.size();
    std::vector< typename graph_traits< Graph >::edge_descriptor > buf_path;
    if (i_size_ed_vec_path == 0)
        b_feasible = true;
    else
    {
        if (i_size_ed_vec_path == 1
            || target(ed_vec_path[0], g) == source(ed_vec_path[1], g))
            buf_path = ed_vec_path;
        else
            for (size_t i = i_size_ed_vec_path; i > 0; --i)
                buf_path.push_back(ed_vec_path[i - 1]);
        for (size_t i = 0; i < i_size_ed_vec_path - 1; ++i)
        {
            if (target(buf_path[i], g) != source(buf_path[i + 1], g))
            {
                b_is_a_path_at_all = false;
                b_feasible = false;
                b_correctly_extended = false;
                return;
            }
        }
    }
    b_is_a_path_at_all = true;
    b_feasible = true;
    b_correctly_extended = false;
    Resource_Container current_resource_levels = initial_resource_levels;
    actual_final_resource_levels = current_resource_levels;
    for (size_t i = 0; i < i_size_ed_vec_path; ++i)
    {
        ed_last_extended_arc = buf_path[i];
        b_feasible = ref(g, actual_final_resource_levels,
            current_resource_levels, buf_path[i]);
        current_resource_levels = actual_final_resource_levels;
        if (!b_feasible)
            return;
    }
    if (b_result_must_be_equal_to_desired_final_resource_levels)
        b_correctly_extended
            = actual_final_resource_levels == desired_final_resource_levels
            ? true
            : false;
    else
    {
        if (actual_final_resource_levels < desired_final_resource_levels
            || actual_final_resource_levels == desired_final_resource_levels)
            b_correctly_extended = true;
    }
} // check_path

} // namespace

#endif // BOOST_GRAPH_R_C_SHORTEST_PATHS_HPP


/* Copyright (c) 2016 - 2020, the adamantine authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#include <MaterialProperty.hh>
#include <instantiation.hh>

#include <deal.II/base/point.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/fe/fe_values.h>

#include <boost/algorithm/string/split.hpp>
#include <boost/optional.hpp>

namespace adamantine
{

template <int dim>
MaterialProperty<dim>::MaterialProperty(
    MPI_Comm const &communicator,
    dealii::parallel::distributed::Triangulation<dim> const &tria,
    boost::property_tree::ptree const &database)
    : _communicator(communicator), _fe(0), _mp_dof_handler(tria)
{
  // Because deal.II cannot easily attach data to a cell. We store the state
  // of the material in distributed::Vector. This allows to use deal.II to
  // compute the new state after refinement of the mesh. However, this
  // requires to use another DoFHandler.
  reinit_dofs();

  // Set the material state to the state defined in the geometry.
  set_state();

  // Fill the _properties map
  fill_properties(database);
}

template <int dim>
double MaterialProperty<dim>::get(
    typename dealii::Triangulation<dim>::active_cell_iterator const &cell,
    StateProperty prop) const
{
  unsigned int property = static_cast<unsigned int>(prop);
  double const mp_dof_index = get_dof_index(cell);

  return _property_values[property][mp_dof_index];
}

template <int dim>
double MaterialProperty<dim>::get(
    typename dealii::Triangulation<dim>::active_cell_iterator const &cell,
    Property prop) const
{
  dealii::types::material_id material_id = cell->material_id();
  unsigned int property = static_cast<unsigned int>(prop);

  return _properties.find(material_id)->second[property];
}

template <int dim>
void MaterialProperty<dim>::reinit_dofs()
{
  _mp_dof_handler.distribute_dofs(_fe);
  // Initialize the state vectors
  for (auto &vec : _state)
    vec.reinit(_mp_dof_handler.locally_owned_dofs(), _communicator);
}

template <int dim>
void MaterialProperty<dim>::update(
    dealii::DoFHandler<dim> const &temperature_dof_handler,
    dealii::LA::distributed::Vector<double> const &temperature)
{
  auto temperature_average =
      compute_average_temperature(temperature_dof_handler, temperature);
  for (auto &val : _property_values)
    val.reinit(temperature_average.get_partitioner());

  std::vector<dealii::types::global_dof_index> mp_dof(1);
  for (auto cell :
       dealii::filter_iterators(_mp_dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    dealii::types::material_id material_id = cell->material_id();
    auto const tmp = _properties.find(material_id);
    ASSERT(tmp != _properties.end(), "Material not found.");

    unsigned int constexpr liquid =
        static_cast<unsigned int>(MaterialState::liquid);
    unsigned int constexpr powder =
        static_cast<unsigned int>(MaterialState::powder);
    unsigned int constexpr solid =
        static_cast<unsigned int>(MaterialState::solid);
    unsigned int constexpr prop_solidus =
        static_cast<unsigned int>(Property::solidus);
    unsigned int constexpr prop_liquidus =
        static_cast<unsigned int>(Property::liquidus);

    double const solidus = tmp->second[prop_solidus];
    double const liquidus = tmp->second[prop_liquidus];
    cell->get_dof_indices(mp_dof);
    unsigned int const dof = mp_dof[0];

    // First determine the ratio of liquid.
    double liquid_ratio = -1.;
    double powder_ratio = -1.;
    double solid_ratio = -1.;
    if (temperature_average[dof] < solidus)
      liquid_ratio = 0.;
    else if (temperature_average[dof] > liquidus)
      liquid_ratio = 1.;
    else
      liquid_ratio =
          (temperature_average[dof] - solidus) / (liquidus - solidus);
    // Because the powder can only become liquid, the solid can only become
    // liquid, and the liquid can only become solid, the ratio of powder can
    // only decrease.
    powder_ratio = std::min(1. - liquid_ratio, _state[powder][dof]);
    // Use max to make sure that we don't create matter because of round-off.
    solid_ratio = std::max(1 - liquid_ratio - powder_ratio, 0.);

    // Update the value
    _state[liquid][dof] = liquid_ratio;
    _state[powder][dof] = powder_ratio;
    _state[solid][dof] = solid_ratio;

    if (_use_table)
    {
      for (unsigned int property = 0; property < _n_state_properties;
           ++property)
      {
        for (unsigned int material_state = 0;
             material_state < _n_material_states; ++material_state)
        {
          _property_values[property][dof] +=
              _state[material_state][dof] *
              compute_property_from_table(
                  _state_property_tables[material_id][material_state][property],
                  temperature_average[dof]);
        }
      }
    }
    else
    {
      for (unsigned int property = 0; property < _n_state_properties;
           ++property)
      {
        for (unsigned int material_state = 0;
             material_state < _n_material_states; ++material_state)
        {
          _property_values[property][dof] +=
              _state[material_state][dof] *
              compute_property_from_polynomial(
                  _state_property_polynomials[material_id][material_state]
                                             [property],
                  temperature_average[dof]);
        }
      }
    }

    // If we are in the mushy state, i.e., part liquid part solid, we need to
    // modify the rho C_p to take into account the latent heat.
    if ((liquid_ratio > 0.) && (liquid_ratio < 1.))
    {
      unsigned int specific_heat_prop =
          static_cast<unsigned int>(StateProperty::specific_heat);
      unsigned int latent_heat_prop =
          static_cast<unsigned int>(Property::latent_heat);
      for (unsigned int material_state = 0; material_state < _n_material_states;
           ++material_state)
      {
        _property_values[specific_heat_prop][dof] +=
            _state[material_state][dof] *
            _properties[material_id][latent_heat_prop] / (liquidus - solidus);
      }
    }
  }
}

template <int dim>
void MaterialProperty<dim>::set_state()
{
  // Set the material state to the one defined by the user_index
  std::vector<dealii::types::global_dof_index> mp_dof(1);
  for (auto cell :
       dealii::filter_iterators(_mp_dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    cell->get_dof_indices(mp_dof);
    _state[cell->user_index()][mp_dof[0]] = 1.;
  }
}

template <int dim>
void MaterialProperty<dim>::fill_properties(
    boost::property_tree::ptree const &database)
{
  std::array<std::string, _n_material_states> material_state = {
      {"powder", "solid", "liquid"}};
  std::array<std::string, _n_properties> properties = {
      {"liquidus", "solidus", "latent_heat"}};
  std::array<std::string, _n_state_properties> state_properties = {
      {"density", "specific_heat", "thermal_conductivity"}};

  std::string property_format = database.get<std::string>("property_format");
  ASSERT_THROW((property_format == "table") ||
                   (property_format == "polynomial"),
               "property_format should be table or polynomial.");
  _use_table = (property_format == "table");
  unsigned int const n_materials = database.get<unsigned int>("n_materials");
  dealii::types::material_id next_material_id = 0;
  for (unsigned int i = 0; i < n_materials; ++i)
  {
    // Try to find the material_id by checking every possible number.
    dealii::types::material_id material_id = next_material_id;
    while (material_id < dealii::numbers::invalid_material_id)
    {
      // If the child exists, exit the loop.
      if (database.count("material_" + std::to_string(material_id)) != 0)
        break;

      ++material_id;
    }
    ASSERT_THROW(material_id != dealii::numbers::invalid_material_id,
                 "Invalid material ID. Choose a smaller number.");
    // Update the next possible material id
    ++next_material_id;

    // Get the material property tree.
    boost::property_tree::ptree const &material_database =
        database.get_child("material_" + std::to_string(material_id));
    // For each material, loop over the possible states.
    bool valid_state = false;
    for (unsigned int state = 0; state < _n_material_states; ++state)
    {
      // The state may or may not exist for the material.
      boost::optional<boost::property_tree::ptree const &> state_database =
          material_database.get_child_optional(material_state[state]);
      if (state_database)
      {
        valid_state = true;
        // For each state, loop over the possible properties.
        for (unsigned int p = 0; p < _n_state_properties; ++p)
        {
          // The property may or may not exist for that state
          boost::optional<std::string> const property =
              state_database.get().get_optional<std::string>(
                  state_properties[p]);
          // If the property exists, put it in the map. If the property does not
          // exist, we have a nullptr.
          if (property)
          {
            // Remove blank spaces
            std::string property_string = property.get();
            property_string.erase(
                std::remove_if(property_string.begin(), property_string.end(),
                               [](unsigned char x) { return std::isspace(x); }),
                property_string.end());
            if (_use_table)
            {
              std::vector<std::string> parsed_property;
              boost::split(parsed_property, property_string,
                           [](char c) { return c == ';'; });
              for (auto const &p_property : parsed_property)
              {
                std::vector<std::string> t_v;
                boost::split(t_v, p_property, [](char c) { return c == ','; });
                ASSERT(t_v.size() == 2, "Error reading material property.");
                _state_property_tables[material_id][state][p].emplace_back(
                    std::stod(t_v[0]), std::stod(t_v[1]));
              }
            }
            else
            {
              std::vector<std::string> parsed_property;
              boost::split(parsed_property, property_string,
                           [](char c) { return c == ','; });
              for (auto const &p_property : parsed_property)
              {
                _state_property_polynomials[material_id][state][p].emplace_back(
                    std::stod(p_property));
              }
            }
          }
        }
      }
    }
    // Check that there is at least one valid MaterialState
    ASSERT_THROW(
        valid_state == true,
        "Material without any valid state (solid, powder, or liquid).");

    // Check for the properties that are associated to a material but that
    // are independent of an individual state. These properties are duplicated
    // for every state.
    for (unsigned int p = 0; p < _n_properties; ++p)
    {
      // The property may or may not exist for that state
      boost::optional<double> const property =
          material_database.get_optional<double>(properties[p]);
      // If the property exists, put it in the map. If the property does not
      // exist, we use the largest. This is useful if the liquidus and the
      // solidus are not set.
      _properties[material_id][p] =
          property ? property.get() : std::numeric_limits<double>::max();
    }
  }
}

// We need to compute the average temperature on the cell because we need the
// material properties to be uniform over the cell. If there aren't then we have
// problems with the weak form discretization.
template <int dim>
dealii::LA::distributed::Vector<double>
MaterialProperty<dim>::compute_average_temperature(
    dealii::DoFHandler<dim> const &temperature_dof_handler,
    dealii::LA::distributed::Vector<double> const &temperature) const
{
  // TODO: this should probably done in a matrix-free fashion.
  // The triangulation is the same for both DoFHandler
  dealii::LA::distributed::Vector<double> temperature_average(
      _mp_dof_handler.locally_owned_dofs(), temperature.get_mpi_communicator());
  temperature_average = 0.;
  auto mp_cell = _mp_dof_handler.begin_active();
  auto mp_end_cell = _mp_dof_handler.end();
  auto enth_cell = temperature_dof_handler.begin_active();
  dealii::FiniteElement<dim> const &fe = temperature_dof_handler.get_fe();
  // We can use a lower degree of quadrature since we are projecting on a
  // piecewise constant space
  dealii::QGauss<dim> quadrature(fe.degree);
  dealii::FEValues<dim> fe_values(
      fe, quadrature,
      dealii::UpdateFlags::update_values |
          dealii::UpdateFlags::update_quadrature_points |
          dealii::UpdateFlags::update_JxW_values);
  unsigned int const dofs_per_cell = fe.dofs_per_cell;
  std::vector<dealii::types::global_dof_index> mp_dof_indices(1);
  std::vector<dealii::types::global_dof_index> enth_dof_indices(dofs_per_cell);
  unsigned int const n_q_points = quadrature.size();
  for (; mp_cell != mp_end_cell; ++enth_cell, ++mp_cell)
    if (mp_cell->is_locally_owned())
    {
      fe_values.reinit(enth_cell);
      mp_cell->get_dof_indices(mp_dof_indices);
      dealii::types::global_dof_index const mp_dof_index = mp_dof_indices[0];
      enth_cell->get_dof_indices(enth_dof_indices);
      double volume = 0.;
      for (unsigned int q = 0; q < n_q_points; ++q)
        for (unsigned int i = 0; i < dofs_per_cell; ++i)
        {
          volume += fe_values.shape_value(i, q) * fe_values.JxW(q);
          temperature_average[mp_dof_index] +=
              fe_values.shape_value(i, q) * temperature[enth_dof_indices[i]] *
              fe_values.JxW(q);
        }
      temperature_average[mp_dof_index] /= volume;
    }

  return temperature_average;
}

template <int dim>
double MaterialProperty<dim>::compute_property_from_table(
    std::vector<std::pair<double, double>> const &table,
    double const temperature) const
{
  if (table.size() == 0)
    return 0.;
  else
  {
    if (temperature <= table.front().first)
      return table.front().second;
    else if (temperature >= table.back().first)
      return table.back().second;
    else
    {
      auto it = std::find_if(table.begin(), table.end(),
                             [=](std::pair<double, double> const &t_v) {
                               return t_v.first > temperature;
                             });
      auto prev_it = it - 1;
      return prev_it->second + (temperature - prev_it->first) *
                                   (it->second - prev_it->second) /
                                   (it->first - prev_it->first);
    }
  }
}

template <int dim>
double MaterialProperty<dim>::compute_property_from_polynomial(
    std::vector<double> const &coef, double const temperature) const
{
  double value = 0.;
  unsigned int const n_coef = coef.size();
  for (unsigned int i = 0; i < n_coef; ++i)
  {
    value += coef[i] * std::pow(temperature, i);
  }

  return value;
}
} // namespace adamantine

INSTANTIATE_DIM(MaterialProperty)


// Spear: Statistical Platform for Elucidating moleculAr Reactivity
// Copyright (C) Purdue University -- BSD license

#include "spear/Molecule.hpp"

#include <boost/graph/graph_utility.hpp>
#include <boost/graph/hawick_circuits.hpp>
#include <boost/graph/undirected_graph.hpp>
#include <boost/graph/connected_components.hpp>

#include "spear/atomtypes/Default.hpp"

#include "spear/partialcharges/DefaultPartialCharge.hpp"

#include "spear/Geometry.hpp"

#ifndef M_PI
static const auto M_PI = std::acos(0.0) * 2;
#endif

using Eigen::Vector3d;

using namespace Spear;

size_t AtomVertex::expected_bonds() const {
    auto atomic_num = atomic_number();

    switch(atomic_num) {
        case Element::H:  // Hydrogen
        case Element::F:  // Fluorine
        case Element::Cl: // Chlorine
        case Element::Br: // Bromine
        case Element::I:  // Iodine
        return 1;
        break;

        case Element::B:  // Boron
        case Element::Al: // Aluminium
        return 3;
        break;

        default: break;
    }

    auto hybridization_state = br_->atomtype()->hybridization(index_);

    if(atomic_num == Element::C) {
        switch (hybridization_state) {
            case Hybridization::SP: return 2; break;
            case Hybridization::SP2: return 3; break;
            case Hybridization::SP3: return 4; break;
            default: return 0; break;
        }
    }

    if(atomic_num == Element::N) {
        switch (hybridization_state) {
            case Hybridization::SP: return 1; break;
            case Hybridization::SP2: return 2; break;
            case Hybridization::SP3: return 3; break;
            default: return 0; break;
        }
    }

    if(atomic_num == Element::O) {
        switch (hybridization_state) {
            case Hybridization::SP: return 1; break;
            case Hybridization::SP2: return 1; break;
            case Hybridization::SP3: return 2; break;
            default: return 0; break;
        }
    }

    if(atomic_num == 15 || atomic_num == 16) { // Phosphorus and Sulfur
        if (is_aromatic()) return 2;
        return degree(); // Hard to tell, but things shouldn't be protonated
    }

    // Give up....
    return static_cast<size_t>(hybridization_state);
}

// The cycle_printer is a visitor that will print the path that comprises
// the cycle. Note that the back() vertex of the path is not the same as
// the front(). It is implicit in the listing of vertices that the back()
// vertex is connected to the front().
struct cycle_saver {

    cycle_saver(RingSet& counter):
        found_rings(counter){
    }

    template <typename Path, typename Graph>
    void cycle(const Path& p, const Graph& g) {
        // Get the property map containing the vertex indices that are saved
        auto indices = boost::get(boost::vertex_index, g);

        // We deal mostly with undirected graphs, so a bond is technically a
        // cycle. Let's avoid those!
        if (p.size() <= 2) {
            return;
        }

        // We've found a ring, so add it to found rings
        std::set<size_t> new_ring;
        for(const auto& i : p) {
            new_ring.insert(boost::get(indices, i));
        }

        found_rings.insert(std::move(new_ring));
    }

    RingSet& found_rings;
};

Molecule::Molecule() {
    add_atomtype<Default>();
    std::vector<double> charges;
    add_partial_charge<DefaultPartialCharge>(std::move(charges));
}

void Molecule::init_(const chemfiles::Frame& frame) {
    std::vector<double> charges;
    charges.reserve(frame.size());
    for (const auto& atom : frame) {
        auto atomic_number = atom.atomic_number();
        if (atomic_number) {
            auto an = static_cast<Element::Symbol>(*atomic_number);
            boost::add_vertex(an, graph_);
        } else {
            boost::add_vertex(Element::Symbol(0), graph_);
        }
        charges.push_back(atom.charge());
    }

    for (size_t i = 0; i < frame.size(); ++i) {
        auto& pos = frame.positions()[i];
        positions_.emplace_back(pos[0], pos[1], pos[2]);
    }

    // Create the graph representation
    auto bonds = topology_.bonds();
    auto bos = topology_.bond_orders();
    for ( size_t i = 0; i < bonds.size(); ++i) {
        boost::add_edge(bonds[i][0], bonds[i][1],
            static_cast<Bond::Order>(bos[i]), graph_);
    }

    add_partial_charge<DefaultPartialCharge>(std::move(charges));
}

void Molecule::rings_() {

    if (!all_rings_.empty()) {
        return;
    }

    bool remove_metal_bonds_ = true;
    std::vector<std::pair<size_t, size_t>> metal_bonds;
    if (remove_metal_bonds_) {
        for (auto bond : bonds()) {
            if (!bond.source().is_non_metal() ||
                !bond.target().is_non_metal()) {
                metal_bonds.push_back({bond.source(), bond.target()});
            }

            auto res1 = topology_.residue_for_atom(bond.source());
            auto res2 = topology_.residue_for_atom(bond.target());
            if (res1 && res2 && res1->name() == "CYS" && res2->name() == "CYS" &&
                *res1 != *res2) {
                metal_bonds.push_back({bond.source(), bond.target()});
            }
        }
        for (auto& rm_bond : metal_bonds) {
            remove_bond(rm_bond.first, rm_bond.second);
        }
    }

    cycle_saver vis(all_rings_);
    boost::hawick_circuits(graph_, vis);

    for (auto& ring : all_rings_) {
        for (auto& atom : ring) {
            atom_to_ring_.insert({atom, ring});
        }
    }

    smallest_set_of_smallest_rings_();

    for (auto& rm_bond : metal_bonds) {
        add_bond(rm_bond.first, rm_bond.second);
    }
}

void Molecule::smallest_set_of_smallest_rings_() {

    if (!sssr_.empty()) {
        return;
    }

    auto all_rings = rings();

    if (all_rings.size() == 0) {
        return;
    }

    auto sssr_count = boost::num_edges(graph_) + connected_components() - size();

    if (sssr_count >= all_rings.size()) {
        sssr_ = all_rings_;
        return;
    }

    size_t max_degree = 0;
    for (auto av : *this) {
        max_degree = std::max(max_degree, av.degree());
    }

    auto find_independant_rings_with_degree =
    [sssr_count, &all_rings, this] (size_t degree) {
        std::vector<bool> used_nodes(size(), false); // move out for speed?

        // The rings are already sorted by their size, therefor we will
        // traverse all of the rings from the smallest to largest here.
        for (auto& current_ring : all_rings) {
            bool is_independant = false;

            // Have we seen this node before?
            // ... but only check if the current degree is correct!
            for (auto atom_id_search : current_ring) {
                if ((*this)[atom_id_search].degree() != degree ||
                    used_nodes[atom_id_search]) {
                    continue;
                }

                is_independant = true;
                break;
            }

            // This ring is completly encircled for the current degree
            if (!is_independant) {
                continue;
            }

            // We found a smallest ring! Mark its atoms as used
            sssr_.insert(current_ring);

            for (auto atom_id : current_ring) {
                used_nodes[atom_id] = true;
            }

            if (sssr_.size() == sssr_count) {
                break;
            }
        }
    };

    size_t iterations = 0; // I'm really scared of non termination. Let's be safe
    do {
        for (size_t i = 2; i <= max_degree; ++i) {
            find_independant_rings_with_degree(i);

            if (sssr_.size() == sssr_count) { // we're done!
                for (auto& ring : sssr_) {
                    for (auto& atom : ring) {
                        atom_to_sssr_.insert({atom, ring});
                    }
                }

                return;
            }
        }

        if (sssr_.size() > sssr_count) {
            break;
        }

        // things just got really weird - suppress the current SSSRs and try again
        for (const auto& added : sssr_) {
            all_rings.erase(added);
        }

        ++iterations;
    } while(all_rings.size() != 0 && iterations < 100);

    throw std::runtime_error(std::string("Unable to find SSSR: found ") +
                             std::to_string(sssr_.size()) + " rings, but expected " +
                             std::to_string(sssr_count));
}

void Molecule::add_default_type() {
    add_atomtype<Default>();
}

std::vector<Spear::BondEdge> Molecule::get_bonds_in(const std::set<size_t>& atoms) const {
    auto all_bonds = bonds();

    std::vector<BondEdge> ret;

    std::copy_if(all_bonds.begin(), all_bonds.end(),
                 std::back_inserter(ret),
        [&atoms, this](BondEdge e){
            auto target = e.target();
            auto source = e.source();
            if (atoms.count(target) == 0) {
                return false;
            }
            if (atoms.count(source) == 0) {
                return false;
            }
            return true;
    });

    return ret;
}

size_t Molecule::connected_components() const {
    std::vector<int> component(size());
    return static_cast<size_t>(boost::connected_components(graph_, &component[0]));
}

void Molecule::remove_bond(size_t idx1, size_t idx2) {
    boost::remove_edge(idx1, idx2, graph_);
    topology_.remove_bond(idx1, idx2);
}

void Molecule::remove_atom(size_t index) {
    boost::clear_vertex(index, graph_);
    boost::remove_vertex(index, graph_);
    topology_.remove(index);
    positions_.erase(positions_.begin() + static_cast<std::ptrdiff_t>(index));
    for (const auto& at : atom_types_) {
         at.second->remove_atom(index);
    }
}

void Molecule::swap_atoms(size_t idx1, size_t idx2) {

    if (idx1 == idx2) {
        throw std::logic_error("You cannot swap an atom with itself");
    }

    std::swap(positions_[idx1], positions_[idx2]);

    for (const auto& at : atom_types_) {
        at.second->swap(idx1, idx2);
    }

    std::set<std::tuple<size_t, size_t, Bond::Order>> atom_bonds;

    auto atm1 = (*this)[idx1].atomic_number();
    auto atm2 = (*this)[idx2].atomic_number();

    boost::get(boost::vertex_name, graph_, idx1) = atm2;
    boost::get(boost::vertex_name, graph_, idx2) = atm1;

    for (auto bond : (*this)[idx1].bonds()) {
        if (bond.source() == idx2 || bond.target() == idx2) {
            atom_bonds.insert({idx1, idx2, bond.order()});
            remove_bond(bond.source(), bond.target());
            continue;
        }

        if (bond.source() == idx1) {
            atom_bonds.insert({idx2, bond.target(), bond.order()});
        } else {
            atom_bonds.insert({bond.source(), idx2, bond.order()});
        }
        remove_bond(bond.source(), bond.target());
    }

    for (auto bond : (*this)[idx2].bonds()) {
        // The swapped atoms are bonded, only do this re-addition once
        if (bond.source() == idx1 || bond.target() == idx1) {
            remove_bond(bond.source(), bond.target());
            continue;
        }
        if (bond.source() == idx2) {
            atom_bonds.insert({idx1, bond.target(), bond.order()});
        } else {
            atom_bonds.insert({bond.source(), idx1, bond.order()});
        }
        remove_bond(bond.source(), bond.target());
    }

    for (auto& bond : atom_bonds) {
        add_bond(get<0>(bond), get<1>(bond), get<2>(bond));
    }

    std::swap(topology_[idx1], topology_[idx2]);
}

void Molecule::remove_hydrogens() {
    auto& mol = *this;

    // Removal of an atom invalidates the vertex descriptors as we are using a
    // vecS for vertex storage (required to link index to frame).
    // Therefore we need to restart the search when we remove something.
    bool has_hydrogens = false;
    size_t skip_len = 0;
    do {
        auto verticies_iter = boost::vertices(graph_);
        has_hydrogens = false;
        for (auto v = verticies_iter.first + static_cast<std::ptrdiff_t>(skip_len);
                  v != verticies_iter.second; ++v) {
            auto index = boost::get(boost::vertex_index, graph_, *v);
            if (mol[index].atomic_number() == Element::H) {
                skip_len = *v;
                boost::clear_vertex(*v, graph_);
                boost::remove_vertex(*v, graph_);
                topology_.remove(index);
                positions_.erase(positions_.begin() + static_cast<std::ptrdiff_t>(index));
                for (const auto& at : atom_types_) {
                    at.second->remove_atom(index);
                }
                has_hydrogens = true;
                break;
            }
        }
    } while (has_hydrogens);
}

AtomVertex Molecule::add_atom(Element::Symbol n_atom, const Eigen::Vector3d& pos) {
    auto desc = boost::add_vertex(n_atom, graph_);
    positions_.push_back(pos);
    topology_.add_atom(chemfiles::Atom(Element::Name[n_atom]));
    for (const auto& at : atom_types_) {
        at.second->add_atom(desc);
    }
    return {this, desc};
}

BondEdge Molecule::add_bond(size_t idx1, size_t idx2, Bond::Order order) {
    auto new_edge = boost::add_edge(idx1, idx2, order, graph_);
    if (!new_edge.second) {
        throw std::runtime_error("Could not add bond between: " +
                                 std::to_string(idx1) + " and " +
                                 std::to_string(idx2));
    }
    topology_.add_bond(idx1, idx2, static_cast<chemfiles::Bond::BondOrder>(order));
    for (const auto& at : atom_types_) {
        at.second->add_bond(idx1, idx2, order);
    }
    return {this, new_edge.first};
}

static Eigen::Vector3d perpendicular(const Eigen::Vector3d& other) {
    Vector3d res(0.0, 0.0, 0.0);
    if (other[0] != 0.0) {
        if (other[1] != 0.0) {
            res[1] = -1 * other[0];
            res[0] = other[1];
        } else if (other[2] != 0.0) {
            res[2] = -1 * other[0];
            res[0] = other[2];
        } else {
            res[1] = 1;
        }
    } else if (other[1] != 0.0) {
        if (other[2] != 0.0) {
            res[2] = -1 * other[1];
            res[1] = other[2];
        } else {
            res[0] = 1;
        }
    } else if (other[2] != 0.0) {
        res[0] = 1;
    }
    res.normalize();
    return res;
}

// This code is largely based off of RDkit's code used to generate hydrogen
// locations. The original version is availible here:
// https://github.com/rdkit/rdkit/blob/95eeec7b3424f482416aa0c85b77fc73ab3008c1/Code/GraphMol/AddHs.cpp#L45

AtomVertex Molecule::add_atom_to(Element::Symbol n_atom, size_t index) {

    auto av = (*this)[index];

    Vector3d dirVect(0, 0, 0);

    Vector3d heavyPos = av.position();

    Vector3d perpVect, rotnAxis, nbrPerp;
    Vector3d nbr1Vect, nbr2Vect, nbr3Vect;

    auto dimensional = dimensionality(positions_);

    // All the coordinates are the same, let's ignore this case
    if (dimensional == 0 && av.degree() >= 1) {
        auto h_atom = add_atom(n_atom, heavyPos);
        add_bond(index, h_atom);
        return h_atom;
    }

    auto hybrid = atomtype()->hybridization(index);

    if (hybrid == Hybridization::UNKNOWN || hybrid == Hybridization::FORCED) {
        throw std::runtime_error("Cannot add atom to UNKNOWN or FORCED hybridizations");
    }

    // Spoof SP2 for planar nitrogens so that atoms are added in the same plane
    // and we can prevent the loss of conjugation
    if (av.atomic_number() == Element::N && hybrid == Hybridization::SP3 &&
        atomtype()->is_planar(index)) {
        hybrid = Hybridization::SP2;
    }

    bool is3D = dimensional == 3;

    double bondLength =
        is3D ?
            Element::CovalentRadius[n_atom] +
            Element::CovalentRadius[av.atomic_number()] :
            1.0;

    if (is3D && av.atomic_number() == Element::C && hybrid == Hybridization::SP2) {
        bondLength += 0.04;
    }

    if (is3D && av.atomic_number() == Element::C && hybrid == Hybridization::SP3) {
        bondLength += 0.07;
    }

    switch (av.degree()) {
    case 0: // No other atom neighbors, add position in the z-direction
        dirVect[2] = 1;
        break;

    case 1: // One Neighbor present
        // get a normalized vector pointing away from the neighbor:
        nbr1Vect = av[0].position() - heavyPos;

        nbr1Vect.normalize();
        nbr1Vect *= -1;

        // nbr1Vect points away from the other atom, add new H accordingly:
        switch (hybrid) {
        case Hybridization::SP3:
            // get a perpendicular to nbr1Vect:
            perpVect = perpendicular(nbr1Vect);
            if (!is3D) {
                perpVect[0] = 0.0;
                perpVect[1] = 0.0;
                perpVect[2] = 1.0;
            }
            // and move off it:
            dirVect = Eigen::AngleAxisd((180 - 109.471) * M_PI / 180.0, perpVect) * nbr1Vect;
            break;
        case Hybridization::SP2:
            // default position is to just take an arbitrary perpendicular:
            perpVect = perpendicular(nbr1Vect);
            if (av[0].degree() > 1) {
                // can we use the neighboring atom to establish a perpendicular?
                auto nbrBond = *(av.bonds().begin()); // Only one bond, so this has to be the one!
                if (nbrBond.order() == Bond::AROMATIC ||
                    nbrBond.order() == Bond::DOUBLE) {
                    for (auto nbr2 : av[0].neighbors()) {
                        if (nbr2 != av) {
                            nbr2Vect = nbr2.position() - av[0].position();
                            nbr2Vect.normalize();
                            break;
                        }
                    }
                    perpVect = nbr2Vect.cross(nbr1Vect);
                }
            }
            perpVect.normalize();
            // rotate the nbr1Vect 60 degrees about perpVect and we're done:
            dirVect = Eigen::AngleAxisd(60.0 * M_PI / 180.0, perpVect) * nbr1Vect;
            break;
        case Hybridization::SP:
            // just lay the H along the vector:
            dirVect = nbr1Vect;
            break;
        default:
            // FIX: handle other hybridizations
            // for now, just lay the H along the vector:
            dirVect = nbr1Vect;
        }
        break;
    case 2: // Two other neighbors:
        // start along the average of the two vectors:
        nbr1Vect = heavyPos - av[0].position();
        nbr2Vect = heavyPos - av[1].position();

        nbr1Vect.normalize();
        nbr2Vect.normalize();
        dirVect = nbr1Vect + nbr2Vect;

        dirVect.normalize();
        if (is3D) {
            switch (hybrid) {
            case Hybridization::SP3:
                // get the perpendicular to the neighbors:
                nbrPerp = nbr1Vect.cross(nbr2Vect);
                // and the perpendicular to that:
                rotnAxis = nbrPerp.cross(dirVect);
                // and then rotate about that:
                rotnAxis.normalize();
                dirVect = Eigen::AngleAxisd((109.471 / 2) * M_PI / 180., rotnAxis) * dirVect;
                break;
            case Hybridization::SP2:
                // don't need to do anything here, the H atom goes right on the direction vector
                break;
            case Hybridization::SP:
                // Can't add an atom here, the bond is saturated!
                throw std::runtime_error("Cannot add a third atom to SP atoms!");
                break;
            default:
                // FIX: handle other hybridizations
                // for now, just lay the H along the neighbor vector;
                break;
            }
        }
        // don't need to do anything here, the H atom goes right on the direction vector
        break;
    case 3: // Three other neighbors:

        if (hybrid == Hybridization::SP2 || hybrid == Hybridization::SP) {
            throw std::runtime_error("Cannot add a fourth atom to planar nitrogens or SP/SP2 atoms!");
        }

        // use the average of the three vectors:
        nbr1Vect = heavyPos - av[0].position();
        nbr2Vect = heavyPos - av[1].position();
        nbr3Vect = heavyPos - av[2].position();
        nbr1Vect.normalize();
        nbr2Vect.normalize();
        nbr3Vect.normalize();

        // if three neighboring atoms are more or less planar, this
        // is going to be in a quasi-random (but almost definitely bad)
        // direction...
        if (is3D) {
            dirVect = nbr1Vect + nbr2Vect + nbr3Vect;
            dirVect.normalize();
        } else {
            // we're in flatland
            // We're in a 2D conformation, put the H between the two neighbors
            // that have the widest angle between them:
            double minDot = nbr1Vect.dot(nbr2Vect);
            dirVect = nbr1Vect + nbr2Vect;
            if (nbr2Vect.dot(nbr3Vect) < minDot) {
                nbr2Vect.dot(nbr3Vect);
                dirVect = nbr2Vect + nbr3Vect;
            }
            if (nbr1Vect.dot(nbr3Vect) < minDot) {
                nbr1Vect.dot(nbr3Vect);
                dirVect = nbr1Vect + nbr3Vect;
            }
            dirVect *= -1;
        }
        break;
    case 4:
        if (hybrid == Hybridization::SP3 ||
            hybrid == Hybridization::SP2 ||
            hybrid == Hybridization::SP) {
            throw std::runtime_error("Cannot add a fifth atom to SP, SP2, or SP3 atoms!");
        }

    default: // Give up!
        break;
    }

    dirVect.normalize();
    auto h_atom = add_atom(n_atom, heavyPos + dirVect * bondLength);
    add_bond(av, h_atom);

    const auto& res = topology_.residue_for_atom(av);
    if (res) {
        chemfiles::Residue res2(res->name());
        res2.add_atom(h_atom);
        auto neigh_name = av.name();
        neigh_name[0] = 'H';
        topology_.add_residue(res2);
        topology_[h_atom].set_name(neigh_name);
    }

    return h_atom;
}

size_t Molecule::add_hydrogens() {
    bool hydrogens_to_add = false;
    size_t hydrogens_added = 0;
    do {
        hydrogens_to_add = false;
        for (auto atom : *this) {
            if (atom.implicit_hydrogens() < 1 || atom.atomic_number() == Element::H) {
                continue;
            }
            hydrogens_to_add = true;
            ++hydrogens_added;
            add_atom_to(Element::H, atom);
            break;
        }
    } while (hydrogens_to_add);

    return hydrogens_added;
}


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2015 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_python_interface.hh"
#include "graph_filtering.hh"
#include "graph.hh"
#include "graph_selectors.hh"
#include "graph_properties.hh"

#include <boost/mpl/push_back.hpp>
#include <boost/python.hpp>

#include "graph_community.hh"

using namespace std;
using namespace boost;

using namespace graph_tool;

void community_structure(GraphInterface& g, double gamma, string corr_name,
                         size_t n_iter, double Tmin, double Tmax, size_t Nspins,
                         rng_t& rng, bool verbose, string history_file,
                         boost::any weight, boost::any property)
{
    typedef property_map_types::apply<boost::mpl::vector<int32_t,int64_t>,
                                      GraphInterface::vertex_index_map_t,
                                      boost::mpl::bool_<false> >::type
        allowed_spin_properties;

    if (!belongs<allowed_spin_properties>()(property))
        throw ValueException("vertex property is not of integer type int32_t "
                             "or int64_t");

    typedef DynamicPropertyMapWrap<double,GraphInterface::edge_t> weight_map_t;
    typedef ConstantPropertyMap<double,GraphInterface::edge_t> no_weight_map_t;
    typedef boost::mpl::vector<weight_map_t,no_weight_map_t> weight_properties;

    if (weight.empty())
        weight = no_weight_map_t(1.0);
    else
        weight = weight_map_t(weight, edge_scalar_properties());

    comm_corr_t corr;
    if (corr_name ==  "erdos")
        corr = ERDOS_REYNI;
    else if (corr_name == "uncorrelated")
        corr = UNCORRELATED;
    else if (corr_name == "correlated")
        corr = CORRELATED;
    else
        throw ValueException("invalid correlation type: " + corr_name);

    run_action<graph_tool::detail::never_directed>()
        (g, std::bind(get_communities_selector(corr, g.GetVertexIndex()),
                      placeholders::_1, placeholders::_2, placeholders::_3, gamma, n_iter,
                      make_pair(Tmin, Tmax), Nspins,
                      std::ref(rng), make_pair(verbose,history_file)),
         weight_properties(), allowed_spin_properties())
        (weight, property);
}

double modularity(GraphInterface& g, boost::any weight, boost::any property)
{
    double modularity = 0;

    typedef ConstantPropertyMap<int32_t,GraphInterface::edge_t> weight_map_t;
    typedef boost::mpl::push_back<edge_scalar_properties, weight_map_t>::type
        edge_props_t;

    if(weight.empty())
        weight = weight_map_t(1);

    run_action<graph_tool::detail::never_directed>()
        (g, std::bind(get_modularity(), placeholders::_1, placeholders::_2,
                      placeholders::_3, std::ref(modularity)),
         edge_props_t(), vertex_scalar_properties())
        (weight, property);
    return modularity;
}

using namespace boost::python;

extern void community_network(GraphInterface& gi, GraphInterface& cgi,
                              boost::any community_property,
                              boost::any condensed_community_property,
                              boost::any vertex_count, boost::any edge_count,
                              boost::any vweight, boost::any eweight,
                              bool self_loops, bool parallel_edges);

void community_network_vavg(GraphInterface& gi, GraphInterface& cgi,
                            boost::any community_property,
                            boost::any condensed_community_property,
                            boost::any vweight,
                            boost::python::list avprops);

void community_network_eavg(GraphInterface& gi, GraphInterface& cgi,
                            boost::any community_property,
                            boost::any condensed_community_property,
                            boost::any eweight,
                            boost::python::list aeprops,
                            bool self_loops);

extern void export_blockmodel();
extern void export_blockmodel_overlap();
extern void export_blockmodel_covariate();

BOOST_PYTHON_MODULE(libgraph_tool_community)
{
    def("community_structure", &community_structure);
    def("modularity", &modularity);
    def("community_network", &community_network);
    def("community_network_vavg", &community_network_vavg);
    def("community_network_eavg", &community_network_eavg);

    export_blockmodel();
    export_blockmodel_overlap();
    export_blockmodel_covariate();
}