// Copyright (C) 2005, 2006 Douglas Gregor.

// 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)
#include <boost/mpi/communicator.hpp>
#include <boost/mpi/group.hpp>
#include <boost/mpi/intercommunicator.hpp>
#include <boost/mpi/graph_communicator.hpp>
#include <boost/mpi/skeleton_and_content.hpp>
#include <boost/mpi/detail/point_to_point.hpp>

namespace boost { namespace mpi {

/***************************************************************************
 * status                                                                  *
 ***************************************************************************/
bool status::cancelled() const
{
  int flag = 0;
  BOOST_MPI_CHECK_RESULT(MPI_Test_cancelled, (&m_status, &flag));
  return flag != 0;
}

/***************************************************************************
 * communicator                                                            *
 ***************************************************************************/

communicator::communicator()
{
  comm_ptr.reset(new MPI_Comm(MPI_COMM_WORLD));
}

communicator::communicator(const MPI_Comm& comm, comm_create_kind kind)
{
  if (comm == MPI_COMM_NULL)
    /* MPI_COMM_NULL indicates that the communicator is not usable. */
    return;

  switch (kind) {
  case comm_duplicate:
    {
      MPI_Comm newcomm;
      BOOST_MPI_CHECK_RESULT(MPI_Comm_dup, (comm, &newcomm));
      comm_ptr.reset(new MPI_Comm(newcomm), comm_free());
      MPI_Errhandler_set(newcomm, MPI_ERRORS_RETURN);
      break;
    }

  case comm_take_ownership:
    comm_ptr.reset(new MPI_Comm(comm), comm_free());
    break;

  case comm_attach:
    comm_ptr.reset(new MPI_Comm(comm));
    break;
  }
}

communicator::communicator(const communicator& comm, 
                           const boost::mpi::group& subgroup)
{
  MPI_Comm newcomm;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_create, 
                         ((MPI_Comm)comm, (MPI_Group)subgroup, &newcomm));
  comm_ptr.reset(new MPI_Comm(newcomm), comm_free());
}

int communicator::size() const
{
  int size_;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_size, (MPI_Comm(*this), &size_));
  return size_;
}

int communicator::rank() const
{
  int rank_;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_rank, (MPI_Comm(*this), &rank_));
  return rank_;
}

boost::mpi::group communicator::group() const
{
  MPI_Group gr;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_group, ((MPI_Comm)*this, &gr));
  return boost::mpi::group(gr, /*adopt=*/true);
}

void communicator::send(int dest, int tag) const
{
  BOOST_MPI_CHECK_RESULT(MPI_Send,
                         (MPI_BOTTOM, 0, MPI_PACKED,
                          dest, tag, MPI_Comm(*this)));
}

status communicator::recv(int source, int tag) const
{
  status stat;
  BOOST_MPI_CHECK_RESULT(MPI_Recv,
                         (MPI_BOTTOM, 0, MPI_PACKED,
                          source, tag, MPI_Comm(*this), &stat.m_status));
  return stat;
}

optional<status> communicator::iprobe(int source, int tag) const
{
  typedef optional<status> result_type;

  status stat;
  int flag;
  BOOST_MPI_CHECK_RESULT(MPI_Iprobe,
                         (source, tag, MPI_Comm(*this), &flag,
                          &stat.m_status));
  if (flag) return stat;
  else return result_type();
}

status communicator::probe(int source, int tag) const
{
  typedef optional<status> result_type;

  status stat;
  BOOST_MPI_CHECK_RESULT(MPI_Probe,
                         (source, tag, MPI_Comm(*this), &stat.m_status));
  return stat;
}

void (communicator::barrier)() const
{
  BOOST_MPI_CHECK_RESULT(MPI_Barrier, (MPI_Comm(*this)));
}


communicator::operator MPI_Comm() const
{
  if (comm_ptr) return *comm_ptr;
  else return MPI_COMM_NULL;
}

communicator communicator::split(int color) const
{
  return split(color, rank());
}

communicator communicator::split(int color, int key) const
{
  MPI_Comm newcomm;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_split,
                         (MPI_Comm(*this), color, key, &newcomm));
  return communicator(newcomm, comm_take_ownership);
}

optional<intercommunicator> communicator::as_intercommunicator() const
{
  int flag;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_test_inter, ((MPI_Comm)*this, &flag));
  if (flag)
    return intercommunicator(comm_ptr);
  else
    return optional<intercommunicator>();
}

