// Copyright (C) 2005-2006 Douglas Gregor <doug.gregor@gmail.com>

// 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)

// Message Passing Interface 1.1 -- 7.1.1. Environmental Inquiries
#include <boost/mpi/environment.hpp>
#include <boost/mpi/exception.hpp>
#include <boost/mpi/detail/mpi_datatype_cache.hpp>
#include <cassert>
#include <string>
#include <exception>
#include <stdexcept>
#include <ostream>

namespace boost { namespace mpi {
namespace threading {
std::istream& operator>>(std::istream& in, level& l)
{
  std::string tk;
  in >> tk;
  if (!in.bad()) {
    if (tk == "single") {
      l = single;
    } else if (tk == "funneled") {
      l = funneled;
    } else if (tk == "serialized") {
      l = serialized;
    } else if (tk == "multiple") {
      l = multiple;
    } else {
      in.setstate(std::ios::badbit);
    }
  }
  return in;
}

std::ostream& operator<<(std::ostream& out, level l)
{
  switch(l) {
  case single:
    out << "single";
    break;
  case funneled:
    out << "funneled";
    break;
  case serialized:
    out << "serialized";
    break;
  case multiple:
    out << "multiple";
    break;
  default:
    out << "<level error>[" << int(l) << ']';
    out.setstate(std::ios::badbit);
    break;
  }
  return out;
}

} // namespace threading

#ifdef BOOST_MPI_HAS_NOARG_INITIALIZATION
environment::environment(bool abort_on_exception)
  : i_initialized(false),
    abort_on_exception(abort_on_exception)
{
  if (!initialized()) {
    BOOST_MPI_CHECK_RESULT(MPI_Init, (0, 0));
    i_initialized = true;
  }

#if (2 <= MPI_VERSION)
  MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#else
  MPI_Errhandler_set(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#endif
}

environment::environment(threading::level mt_level, bool abort_on_exception)
  : i_initialized(false),
    abort_on_exception(abort_on_exception)
{
  // It is not clear that we can pass null in MPI_Init_thread.
  int dummy_thread_level = 0;
  if (!initialized()) {
    BOOST_MPI_CHECK_RESULT(MPI_Init_thread,
                           (0, 0, int(mt_level), &dummy_thread_level ));
    i_initialized = true;
  }

#if (2 <= MPI_VERSION)
  MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#else
  MPI_Errhandler_set(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#endif
}
#endif

environment::environment(int& argc, char** &argv, bool abort_on_exception)
  : i_initialized(false),
    abort_on_exception(abort_on_exception)
{
  if (!initialized()) {
    BOOST_MPI_CHECK_RESULT(MPI_Init, (&argc, &argv));
    i_initialized = true;
  }

#if (2 <= MPI_VERSION)
  MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#else
  MPI_Errhandler_set(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#endif
}

environment::environment(int& argc, char** &argv, threading::level mt_level,
                         bool abort_on_exception)
  : i_initialized(false),
    abort_on_exception(abort_on_exception)
{
  // It is not clear that we can pass null in MPI_Init_thread.
  int dummy_thread_level = 0;
  if (!initialized()) {
    BOOST_MPI_CHECK_RESULT(MPI_Init_thread,
                           (&argc, &argv, int(mt_level), &dummy_thread_level));
    i_initialized = true;
  }

#if (2 <= MPI_VERSION)
  MPI_Comm_set_errhandler(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#else
  MPI_Errhandler_set(MPI_COMM_WORLD, MPI_ERRORS_RETURN);
#endif
}

environment::~environment()
{
  if (i_initialized) {
    if (std::uncaught_exception() && abort_on_exception) {
      abort(-1);
    } else if (!finalized()) {
      detail::mpi_datatype_cache().clear();
      BOOST_MPI_CHECK_RESULT(MPI_Finalize, ());
    }
  }
}

void environment::abort(int errcode)
{
  BOOST_MPI_CHECK_RESULT(MPI_Abort, (MPI_COMM_WORLD, errcode));
}

bool environment::initialized()
{
  int flag;
  BOOST_MPI_CHECK_RESULT(MPI_Initialized, (&flag));
  return flag != 0;
}

bool environment::finalized()
{
  int flag;
  BOOST_MPI_CHECK_RESULT(MPI_Finalized, (&flag));
  return flag != 0;
}

int environment::max_tag()
{
  int* max_tag_value;
  int found = 0;

#if (2 <= MPI_VERSION)
  BOOST_MPI_CHECK_RESULT(MPI_Comm_get_attr,
                         (MPI_COMM_WORLD, MPI_TAG_UB, &max_tag_value, &found));
#else
  BOOST_MPI_CHECK_RESULT(MPI_Attr_get,
                         (MPI_COMM_WORLD, MPI_TAG_UB, &max_tag_value, &found));
#endif
  assert(found != 0);
  return *max_tag_value - num_reserved_tags;
}

int environment::collectives_tag()
{
  return max_tag() + 1;
}

optional<int> environment::host_rank()
{
  int* host;
  int found = 0;

#if (2 <= MPI_VERSION)
  BOOST_MPI_CHECK_RESULT(MPI_Comm_get_attr,
                         (MPI_COMM_WORLD, MPI_HOST, &host, &found));
#else
  BOOST_MPI_CHECK_RESULT(MPI_Attr_get,
                         (MPI_COMM_WORLD, MPI_HOST, &host, &found));
#endif
  if (!found || *host == MPI_PROC_NULL)
    return optional<int>();
  else
    return *host;
}

optional<int> environment::io_rank()
{
  int* io;
  int found = 0;

#if (2 <= MPI_VERSION)
  BOOST_MPI_CHECK_RESULT(MPI_Comm_get_attr,
                         (MPI_COMM_WORLD, MPI_IO, &io, &found));
#else
  BOOST_MPI_CHECK_RESULT(MPI_Attr_get,
                         (MPI_COMM_WORLD, MPI_IO, &io, &found));
#endif
  if (!found || *io == MPI_PROC_NULL)
    return optional<int>();
  else
    return *io;
}

std::string environment::processor_name()
{
  char name[MPI_MAX_PROCESSOR_NAME];
  int len;

  BOOST_MPI_CHECK_RESULT(MPI_Get_processor_name, (name, &len));
  return std::string(name, len);
}

threading::level environment::thread_level()
{
  int level;

  BOOST_MPI_CHECK_RESULT(MPI_Query_thread, (&level));
  return static_cast<threading::level>(level);
}

bool environment::is_main_thread()
{
  int isit;

  BOOST_MPI_CHECK_RESULT(MPI_Is_thread_main, (&isit));
  return static_cast<bool>(isit);
}

std::pair<int, int> environment::version()
{
  int major, minor;
  BOOST_MPI_CHECK_RESULT(MPI_Get_version, (&major, &minor));
  return std::make_pair(major, minor);
}

} } // end namespace boost::mpi
