// Copyright (C) 2006 The Trustees of Indiana University.

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

//  Authors: Nick Edmonds
//           Andrew Lumsdaine

// A test of the distributed compressed sparse row graph type
#include <boost/graph/use_mpi.hpp>
#include <boost/config.hpp>
#include <boost/throw_exception.hpp>
#include <boost/graph/distributed/compressed_sparse_row_graph.hpp>
#include <boost/graph/distributed/mpi_process_group.hpp>
#include <boost/graph/distributed/concepts.hpp>

#include <boost/graph/erdos_renyi_generator.hpp>
#include <boost/graph/small_world_generator.hpp>
#include <boost/graph/rmat_graph_generator.hpp>

#include <boost/graph/breadth_first_search.hpp>
#include <boost/graph/depth_first_search.hpp>
#include <boost/graph/distributed/delta_stepping_shortest_paths.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/distributed/page_rank.hpp>
#include <boost/graph/distributed/boman_et_al_graph_coloring.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/graph/strong_components.hpp>
#include <boost/graph/distributed/betweenness_centrality.hpp>
#include <boost/graph/distributed/dehne_gotz_min_spanning_tree.hpp>
#include <boost/graph/distributed/st_connected.hpp>

#if 0 // Contains internal AdjList types not present in CSR graph
#  include <boost/graph/distributed/connected_components_parallel_search.hpp>
#endif

#include <boost/graph/distributed/vertex_list_adaptor.hpp> // Needed for MST

#include <boost/random/linear_congruential.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/property_map/vector_property_map.hpp>
#include <boost/core/lightweight_test.hpp>

#ifdef BOOST_NO_EXCEPTIONS
void
boost::throw_exception(std::exception const& ex)
{
    std::cout << ex.what() << std::endl;
    abort();
}
#endif


/****************************************************************************
 * Edge weight generator iterator                                           *
 ****************************************************************************/
template<typename F, typename RandomGenerator>
class generator_iterator
{
public:
  typedef std::input_iterator_tag iterator_category;
  typedef typename F::result_type value_type;
  typedef const value_type&       reference;
  typedef const value_type*       pointer;
  typedef void                    difference_type;

  explicit 
  generator_iterator(RandomGenerator& gen, const F& f = F()) 
    : f(f), gen(&gen) 
  { 
    value = this->f(gen); 
  }

  reference operator*() const  { return value; }
  pointer   operator->() const { return &value; }

  generator_iterator& operator++()
  {
    value = f(*gen);
    return *this;
  }

  generator_iterator operator++(int)
  {
    generator_iterator temp(*this);
    ++(*this);
    return temp;
  }

  bool operator==(const generator_iterator& other) const
  { return f == other.f; }

  bool operator!=(const generator_iterator& other) const
  { return !(*this == other); }

private:
  F f;
  RandomGenerator* gen;
  value_type value;
};

template<typename F, typename RandomGenerator>
inline generator_iterator<F, RandomGenerator> 
make_generator_iterator( RandomGenerator& gen, const F& f)
{ return generator_iterator<F, RandomGenerator>(gen, f); }


/****************************************************************************
 * Printing DFS Visitor                                                     *
 ****************************************************************************/

struct printing_dfs_visitor
{
  template<typename Vertex, typename Graph>
  void initialize_vertex(Vertex v, const Graph& g)
  {
    vertex_event("initialize_vertex", v, g);
  }

  template<typename Vertex, typename Graph>
  void start_vertex(Vertex v, const Graph& g)
  {
    vertex_event("start_vertex", v, g);
  }

  template<typename Vertex, typename Graph>
  void discover_vertex(Vertex v, const Graph& g)
  {
    vertex_event("discover_vertex", v, g);
  }

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

  template<typename Edge, typename Graph>
  void tree_edge(Edge e, const Graph& g)
  {
    edge_event("tree_edge", e, g);
  }

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

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

  template<typename Vertex, typename Graph>
  void finish_vertex(Vertex v, const Graph& g)
  {
    vertex_event("finish_vertex", v, g);
  }

private:
  template<typename Vertex, typename Graph>
  void vertex_event(const char* name, Vertex v, const Graph& g)
  {
    std::cerr << "#" << process_id(g.process_group()) << ": " << name << "("
              << get_vertex_name(v, g) << ": " << local(v) << "@" << owner(v)
              << ")\n";
  }

  template<typename Edge, typename Graph>
  void edge_event(const char* name, Edge e, const Graph& g)
  {
    std::cerr << "#" << process_id(g.process_group()) << ": " << name << "("
              << get_vertex_name(source(e, g), g) << ": "
              << local(source(e, g)) << "@" << owner(source(e, g)) << ", "
              << get_vertex_name(target(e, g), g) << ": "
              << local(target(e, g)) << "@" << owner(target(e, g)) << ")\n";
  }

};

using namespace boost;
using boost::graph::distributed::mpi_process_group;

typedef int weight_type;

struct WeightedEdge {
  WeightedEdge(weight_type weight = 0) : weight(weight) { }
  
  weight_type weight;

  template<typename Archiver>
  void serialize(Archiver& ar, const unsigned int /*version*/)
  {
    ar & weight;
  }
};

struct VertexProperties {
  VertexProperties(int d = 0)
    : distance(d) { }

  int distance;

  template<typename Archiver>
  void serialize(Archiver& ar, const unsigned int /*version*/)
  {
    ar & distance;
  }
};

