// (C) Copyright 2007 
// Douglas Gregor <doug.gregor -at- gmail.com>
// Andreas Kloeckner <inform -at- tiker.net>

// 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: Douglas Gregor, Andreas Kloeckner

/** @file py_nonblocking.cpp
 *
 *  This file reflects the Boost.MPI nonblocking operations into Python
 *  functions.
 */

#include <vector>
#include <iterator>
#include <algorithm>
#include <boost/operators.hpp>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/mpi.hpp>
#include "request_with_value.hpp"

using namespace std;
using namespace boost::python;
using namespace boost::mpi;




namespace
{
  template <class ValueType, class RequestIterator>
  class py_call_output_iterator :
    public boost::output_iterator_helper< 
      py_call_output_iterator<ValueType, RequestIterator> >
  {
    private:
      object m_callable;
      RequestIterator m_request_iterator;

    public:
      explicit py_call_output_iterator(object callable, 
          const RequestIterator &req_it)
        : m_callable(callable), m_request_iterator(req_it)
      { }

      py_call_output_iterator &operator=(ValueType const &v)
      {
        m_callable((m_request_iterator++)->get_value_or_none(), v);
        return *this;
      }
  };




  typedef std::vector<python::request_with_value> request_list;
  typedef py_call_output_iterator<status, request_list::iterator> 
    status_value_iterator;




  std::shared_ptr<request_list> make_request_list_from_py_list(object iterable)
  {
    std::shared_ptr<request_list> result(new request_list);
    std::copy(
        stl_input_iterator<python::request_with_value>(iterable),
        stl_input_iterator<python::request_with_value>(),
        back_inserter(*result));
    return result;
  }




  class request_list_indexing_suite :
    public vector_indexing_suite<request_list, false, request_list_indexing_suite>
  {
    public:
      // FIXME: requests are not comparable, thus __contains__ makes no sense.
      // Unfortunately, indexing_suites insist on having __contains__ available.
      // Just make it error out for now.

      static bool
        contains(request_list& container, request const& key)
        {
          PyErr_SetString(PyExc_NotImplementedError, "mpi requests are not comparable");
          throw error_already_set();
        }
  };




  void check_request_list_not_empty(const request_list &requests)
  {
    if (requests.size() == 0)
    {
      PyErr_SetString(PyExc_ValueError, "cannot wait on an empty request vector");
      throw error_already_set();
    }

  }





  object wrap_wait_any(request_list &requests)
  {
    check_request_list_not_empty(requests);

    pair<status, request_list::iterator> result = 
      wait_any(requests.begin(), requests.end());

    return boost::python::make_tuple(
        result.second->get_value_or_none(),
        result.first, 
        distance(requests.begin(), result.second));
  }




  object wrap_test_any(request_list &requests)
  {
    check_request_list_not_empty(requests);
    ::boost::optional<pair<status, request_list::iterator> > result = 
      test_any(requests.begin(), requests.end());

    if (result)
      return boost::python::make_tuple(
          result->second->get_value_or_none(),
          result->first, 
          distance(requests.begin(), result->second));
    else
      return object();
  }





  void wrap_wait_all(request_list &requests, object py_callable)
  {
    check_request_list_not_empty(requests);
    if (py_callable != object())
      wait_all(requests.begin(), requests.end(), 
          status_value_iterator(py_callable, requests.begin()));
    else
      wait_all(requests.begin(), requests.end());
  }




  bool wrap_test_all(request_list &requests, object py_callable)
  {
    check_request_list_not_empty(requests);
    if (py_callable != object())
      return bool(test_all(requests.begin(), requests.end(), 
			   status_value_iterator(py_callable, requests.begin())));
    else
      return bool(test_all(requests.begin(), requests.end()));
  }




  int wrap_wait_some(request_list &requests, object py_callable)
  {
    check_request_list_not_empty(requests);
    request_list::iterator first_completed;
    if (py_callable != object())
      first_completed = wait_some(requests.begin(), requests.end(), 
          status_value_iterator(py_callable, requests.begin())).second;
    else
      first_completed = wait_some(requests.begin(), requests.end());

    return distance(requests.begin(), first_completed);
  }




  int wrap_test_some(request_list &requests, object py_callable)
  {
    check_request_list_not_empty(requests);
    request_list::iterator first_completed;
    if (py_callable != object())
      first_completed = test_some(requests.begin(), requests.end(), 
          status_value_iterator(py_callable, requests.begin())).second;
    else
      first_completed = test_some(requests.begin(), requests.end());

    return distance(requests.begin(), first_completed);
  }
}




namespace boost { namespace mpi { namespace python {

extern const char* request_list_init_docstring;
extern const char* request_list_append_docstring;

extern const char* nonblocking_wait_any_docstring;
extern const char* nonblocking_test_any_docstring;
extern const char* nonblocking_wait_all_docstring;
extern const char* nonblocking_test_all_docstring;
extern const char* nonblocking_wait_some_docstring;
extern const char* nonblocking_test_some_docstring;

void export_nonblocking()
{
  using boost::python::arg;

  {
    typedef request_list cl;
    class_<cl>("RequestList", "A list of Request objects.")
      .def("__init__", make_constructor(make_request_list_from_py_list),
          /*arg("iterable"),*/ request_list_init_docstring)
      .def(request_list_indexing_suite())
      ;
  }

  def("wait_any", wrap_wait_any,
      (arg("requests")),
      nonblocking_wait_any_docstring);
  def("test_any", wrap_test_any,
      (arg("requests")),
      nonblocking_test_any_docstring);

  def("wait_all", wrap_wait_all,
      (arg("requests"), arg("callable") = object()),
      nonblocking_wait_all_docstring);
  def("test_all", wrap_test_all,
      (arg("requests"), arg("callable") = object()),
      nonblocking_test_all_docstring);

  def("wait_some", wrap_wait_some,
      (arg("requests"), arg("callable") = object()),
      nonblocking_wait_some_docstring);
  def("test_some", wrap_test_some,
      (arg("requests"), arg("callable") = object()),
      nonblocking_test_some_docstring);
}

} } }
