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

#ifndef BOOST_GRAPH_DISTRIBUTED_ADJLIST_SERIALIZATION_070925_HPP
#define BOOST_GRAPH_DISTRIBUTED_ADJLIST_SERIALIZATION_070925_HPP

#ifndef BOOST_GRAPH_USE_MPI
#error "Parallel BGL files should not be included unless <boost/graph/use_mpi.hpp> has been included"
#endif

# include <boost/assert.hpp>
# include <boost/lexical_cast.hpp>
# include <boost/foreach.hpp>
# include <boost/filesystem/path.hpp>
# include <boost/filesystem/operations.hpp>
# include <cctype>
# include <fstream>

namespace boost {

namespace detail { namespace parallel
{

  // Wraps a local descriptor, making it serializable. 
  template <class Local>
  struct serializable_local_descriptor
  {
      serializable_local_descriptor()
      {}

      serializable_local_descriptor(Local local)
        : local(local)
      {}

      operator Local const&() const
      {
          return local;
      }

      bool operator==(serializable_local_descriptor const& other) const
      {
          return local == other.local;
      }

      bool operator<(serializable_local_descriptor const& other) const
      {
          return local < other.local;
      }

      template <class Archive>
      void serialize(Archive& ar, const unsigned int /*version*/)
      {
          ar & unsafe_serialize(local);
      }

      Local local;
  };

  template <class Vertex, class Properties>
  struct pending_edge
  {
      pending_edge(
          Vertex source, Vertex target
        , Properties properties, void* property_ptr
      )
        : source(source)
        , target(target)
        , properties(properties)
        , property_ptr(property_ptr)
      {}

      Vertex source;
      Vertex target;
      Properties properties;
      void* property_ptr;
  };

  inline bool is_digit(char c)
  {
      return (bool)std::isdigit(c);
  }

  inline std::vector<int> 
      available_process_files(std::string const& filename)
      {
          if (! filesystem::exists(filename))
              return std::vector<int>();

          std::vector<int> result;

          for (filesystem::directory_iterator i(filename), end; i ! = end; ++i)
          {
              if (! filesystem::is_regular(*i))
                  boost::throw_exception(std::runtime_error("directory contains non-regular entries"));

              std::string process_name = i->path().filename().string();
              for (std::string::size_type i = 0; i < process_name.size(); ++i)
                if (! is_digit(process_name[i]))
                  boost::throw_exception(std::runtime_error("directory contains files with invalid names"));

              result.push_back(boost::lexical_cast<int>(process_name));
          }

          return result;
      }

  template <class Archive, class Tag, class T, class Base>
  void maybe_load_properties(
      Archive& ar, char const* name, property<Tag, T, Base>& properties)
  {
      ar >> serialization::make_nvp(name, get_property_value(properties, Tag()));
      maybe_load_properties(ar, name, static_cast<Base&>(properties));
  }

  template <class Archive>
  void maybe_load_properties(
      Archive&, char const*, no_property&)
  {}

  template <class Archive, typename Bundle>
  void maybe_load_properties(
      Archive& ar, char const* name, Bundle& bundle)
  {
    ar >> serialization::make_nvp(name, bundle);
    no_property prop;
    maybe_load_properties(ar, name, prop);
  }






  template <class Graph, class Archive, class VertexListS>
  struct graph_loader
  {
      typedef typename Graph::vertex_descriptor vertex_descriptor;
      typedef typename Graph::local_vertex_descriptor local_vertex_descriptor;
      typedef typename Graph::vertex_property_type vertex_property_type;
      typedef typename Graph::edge_descriptor edge_descriptor;
      typedef typename Graph::local_edge_descriptor local_edge_descriptor;
      typedef typename Graph::edge_property_type edge_property_type;
      typedef typename Graph::process_group_type process_group_type;
      typedef typename process_group_type::process_id_type process_id_type;
      typedef typename Graph::directed_selector directed_selector;
      typedef typename mpl::if_<
          is_same<VertexListS, defaultS>, vecS, VertexListS
      >::type vertex_list_selector;
      typedef pending_edge<vertex_descriptor, edge_property_type> 
          pending_edge_type; 
      typedef serializable_local_descriptor<local_vertex_descriptor>
          serializable_vertex_descriptor;

