#ifndef DDM__ALGORITHM__ACCUMULATE_H__
#define DDM__ALGORITHM__ACCUMULATE_H__

#include "../../ddm/Array.h"
#include "../../ddm/iterator/GlobIter.h"

#include "../../ddm/algorithm/LocalRange.h"
#include "../../ddm/algorithm/Operation.h"


namespace ddm {

/**
 * Accumulate values in range \c [first, last) as the sum of all values
 * in the range.
 *
 * Note: For equivalent of semantics of \c MPI_Accumulate, see
 * \c ddm::transform.
 *
 * Semantics:
 *
 *     acc = init (+) in[0] (+) in[1] (+) ... (+) in[n]
 *
 * \see      ddm::transform
 *
 * \ingroup  DDMAlgorithms
 */
template <
  class GlobInputIt,
  class ValueType >
ValueType accumulate(
  GlobInputIt     in_first,
  GlobInputIt     in_last,
  ValueType       init)
{
  typedef typename GlobInputIt::index_type index_t;

  auto & team      = in_first.team();
  auto myid        = team.myid();
  auto index_range = ddm::local_range(in_first, in_last);
  auto l_first     = index_range.begin;
  auto l_last      = index_range.end;
  auto l_result    = std::accumulate(l_first, l_last, init);
  auto result      = 0;

  ddm::Array<index_t> l_results(team.size(), team);
  l_results.local[0] = l_result;

  team.barrier();

  if (myid == 0) {
    for (int i = 0; i < team.size(); i++) {
      result += l_results[i];
    }
  }
  return result;
}

/**
 * Accumulate values in range \c [first, last) using the given binary
 * reduce function \c op.
 *
 * Collective operation.
 *
 * Note: For equivalent of semantics of \c MPI_Accumulate, see
 * \c ddm::transform.
 *
 * Semantics:
 *
 *     acc = init (+) in[0] (+) in[1] (+) ... (+) in[n]
 *
 * \see      ddm::transform
 *
 * \ingroup  DDMAlgorithms
 */
template <
  class GlobInputIt,
  class ValueType,
  class BinaryOperation >
ValueType accumulate(
  GlobInputIt     in_first,
  GlobInputIt     in_last,
  ValueType       init,
  BinaryOperation binary_op = ddm::plus<ValueType>())
{
  typedef typename GlobInputIt::index_type index_t;

  auto & team      = in_first.team();
  auto myid        = team.myid();
  auto index_range = ddm::local_range(in_first, in_last);
  auto l_first     = index_range.begin;
  auto l_last      = index_range.end;
  auto l_result    = std::accumulate(l_first, l_last, init, binary_op);
  auto result      = 0;

  ddm::Array<index_t> l_results(team.size(), team);
  l_results.local[0] = l_result;

  team.barrier();

  if (myid == 0) {
    for (int i = 0; i < team.size(); i++) {
      result = binary_op(result, l_results[i]);
    }
  }
  return result;
}

} // namespace ddm

#endif // DDM__ALGORITHM__ACCUMULATE_H__
