
//          Copyright Alain Miniussi 2014.
// Distributed under 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: Alain Miniussi

#include <algorithm>
#include <cassert>

#include <boost/mpi/cartesian_communicator.hpp>

namespace boost { namespace mpi {

namespace {
  template <typename T, typename A>
  T* c_data(std::vector<T,A>& v) { return &(v[0]); }
}

std::ostream&
operator<<(std::ostream& out, cartesian_dimension const& d) {
  out << '(' << d.size << ',';
  if (d.periodic) {
    out << "periodic";
  } else {
    out << "bounded";
  }
  out << ')';
  return out;
}

std::ostream&
operator<<(std::ostream& out, cartesian_topology const& topo) {
  out << '{';
  int const sz = topo.size();
  for (int i = 0; i < sz; ++i) {
    out << topo[i];
    if ( i < (sz-1) ) {
      out << ',';
    }
  }
  out << '}';
  return out;
}

cartesian_communicator::cartesian_communicator(const communicator&         comm,
                                               const cartesian_topology&   topology,
                                               bool                        reorder )
  : communicator(MPI_COMM_NULL, comm_attach) 
{
  std::vector<int> dims(topology.size());
  std::vector<int> periodic(topology.size());
  int tsz = topology.size();
  for(int i = 0; i < tsz; ++i) {
    dims[i]     = topology[i].size;
    periodic[i] = topology[i].periodic;
  }
  // Fill the gaps, if any
  if (std::count(dims.begin(), dims.end(), 0) > 0) {
    cartesian_dimensions(comm, dims);
  }
  MPI_Comm newcomm;
  BOOST_MPI_CHECK_RESULT(MPI_Cart_create, 
                         ((MPI_Comm)comm, dims.size(),
                          c_data(dims), c_data(periodic),
                          int(reorder), &newcomm));
  if(newcomm != MPI_COMM_NULL) {
    comm_ptr.reset(new MPI_Comm(newcomm), comm_free());
  }
}

cartesian_communicator::cartesian_communicator(const cartesian_communicator& comm,
                                               const std::vector<int>&       keep ) 
  : communicator(MPI_COMM_NULL, comm_attach) 
{
  int const max_dims = comm.ndims();
  int const nbkept = keep.size();
  assert(nbkept <= max_dims);
  std::vector<int> bitset(max_dims, int(false));
  for(int i = 0; i < nbkept; ++i) {
    assert(keep[i] < max_dims);
    bitset[keep[i]] = true;
  }
  
  MPI_Comm newcomm;
  BOOST_MPI_CHECK_RESULT(MPI_Cart_sub, 
                         ((MPI_Comm)comm, c_data(bitset), &newcomm));
  if(newcomm != MPI_COMM_NULL) {
    comm_ptr.reset(new MPI_Comm(newcomm), comm_free());
  }
}

int
cartesian_communicator::ndims() const {
  int n = -1;
  BOOST_MPI_CHECK_RESULT(MPI_Cartdim_get, 
                         (MPI_Comm(*this), &n));
  return n;
}

int
cartesian_communicator::rank(const std::vector<int>& coords ) const {
  int r = -1;
  assert(int(coords.size()) == ndims());
  BOOST_MPI_CHECK_RESULT(MPI_Cart_rank, 
                         (MPI_Comm(*this), c_data(const_cast<std::vector<int>&>(coords)), 
                          &r));
  return r;
}

std::pair<int, int>
cartesian_communicator::shifted_ranks(int dim, int disp) const {
  std::pair<int, int> r(-1,-1);
  assert(0 <= dim && dim < ndims());
  BOOST_MPI_CHECK_RESULT(MPI_Cart_shift, 
                         (MPI_Comm(*this), dim, disp, &(r.first), &(r.second)));
  return r;
}

std::vector<int>
cartesian_communicator::coordinates(int rk) const {
  std::vector<int> cbuf(ndims());
  BOOST_MPI_CHECK_RESULT(MPI_Cart_coords, 
                         (MPI_Comm(*this), rk, cbuf.size(), c_data(cbuf) ));
  return cbuf;
}

void
cartesian_communicator::topology(  cartesian_topology&  topo,
                                   std::vector<int>&  coords ) const {
  int ndims = this->ndims();
  topo.resize(ndims);
  coords.resize(ndims);
  std::vector<int> cdims(ndims);
  std::vector<int> cperiods(ndims);
  BOOST_MPI_CHECK_RESULT(MPI_Cart_get,
                         (MPI_Comm(*this), ndims, c_data(cdims), c_data(cperiods), c_data(coords)));
  cartesian_topology res(cdims.begin(), cperiods.begin(), ndims);
  topo.swap(res);
}

cartesian_topology
cartesian_communicator::topology() const {
  cartesian_topology topo(ndims());
  std::vector<int> coords;
  topology(topo, coords);
  return topo;
}

void
cartesian_topology::split(std::vector<int>& dims, std::vector<bool>& periodics) const {
  int ndims = size();
  dims.resize(ndims);
  periodics.resize(ndims);
  for(int i = 0; i < ndims; ++i) {
    cartesian_dimension const& d = (*this)[i];
    dims[i]      = d.size;
    periodics[i] = d.periodic;
  }
}

std::vector<int>&
cartesian_dimensions(int sz, std::vector<int>&  dims) {
  int min = 1;
  int const dimsz = dims.size();
  for(int i = 0; i < dimsz; ++i) {
    if (dims[i] > 0) {
      min *= dims[i];
    }
  }
  int leftover = sz % min;
  
  BOOST_MPI_CHECK_RESULT(MPI_Dims_create,
                         (sz-leftover, dims.size(), c_data(dims)));
  return dims;
}

} } // end namespace boost::mpi