optional<graph_communicator> communicator::as_graph_communicator() const
{
  int status;
  BOOST_MPI_CHECK_RESULT(MPI_Topo_test, ((MPI_Comm)*this, &status));
  if (status == MPI_GRAPH)
    return graph_communicator(comm_ptr);
  else
    return optional<graph_communicator>();
}

bool communicator::has_cartesian_topology() const
{
  int status;
  BOOST_MPI_CHECK_RESULT(MPI_Topo_test, ((MPI_Comm)*this, &status));

  return status == MPI_CART;
}

void communicator::abort(int errcode) const
{
  BOOST_MPI_CHECK_RESULT(MPI_Abort, (MPI_Comm(*this), errcode));
}

/*************************************************************
 * archived send/recv                                     *
 *************************************************************/
template<>
void
communicator::send<packed_oarchive>(int dest, int tag,
                                    const packed_oarchive& ar) const
{
  detail::packed_archive_send(MPI_Comm(*this), dest, tag, ar);
}

template<>
void
communicator::send<packed_skeleton_oarchive>
  (int dest, int tag, const packed_skeleton_oarchive& ar) const
{
  this->send(dest, tag, ar.get_skeleton());
}

template<>
void communicator::send<content>(int dest, int tag, const content& c) const
{
  BOOST_MPI_CHECK_RESULT(MPI_Send,
                         (MPI_BOTTOM, 1, c.get_mpi_datatype(),
                          dest, tag, MPI_Comm(*this)));
}

template<>
status
communicator::recv<packed_iarchive>(int source, int tag,
                                    packed_iarchive& ar) const
{
  status stat;
  detail::packed_archive_recv(MPI_Comm(*this), source, tag, ar,
                              stat.m_status);
  return stat;
}

template<>
status
communicator::recv<packed_skeleton_iarchive>
  (int source, int tag, packed_skeleton_iarchive& ar) const
{
  return this->recv(source, tag, ar.get_skeleton());
}

template<>
status
communicator::recv<const content>(int source, int tag, const content& c) const
{
  status stat;
  BOOST_MPI_CHECK_RESULT(MPI_Recv,
                         (MPI_BOTTOM, 1, c.get_mpi_datatype(),
                          source, tag, MPI_Comm(*this), &stat.m_status));
  return stat;
}

/*************************************************************
 * non-blocking send/recv                                    *
 *************************************************************/
template<>
request
communicator::isend<packed_oarchive>(int dest, int tag,
                                     const packed_oarchive& ar) const
{
  request req;
  detail::packed_archive_isend(MPI_Comm(*this), dest, tag, ar,
                               &req.m_requests[0] ,2);
  return req;
}

template<>
request
communicator::isend<packed_skeleton_oarchive>
  (int dest, int tag, const packed_skeleton_oarchive& ar) const
{
  return this->isend(dest, tag, ar.get_skeleton());
}

template<>
request communicator::isend<content>(int dest, int tag, const content& c) const
{
  request req;
  BOOST_MPI_CHECK_RESULT(MPI_Isend,
                         (MPI_BOTTOM, 1, c.get_mpi_datatype(),
                          dest, tag, MPI_Comm(*this), &req.m_requests[0]));
  return req;
}

request communicator::isend(int dest, int tag) const
{
  request req;
  BOOST_MPI_CHECK_RESULT(MPI_Isend,
                         (MPI_BOTTOM, 0, MPI_PACKED,
                          dest, tag, MPI_Comm(*this), &req.m_requests[0]));
  return req;
}

template<>
request
communicator::irecv<packed_skeleton_iarchive>
  (int source, int tag, packed_skeleton_iarchive& ar) const
{
  return this->irecv(source, tag, ar.get_skeleton());
}

template<>
request
communicator::irecv<const content>(int source, int tag,
                                   const content& c) const
{
  request req;
  BOOST_MPI_CHECK_RESULT(MPI_Irecv,
                         (MPI_BOTTOM, 1, c.get_mpi_datatype(),
                          source, tag, MPI_Comm(*this), &req.m_requests[0]));
  return req;
}

request communicator::irecv(int source, int tag) const
{
  request req;
  BOOST_MPI_CHECK_RESULT(MPI_Irecv,
                         (MPI_BOTTOM, 0, MPI_PACKED,
                          source, tag, MPI_Comm(*this), &req.m_requests[0]));
  return req;
}

bool operator==(const communicator& comm1, const communicator& comm2)
{
  int result;
  BOOST_MPI_CHECK_RESULT(MPI_Comm_compare,
                         ((MPI_Comm)comm1, (MPI_Comm)comm2, &result));
  return result == MPI_IDENT;
}

} } // end namespace boost::mpi