      graph_loader(Graph& g, Archive& ar)
        : m_g(g)
        , m_ar(ar)
        , m_pg(g.process_group())
        , m_requested_vertices(num_processes(m_pg))
        , m_remote_vertices(num_processes(m_pg))
        , m_property_ptrs(num_processes(m_pg))
      {
          g.clear();
          load_prefix();
          load_vertices();
          load_edges();
          ar >> make_nvp("distribution", m_g.distribution());
      }

  private:
      struct pending_in_edge
      {
          pending_in_edge(
              vertex_descriptor u, vertex_descriptor v, void* property_ptr
          )
            : u(u)
            , v(v)
            , property_ptr(property_ptr)
          {}

          vertex_descriptor u;
          vertex_descriptor v;
          void* property_ptr;
      };

      bool is_root() const
      {
          return process_id(m_pg) == 0;
      }

      template <class T>
      serialization::nvp<T> const make_nvp(char const* name, T& value) const
      {
          return serialization::nvp<T>(name, value);
      }

      void load_prefix();
      void load_vertices();

      template <class Anything>
      void maybe_load_and_store_local_vertex(Anything);
      void maybe_load_and_store_local_vertex(vecS);

      void load_edges();
      void load_in_edges(bidirectionalS);
      void load_in_edges(directedS);
      void add_pending_in_edge(
          vertex_descriptor u, vertex_descriptor v, void* property_ptr, vecS);
      template <class Anything>
      void add_pending_in_edge(
          vertex_descriptor u, vertex_descriptor v, void* property_ptr, Anything);
      template <class Anything>
      void add_edge(
          vertex_descriptor u, vertex_descriptor v
        , edge_property_type const& property, void* property_ptr, Anything);
      void add_edge(
          vertex_descriptor u, vertex_descriptor v
        , edge_property_type const& property, void* property_ptr, vecS);
      void add_remote_vertex_request(
          vertex_descriptor u, vertex_descriptor v, directedS);
      void add_remote_vertex_request(
          vertex_descriptor u, vertex_descriptor v, bidirectionalS);
      void add_in_edge(
          edge_descriptor const&, void*, directedS);
      void add_in_edge(
          edge_descriptor const& edge, void* old_property_ptr, bidirectionalS);

      void resolve_remote_vertices(directedS);
      void resolve_remote_vertices(bidirectionalS);
      vertex_descriptor resolve_remote_vertex(vertex_descriptor u) const;
      vertex_descriptor resolve_remote_vertex(vertex_descriptor u, vecS) const;
      template <class Anything>
      vertex_descriptor resolve_remote_vertex(vertex_descriptor u, Anything) const;

      void resolve_property_ptrs();

      void commit_pending_edges(vecS);
      template <class Anything>
      void commit_pending_edges(Anything);
      void commit_pending_in_edges(directedS);
      void commit_pending_in_edges(bidirectionalS);

      void* maybe_load_property_ptr(directedS) { return 0; }
      void* maybe_load_property_ptr(bidirectionalS);

      Graph& m_g;
      Archive& m_ar;
      process_group_type m_pg;

      std::vector<process_id_type> m_id_mapping;

      // Maps local vertices as loaded from the archive to
      // the ones actually added to the graph.  Only used 
      // when ! vecS.
      std::map<local_vertex_descriptor, local_vertex_descriptor> m_local_vertices;

      // This is the list of remote vertex descriptors that we
      // are going to receive from other processes.  This is
      // kept sorted so that we can determine the position of
      // the matching vertex descriptor in m_remote_vertices. 
      std::vector<std::vector<serializable_vertex_descriptor> > m_requested_vertices;