int main(int argc, char* argv[])
{
  mpi::environment env(argc, argv);

  typedef compressed_sparse_row_graph<directedS, VertexProperties, WeightedEdge, 
                                      no_property, distributedS<mpi_process_group> >
    Digraph;

  // Make sure we can build graphs using common graph generators
  typedef sorted_erdos_renyi_iterator<minstd_rand, Digraph> ERIter;
  typedef small_world_iterator<minstd_rand, Digraph> SWIter;
  typedef sorted_rmat_iterator<minstd_rand, Digraph> RMATIter;

  typedef graph_traits<Digraph>::edge_descriptor edge_descriptor;

  int n = 40;
  int k = 3;
  double prob = 0.1;
  int C = 10;
  double a = 0.5, b = 0.1, c = 0.25, d = 0.15;
  int iterations = 50;
  int num_colors = n / 10;
  int lookahead = C / 10;

  minstd_rand gen;

  // Directed Graphs
  Digraph g(ERIter(gen, n, prob), ERIter(), 
            make_generator_iterator(gen, uniform_int<int>(0, C)),
            n);
  Digraph g2(SWIter(gen, n, k, prob), SWIter(), n);
  Digraph g3(RMATIter(gen, n, size_t(n*n*prob), a, b, c, d), RMATIter(), n);

  // Test BFS
  breadth_first_search(g, vertex(0, g), visitor(bfs_visitor<>()));

  // Test SSSP Algorithms
  graph::distributed::delta_stepping_shortest_paths(g, 
                                                    vertex(0, g),
                                                    dummy_property_map(),
                                                    get(&VertexProperties::distance, g),
                                                    get(&WeightedEdge::weight, g));

  dijkstra_shortest_paths(g, vertex(0, g),
                          distance_map(get(&VertexProperties::distance, g)).
                          weight_map(get(&WeightedEdge::weight, g)));

  dijkstra_shortest_paths(g, vertex(0, g),
                          distance_map(get(&VertexProperties::distance, g)).
                          weight_map(get(&WeightedEdge::weight, g)).
                          lookahead(lookahead));

  // Test PageRank
  using boost::graph::n_iterations;

  std::vector<double> ranks(num_vertices(g));

  page_rank(g,
            make_iterator_property_map(ranks.begin(),
                                       get(boost::vertex_index, g)),
            n_iterations(iterations), 0.85, vertex(0, g));

  // Test Graph Coloring
  typedef property_map<Digraph, vertex_index_t>::type vertex_index_map;

  std::vector<int> colors_vec(num_vertices(g));
  iterator_property_map<int*, vertex_index_map> color(&colors_vec[0],
                                                      get(vertex_index, g));

  graph::boman_et_al_graph_coloring(g, color, num_colors);

  // Test DFS
  //
  // DFS requires an undirected graph, currently CSR graphs must be directed
#if 0
  std::vector<vertex_descriptor> parent(num_vertices(g));
  std::vector<vertex_descriptor> explore(num_vertices(g));

  boost::graph::tsin_depth_first_visit
    (g,
     vertex(0, g),
     printing_dfs_visitor(),
     color,
     make_iterator_property_map(parent.begin(), get(vertex_index, g)),
     make_iterator_property_map(explore.begin(), get(vertex_index, g)),
     get(vertex_index, g));
#endif

  // Test S-T Connected
  st_connected(g, vertex(0, g), vertex(1, g), color, get(vertex_owner, g));

  // Test Connected Components
  //
  // CC requires an undirected graph, currently CSR graphs must be directed
#if 0
  std::vector<int> local_components_vec(num_vertices(g));
  typedef iterator_property_map<std::vector<int>::iterator, 
                                vertex_index_map> ComponentMap;
  ComponentMap component(local_components_vec.begin(), get(vertex_index, g));

  assert(connected_components(g, component) == 
         connected_components_ps(g, component));
#endif

  // Test Betweenness Centrality
  //
  // Betweenness Centrality is broken at the moment
  typedef iterator_property_map<std::vector<int>::iterator, vertex_index_map> 
    CentralityMap;

  std::vector<int> centralityS(num_vertices(g), 0);
  CentralityMap centrality(centralityS.begin(), get(vertex_index, g));

  brandes_betweenness_centrality(g, centrality);

  //
  // Test MST
  //
  std::vector<edge_descriptor> mst_edges;

  dense_boruvka_minimum_spanning_tree(make_vertex_list_adaptor(g), 
                                      get(&WeightedEdge::weight, g),
                                      std::back_inserter(mst_edges));

  mst_edges.clear();
  merge_local_minimum_spanning_trees(make_vertex_list_adaptor(g), 
                                     get(&WeightedEdge::weight, g),
                                     std::back_inserter(mst_edges));

  mst_edges.clear();
  boruvka_then_merge(make_vertex_list_adaptor(g), 
                     get(&WeightedEdge::weight, g),
                     std::back_inserter(mst_edges));

  mst_edges.clear();
  boruvka_mixed_merge(make_vertex_list_adaptor(g), 
                      get(&WeightedEdge::weight, g),
                      std::back_inserter(mst_edges));

  // Test Strong Components
  //
  // Can't build reverse graph of a CSR graph
#if 0
  std::vector<int> local_components_vec(num_vertices(g));
  typedef iterator_property_map<std::vector<int>::iterator, 
                                vertex_index_map> ComponentMap;
  ComponentMap component(local_components_vec.begin(), get(vertex_index, g));

  int num_components = strong_components(g, component);
#endif

  std::ofstream out("dcsr.dot");
  write_graphviz(out, g);

  return boost::report_errors();
}