      // This is the list of remote vertex descriptors that
      // we send and receive from other processes. 
      std::vector<std::vector<serializable_vertex_descriptor> > m_remote_vertices;

      // ...
      std::vector<pending_edge_type> m_pending_edges;

      // The pending in-edges that will be added in the commit step, after
      // the remote vertex descriptors has been resolved.  Only used
      // when bidirectionalS and ! vecS.
      std::vector<pending_in_edge> m_pending_in_edges;

      std::vector<std::vector<unsafe_pair<void*,void*> > > m_property_ptrs;
  };

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::load_prefix()
  {
      typename process_group_type::process_size_type num_processes_;
      m_ar >> make_nvp("num_processes", num_processes_);

      if (num_processes_ ! = num_processes(m_pg))
          boost::throw_exception(std::runtime_error("number of processes mismatch"));

      process_id_type old_id;
      m_ar >> make_nvp("id", old_id);

      std::vector<typename Graph::distribution_type::size_type> mapping;
      m_ar >> make_nvp("mapping", mapping);

      // Fetch all the old id's from the other processes. 
      std::vector<process_id_type> old_ids;
      all_gather(m_pg, &old_id, &old_id+1, old_ids);

      m_id_mapping.resize(num_processes(m_pg), -1);

      for (process_id_type i = 0; i < num_processes(m_pg); ++i)
      {
# ifdef PBGL_SERIALIZE_DEBUG
          if (is_root())
              std::cout << i << " used to be " << old_ids[i] << "\n"; 
# endif
          BOOST_ASSERT(m_id_mapping[old_ids[i]] == -1);
          m_id_mapping[old_ids[i]] = i;
      }

      std::vector<typename Graph::distribution_type::size_type> new_mapping(
          mapping.size());

      for (int i = 0; i < num_processes(m_pg); ++i)
      {
          new_mapping[mapping[old_ids[i]]] = i;
      }

      m_g.distribution().assign_mapping(
          new_mapping.begin(), new_mapping.end());
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::load_vertices()
  {
      int V;
      m_ar >> BOOST_SERIALIZATION_NVP(V); 

# ifdef PBGL_SERIALIZE_DEBUG
      if (is_root())
          std::cout << "Loading vertices\n";
# endif

      for (int i = 0; i < V; ++i)
      {
          maybe_load_and_store_local_vertex(vertex_list_selector());
      }
  }

  template <class Graph, class Archive, class VertexListS>
  template <class Anything>
  void graph_loader<Graph, Archive, VertexListS>::maybe_load_and_store_local_vertex(Anything)
  {
      // Load the original vertex descriptor
      local_vertex_descriptor local;
      m_ar >> make_nvp("local", unsafe_serialize(local)); 

      // Load the properties
      vertex_property_type property;
      detail::parallel::maybe_load_properties(m_ar, "vertex_property",
                          property);

      // Add the vertex
      vertex_descriptor v(process_id(m_pg), add_vertex(property, m_g.base()));

      if (m_g.on_add_vertex)
        m_g.on_add_vertex(v, m_g);

      // Create the mapping from the "old" local descriptor to the new
      // local descriptor. 
      m_local_vertices[local] = v.local;
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::maybe_load_and_store_local_vertex(vecS)
  {
      // Load the properties
      vertex_property_type property;
      detail::parallel::maybe_load_properties(m_ar, "vertex_property",
                          property);

      // Add the vertex
      vertex_descriptor v(process_id(m_pg), 
                          add_vertex(m_g.build_vertex_property(property), 
                                     m_g.base()));

      if (m_g.on_add_vertex)
        m_g.on_add_vertex(v, m_g);
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::load_edges()
  {
      int E;
      m_ar >> BOOST_SERIALIZATION_NVP(E);

# ifdef PBGL_SERIALIZE_DEBUG
      if (is_root())
          std::cout << "Loading edges\n";
# endif

      for (int i = 0; i < E; ++i)
      {
          local_vertex_descriptor local_src;
          process_id_type target_owner;
          local_vertex_descriptor local_tgt;

          m_ar >> make_nvp("source", unsafe_serialize(local_src)); 
          m_ar >> make_nvp("target_owner", target_owner); 
          m_ar >> make_nvp("target", unsafe_serialize(local_tgt)); 

          process_id_type new_src_owner = process_id(m_pg);
          process_id_type new_tgt_owner = m_id_mapping[target_owner];

          vertex_descriptor source(new_src_owner, local_src);
          vertex_descriptor target(new_tgt_owner, local_tgt);

          edge_property_type properties;
          detail::parallel::maybe_load_properties(m_ar, "edge_property", properties);

          void* property_ptr = maybe_load_property_ptr(directed_selector());
          add_edge(source, target, properties, property_ptr, vertex_list_selector());
      }

      load_in_edges(directed_selector());
      commit_pending_edges(vertex_list_selector());
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::load_in_edges(bidirectionalS)
  {
      std::size_t I;
      m_ar >> BOOST_SERIALIZATION_NVP(I);

# ifdef PBGL_SERIALIZE_DEBUG
      if (is_root())
          std::cout << "Loading in-edges\n";
# endif

      for (int i = 0; i < I; ++i)
      {
          process_id_type src_owner;
          local_vertex_descriptor local_src;
          local_vertex_descriptor local_target;
          void* property_ptr;

          m_ar >> make_nvp("src_owner", src_owner);
          m_ar >> make_nvp("source", unsafe_serialize(local_src));
          m_ar >> make_nvp("target", unsafe_serialize(local_target));
          m_ar >> make_nvp("property_ptr", unsafe_serialize(property_ptr));

          src_owner = m_id_mapping[src_owner];

          vertex_descriptor u(src_owner, local_src);
          vertex_descriptor v(process_id(m_pg), local_target);

          add_pending_in_edge(u, v, property_ptr, vertex_list_selector());
      }
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::load_in_edges(directedS)
  {}

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::add_pending_in_edge(
      vertex_descriptor u, vertex_descriptor v, void* property_ptr, vecS)
  {
      m_pending_in_edges.push_back(pending_in_edge(u,v,property_ptr));
  }

  template <class Graph, class Archive, class VertexListS>
  template <class Anything>
  void graph_loader<Graph, Archive, VertexListS>::add_pending_in_edge(
      vertex_descriptor u, vertex_descriptor v, void* property_ptr, Anything)
  {
      // u and v represent the out-edge here, meaning v is local
      // to us, and u is always remote. 
      m_pending_in_edges.push_back(pending_in_edge(u,v,property_ptr));
      add_remote_vertex_request(v, u, bidirectionalS());
  }

  template <class Graph, class Archive, class VertexListS> 
  template <class Anything>
  void graph_loader<Graph, Archive, VertexListS>::add_edge(
      vertex_descriptor u, vertex_descriptor v
    , edge_property_type const& property, void* property_ptr, Anything)
  {
      m_pending_edges.push_back(pending_edge_type(u, v, property, property_ptr));
      add_remote_vertex_request(u, v, directed_selector());
  }

  template <class Graph, class Archive, class VertexListS> 
  void graph_loader<Graph, Archive, VertexListS>::add_remote_vertex_request(
      vertex_descriptor u, vertex_descriptor v, directedS)
  {
      // We have to request the remote vertex. 
      m_requested_vertices[owner(v)].push_back(local(v));
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::add_remote_vertex_request(
      vertex_descriptor u, vertex_descriptor v, bidirectionalS)
  {
      // If the edge spans to another process, we know
      // that that process has a matching in-edge, so
      // we can just send our vertex.  No requests
      // necessary. 
      if (owner(v) ! = m_g.processor())
      {
          m_remote_vertices[owner(v)].push_back(local(u));
          m_requested_vertices[owner(v)].push_back(local(v));
      }
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::add_edge(
      vertex_descriptor u, vertex_descriptor v
    , edge_property_type const& property, void* property_ptr, vecS)
  {
      std::pair<local_edge_descriptor, bool> inserted = 
          detail::parallel::add_local_edge(
              local(u), local(v)
            , m_g.build_edge_property(property), m_g.base());
      BOOST_ASSERT(inserted.second);
      put(edge_target_processor_id, m_g.base(), inserted.first, owner(v));

      edge_descriptor e(owner(u), owner(v), true, inserted.first);

      if (inserted.second && m_g.on_add_edge)
        m_g.on_add_edge(e, m_g);

      add_in_edge(e, property_ptr, directed_selector());
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::add_in_edge(
      edge_descriptor const&, void*, directedS)
  {}

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::add_in_edge(
      edge_descriptor const& edge, void* old_property_ptr, bidirectionalS)
  {
      if (owner(target(edge, m_g)) == m_g.processor())
      {
          detail::parallel::stored_in_edge<local_edge_descriptor>
              e(m_g.processor(), local(edge));
          boost::graph_detail::push(get(
              vertex_in_edges, m_g.base())[local(target(edge, m_g))], e);
      }
      else
      {
          // We send the (old,new) property pointer pair to
          // the remote process.  This could be optimized to
          // only send the new one -- the ordering can be
          // made implicit because the old pointer value is
          // stored on the remote process. 
          //
          // Doing that is a little bit more complicated, but
          // in case it turns out it's important we can do it. 
          void* property_ptr = local(edge).get_property();
          m_property_ptrs[owner(target(edge, m_g))].push_back(
              unsafe_pair<void*,void*>(old_property_ptr, property_ptr));
      }
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::resolve_property_ptrs()
  {
# ifdef PBGL_SERIALIZE_DEBUG
      if (is_root())
          std::cout << "Resolving property pointers\n";
# endif

      for (int i = 0; i < num_processes(m_pg); ++i)
      {
          std::sort(
              m_property_ptrs[i].begin(), m_property_ptrs[i].end());
      }

      boost::parallel::inplace_all_to_all(m_pg, m_property_ptrs);
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::resolve_remote_vertices(directedS)
  {
      for (int i = 0; i < num_processes(m_pg); ++i)
      {
          std::sort(m_requested_vertices[i].begin(), m_requested_vertices[i].end());
      }

      boost::parallel::inplace_all_to_all(
          m_pg, m_requested_vertices, m_remote_vertices);

      for (int i = 0; i < num_processes(m_pg); ++i)
      {
          BOOST_FOREACH(serializable_vertex_descriptor& u, m_remote_vertices[i])
          {
              u = m_local_vertices[u];
          }
      }

      boost::parallel::inplace_all_to_all(m_pg, m_remote_vertices);
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::resolve_remote_vertices(bidirectionalS)
  {
# ifdef PBGL_SERIALIZE_DEBUG
      if (is_root())
          std::cout << "Resolving remote vertices\n";
# endif

      for (int i = 0; i < num_processes(m_pg); ++i)
      {
          std::sort(m_requested_vertices[i].begin(), m_requested_vertices[i].end());
          std::sort(m_remote_vertices[i].begin(), m_remote_vertices[i].end());

          BOOST_FOREACH(serializable_vertex_descriptor& u, m_remote_vertices[i])
          {
              u = m_local_vertices[u];
          }
      }

      boost::parallel::inplace_all_to_all(m_pg, m_remote_vertices);

      for (int i = 0; i < num_processes(m_pg); ++i)
          BOOST_ASSERT(m_remote_vertices[i].size() == m_requested_vertices[i].size());
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::commit_pending_edges(vecS)
  {
      commit_pending_in_edges(directed_selector());
  }

  template <class Graph, class Archive, class VertexListS>
  template <class Anything>
  void graph_loader<Graph, Archive, VertexListS>::commit_pending_edges(Anything)
  {
      resolve_remote_vertices(directed_selector());

      BOOST_FOREACH(pending_edge_type const& e, m_pending_edges)
      {
          vertex_descriptor u = resolve_remote_vertex(e.source);
          vertex_descriptor v = resolve_remote_vertex(e.target);
          add_edge(u, v, e.properties, e.property_ptr, vecS());
      }

      commit_pending_in_edges(directed_selector());
  }

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::commit_pending_in_edges(directedS)
  {}

  template <class Graph, class Archive, class VertexListS>
  void graph_loader<Graph, Archive, VertexListS>::commit_pending_in_edges(bidirectionalS)
  {
      resolve_property_ptrs();

      BOOST_FOREACH(pending_in_edge const& e, m_pending_in_edges)
      {
          vertex_descriptor u = resolve_remote_vertex(e.u, vertex_list_selector());
          vertex_descriptor v = resolve_remote_vertex(e.v, vertex_list_selector());

          typedef detail::parallel::stored_in_edge<local_edge_descriptor> stored_edge;

          std::vector<unsafe_pair<void*,void*> >::iterator i = std::lower_bound(
              m_property_ptrs[owner(u)].begin()
            , m_property_ptrs[owner(u)].end()
            , unsafe_pair<void*,void*>(e.property_ptr, 0)
          );

          if (i == m_property_ptrs[owner(u)].end()
              || i->first ! = e.property_ptr)
          {
              BOOST_ASSERT(false);
          }

          local_edge_descriptor local_edge(local(u), local(v), i->second);
          stored_edge edge(owner(u), local_edge);
          boost::graph_detail::push(
              get(vertex_in_edges, m_g.base())[local(v)], edge);
      }
  }

  template <class Graph, class Archive, class VertexListS>
  typename graph_loader<Graph, Archive, VertexListS>::vertex_descriptor 
  graph_loader<Graph, Archive, VertexListS>::resolve_remote_vertex(
      vertex_descriptor u) const
  {
      if (owner(u) == process_id(m_pg))
      { 
          return vertex_descriptor(
              process_id(m_pg), m_local_vertices.find(local(u))->second);
      }

      typename std::vector<serializable_vertex_descriptor>::const_iterator 
          i = std::lower_bound(
              m_requested_vertices[owner(u)].begin()
            , m_requested_vertices[owner(u)].end()
            , serializable_vertex_descriptor(local(u))
          );

      if (i == m_requested_vertices[owner(u)].end()
          || *i ! = local(u))
      {
          BOOST_ASSERT(false);
      }

      local_vertex_descriptor local =
          m_remote_vertices[owner(u)][m_requested_vertices[owner(u)].end() - i];
      return vertex_descriptor(owner(u), local);
  }

  template <class Graph, class Archive, class VertexListS>
  typename graph_loader<Graph, Archive, VertexListS>::vertex_descriptor 
  graph_loader<Graph, Archive, VertexListS>::resolve_remote_vertex(
      vertex_descriptor u, vecS) const
  {
      return u;
  }

  template <class Graph, class Archive, class VertexListS>
  template <class Anything>
  typename graph_loader<Graph, Archive, VertexListS>::vertex_descriptor 
  graph_loader<Graph, Archive, VertexListS>::resolve_remote_vertex(
      vertex_descriptor u, Anything) const
  {
      return resolve_remote_vertex(u);
  }

  template <class Graph, class Archive, class VertexListS>
  void* 
  graph_loader<Graph, Archive, VertexListS>::maybe_load_property_ptr(bidirectionalS)
  {
      void* ptr;
      m_ar >> make_nvp("property_ptr", unsafe_serialize(ptr));
      return ptr;
  }

template <class Archive, class D>
void maybe_save_local_descriptor(Archive& ar, D const&, vecS)
{}

template <class Archive, class D, class NotVecS>
void maybe_save_local_descriptor(Archive& ar, D const& d, NotVecS)
{
    ar << serialization::make_nvp(
        "local", unsafe_serialize(const_cast<D&>(d)));
}

template <class Archive>
void maybe_save_properties(
    Archive&, char const*, no_property const&)
{}

template <class Archive, class Tag, class T, class Base>
void maybe_save_properties(
    Archive& ar, char const* name, property<Tag, T, Base> const& properties)
{
    ar & serialization::make_nvp(name, get_property_value(properties, Tag()));
    maybe_save_properties(ar, name, static_cast<Base const&>(properties));
}

template <class Archive, class Graph>
void save_in_edges(Archive& ar, Graph const& g, directedS)
{}

// We need to save the edges in the base edge
// list, and the in_edges that are stored in the
// vertex_in_edges vertex property. 
template <class Archive, class Graph>
void save_in_edges(Archive& ar, Graph const& g, bidirectionalS)
{
    typedef typename Graph::process_group_type
        process_group_type;
    typedef typename process_group_type::process_id_type
        process_id_type;
    typedef typename graph_traits<
        Graph>::vertex_descriptor vertex_descriptor;
    typedef typename vertex_descriptor::local_descriptor_type 
        local_vertex_descriptor;
    typedef typename graph_traits<
        Graph>::edge_descriptor edge_descriptor;

    process_id_type id = g.processor();

    std::vector<edge_descriptor> saved_in_edges;

    BGL_FORALL_VERTICES_T(v, g, Graph) 
    {
        BOOST_FOREACH(edge_descriptor const& e, in_edges(v, g))
        {
            // Only save the in_edges that isn't owned by this process. 
            if (owner(e) == id)
                continue;

            saved_in_edges.push_back(e);
        }
    }

    std::size_t I = saved_in_edges.size();
    ar << BOOST_SERIALIZATION_NVP(I);

    BOOST_FOREACH(edge_descriptor const& e, saved_in_edges)
    {
        process_id_type src_owner = owner(source(e,g));
        local_vertex_descriptor local_src = local(source(e,g));
        local_vertex_descriptor local_target = local(target(e,g));
        void* property_ptr = local(e).get_property();

        using serialization::make_nvp;

        ar << make_nvp("src_owner", src_owner);
        ar << make_nvp("source", unsafe_serialize(local_src));
        ar << make_nvp("target", unsafe_serialize(local_target));
        ar << make_nvp("property_ptr", unsafe_serialize(property_ptr));
    }
}

template <class Archive, class Edge>
void maybe_save_property_ptr(Archive&, Edge const&, directedS)
{}

template <class Archive, class Edge>
void maybe_save_property_ptr(Archive& ar, Edge const& e, bidirectionalS)
{
    void* ptr = local(e).get_property();
    ar << serialization::make_nvp("property_ptr", unsafe_serialize(ptr));
}

template <class Archive, class Graph, class DirectedS>
void save_edges(Archive& ar, Graph const& g, DirectedS)
{
    typedef typename Graph::process_group_type
        process_group_type;
    typedef typename process_group_type::process_id_type
        process_id_type;
    typedef typename graph_traits<
        Graph>::vertex_descriptor vertex_descriptor;

    typedef typename Graph::edge_property_type edge_property_type;

    int E = num_edges(g);
    ar << BOOST_SERIALIZATION_NVP(E);

    // For *directed* graphs, we can just save
    // the edge list and be done. 
    //
    // For *bidirectional* graphs, we need to also
    // save the "vertex_in_edges" property map,
    // because it might contain in-edges that
    // are not locally owned. 
    BGL_FORALL_EDGES_T(e, g, Graph) 
    {
        vertex_descriptor src(source(e, g));
        vertex_descriptor tgt(target(e, g));

        typename vertex_descriptor::local_descriptor_type
            local_u(local(src));
        typename vertex_descriptor::local_descriptor_type
            local_v(local(tgt));

        process_id_type target_owner = owner(tgt);

        using serialization::make_nvp;

        ar << make_nvp("source", unsafe_serialize(local_u)); 
        ar << make_nvp("target_owner", target_owner); 
        ar << make_nvp("target", unsafe_serialize(local_v)); 

        maybe_save_properties(
            ar, "edge_property"
          , static_cast<edge_property_type const&>(get(edge_all_t(), g, e))
        );

        maybe_save_property_ptr(ar, e, DirectedS());
    }

    save_in_edges(ar, g, DirectedS());
}

}} // namespace detail::parallel

template <PBGL_DISTRIB_ADJLIST_TEMPLATE_PARMS>
template <class IStreamConstructibleArchive>
void PBGL_DISTRIB_ADJLIST_TYPE::load(std::string const& filename)
{
    process_group_type pg = process_group();
    process_id_type id = process_id(pg);

    synchronize(pg);

    std::vector<int> disk_files = detail::parallel::available_process_files(filename);
    std::sort(disk_files.begin(), disk_files.end());

    // Negotiate which process gets which file.  Serialized. 
    std::vector<int> consumed_files;
    int picked_file = -1;

    if (id > 0)
        receive_oob(pg, id-1, 0, consumed_files);

    std::sort(consumed_files.begin(), consumed_files.end());
    std::vector<int> available_files;
    std::set_difference(
        disk_files.begin(), disk_files.end()
      , consumed_files.begin(), consumed_files.end()
      , std::back_inserter(available_files)
    );

    if (available_files.empty())
        boost::throw_exception(std::runtime_error("no file available"));

    // back() used for debug purposes.  Making sure the
    // ranks are shuffled. 
    picked_file = available_files.back();

# ifdef PBGL_SERIALIZE_DEBUG
    std::cout << id << " picked " << picked_file << "\n";
# endif

    consumed_files.push_back(picked_file);

    if (id < num_processes(pg) - 1)
        send_oob(pg, id+1, 0, consumed_files);

    std::string local_filename = filename + "/" + 
        lexical_cast<std::string>(picked_file);

    std::ifstream in(local_filename.c_str(), std::ios_base::binary);
    IStreamConstructibleArchive ar(in);

    detail::parallel::graph_loader<
        graph_type, IStreamConstructibleArchive, InVertexListS
    > loader(*this, ar);

# ifdef PBGL_SERIALIZE_DEBUG
    std::cout << "Process " << id << " done loading.\n";
# endif

    synchronize(pg);
}

template <PBGL_DISTRIB_ADJLIST_TEMPLATE_PARMS>
template <class OStreamConstructibleArchive>
void PBGL_DISTRIB_ADJLIST_TYPE::save(std::string const& filename) const
{
    typedef typename config_type::VertexListS vertex_list_selector;

    process_group_type pg = process_group();
    process_id_type id = process_id(pg);

    if (filesystem::exists(filename) && ! filesystem::is_directory(filename))
        boost::throw_exception(std::runtime_error("entry exists, but is not a directory"));

    filesystem::remove_all(filename);
    filesystem::create_directory(filename);

    synchronize(pg);

    std::string local_filename = filename + "/" + 
        lexical_cast<std::string>(id);

    std::ofstream out(local_filename.c_str(), std::ios_base::binary);
    OStreamConstructibleArchive ar(out);

    using serialization::make_nvp;

    typename process_group_type::process_size_type num_processes_ = num_processes(pg);
    ar << make_nvp("num_processes", num_processes_);
    ar << BOOST_SERIALIZATION_NVP(id);
    ar << make_nvp("mapping", this->distribution().mapping());

    int V = num_vertices(*this);
    ar << BOOST_SERIALIZATION_NVP(V);

    BGL_FORALL_VERTICES_T(v, *this, graph_type)
    {
        local_vertex_descriptor local_descriptor(local(v));
        detail::parallel::maybe_save_local_descriptor(
            ar, local_descriptor, vertex_list_selector());
        detail::parallel::maybe_save_properties(
            ar, "vertex_property"
          , static_cast<vertex_property_type const&>(get(vertex_all_t(), *this, v))
        );
    }

    detail::parallel::save_edges(ar, *this, directed_selector());

    ar << make_nvp("distribution", this->distribution());
}

} // namespace boost

#endif // BOOST_GRAPH_DISTRIBUTED_ADJLIST_SERIALIZATION_070925_HPP

