// Copyright 2009 The Trustees of Indiana University.

// 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: Jeremiah Willcock
//           Andrew Lumsdaine

#ifndef BOOST_GRAPH_DETAIL_HISTOGRAM_SORT_HPP
#define BOOST_GRAPH_DETAIL_HISTOGRAM_SORT_HPP

#include <boost/assert.hpp>

namespace boost {
  namespace graph {
    namespace detail {

template<typename InputIterator>
size_t
reserve_count_for_single_pass_helper(InputIterator, InputIterator,
                                     std::input_iterator_tag)
{
  // Do nothing: we have no idea how much storage to reserve.
  return 0;
}

template<typename InputIterator>
size_t
reserve_count_for_single_pass_helper(InputIterator first, InputIterator last,
                                     std::random_access_iterator_tag)
{
  using std::distance;
  typename std::iterator_traits<InputIterator>::difference_type n =
    distance(first, last);
  return (size_t)n;
}

template<typename InputIterator>
size_t
reserve_count_for_single_pass(InputIterator first, InputIterator last) {
  typedef typename std::iterator_traits<InputIterator>::iterator_category
    category;
  return reserve_count_for_single_pass_helper(first, last, category());
}

template <typename KeyIterator, typename RowstartIterator,
          typename VerticesSize, typename KeyFilter, typename KeyTransform>
void
count_starts
  (KeyIterator begin, KeyIterator end,
   RowstartIterator starts, // Must support numverts + 1 elements
   VerticesSize numkeys,
   KeyFilter key_filter,
   KeyTransform key_transform) {

  typedef VerticesSize vertices_size_type;
  typedef typename std::iterator_traits<RowstartIterator>::value_type EdgeIndex;

  // Put the degree of each vertex v into m_rowstart[v + 1]
  for (KeyIterator i = begin; i != end; ++i) {
    if (key_filter(*i)) {
      BOOST_ASSERT (key_transform(*i) < numkeys);
      ++starts[key_transform(*i) + 1];
    }
  }

  // Compute the partial sum of the degrees to get the actual values of
  // m_rowstart
  EdgeIndex start_of_this_row = 0;
  starts[0] = start_of_this_row;
  for (vertices_size_type i = 1; i <= numkeys; ++i) {
    start_of_this_row += starts[i];
    starts[i] = start_of_this_row;
  }
}

template <typename KeyIterator, typename RowstartIterator,
          typename NumKeys,
          typename Value1InputIter,
          typename Value1OutputIter, typename KeyFilter, typename KeyTransform>
void
histogram_sort(KeyIterator key_begin, KeyIterator key_end,
               RowstartIterator rowstart, // Must support numkeys + 1 elements and be precomputed
               NumKeys numkeys,
               Value1InputIter values1_begin,
               Value1OutputIter values1_out,
               KeyFilter key_filter,
               KeyTransform key_transform) {

  typedef NumKeys vertices_size_type;
  typedef typename std::iterator_traits<RowstartIterator>::value_type EdgeIndex;

  // Histogram sort the edges by their source vertices, putting the targets
  // into m_column.  The index current_insert_positions[v] contains the next
  // location to insert out edges for vertex v.
  std::vector<EdgeIndex>
    current_insert_positions(rowstart, rowstart + numkeys);
  Value1InputIter v1i = values1_begin;
  for (KeyIterator i = key_begin; i != key_end; ++i, ++v1i) {
    if (key_filter(*i)) {
      vertices_size_type source = key_transform(*i);
      BOOST_ASSERT (source < numkeys);
      EdgeIndex insert_pos = current_insert_positions[source];
      ++current_insert_positions[source];
      values1_out[insert_pos] = *v1i;
    }
  }
}

template <typename KeyIterator, typename RowstartIterator,
          typename NumKeys,
          typename Value1InputIter,
          typename Value1OutputIter,
          typename Value2InputIter,
          typename Value2OutputIter,
          typename KeyFilter, typename KeyTransform>
void
histogram_sort(KeyIterator key_begin, KeyIterator key_end,
               RowstartIterator rowstart, // Must support numkeys + 1 elements and be precomputed
               NumKeys numkeys,
               Value1InputIter values1_begin,
               Value1OutputIter values1_out,
               Value2InputIter values2_begin,
               Value2OutputIter values2_out,
               KeyFilter key_filter,
               KeyTransform key_transform) {

  typedef NumKeys vertices_size_type;
  typedef typename std::iterator_traits<RowstartIterator>::value_type EdgeIndex;

  // Histogram sort the edges by their source vertices, putting the targets
  // into m_column.  The index current_insert_positions[v] contains the next
  // location to insert out edges for vertex v.
  std::vector<EdgeIndex>
    current_insert_positions(rowstart, rowstart + numkeys);
  Value1InputIter v1i = values1_begin;
  Value2InputIter v2i = values2_begin;
  for (KeyIterator i = key_begin; i != key_end; ++i, ++v1i, ++v2i) {
    if (key_filter(*i)) {
      vertices_size_type source = key_transform(*i);
      BOOST_ASSERT (source < numkeys);
      EdgeIndex insert_pos = current_insert_positions[source];
      ++current_insert_positions[source];
      values1_out[insert_pos] = *v1i;
      values2_out[insert_pos] = *v2i;
    }
  }
}

template <typename KeyIterator, typename RowstartIterator,
          typename NumKeys,
          typename Value1Iter,
          typename KeyTransform>
void
histogram_sort_inplace(KeyIterator key_begin,
                       RowstartIterator rowstart, // Must support numkeys + 1 elements and be precomputed
                       NumKeys numkeys,
                       Value1Iter values1,
                       KeyTransform key_transform) {

  typedef NumKeys vertices_size_type;
  typedef typename std::iterator_traits<RowstartIterator>::value_type EdgeIndex;

  // 1. Copy m_rowstart (except last element) to get insert positions
  std::vector<EdgeIndex> insert_positions(rowstart, rowstart + numkeys);
  // 2. Swap the sources and targets into place
  for (size_t i = 0; i < rowstart[numkeys]; ++i) {
    BOOST_ASSERT (key_transform(key_begin[i]) < numkeys);
    // While edge i is not in the right bucket:
    while (!(i >= rowstart[key_transform(key_begin[i])] && i < insert_positions[key_transform(key_begin[i])])) {
      // Add a slot in the right bucket
      size_t target_pos = insert_positions[key_transform(key_begin[i])]++;
      BOOST_ASSERT (target_pos < rowstart[key_transform(key_begin[i]) + 1]);
      if (target_pos == i) continue;
      // Swap this edge into place
      using std::swap;
      swap(key_begin[i], key_begin[target_pos]);
      swap(values1[i], values1[target_pos]);
    }
  }
}

template <typename KeyIterator, typename RowstartIterator,
          typename NumKeys,
          typename Value1Iter,
          typename Value2Iter,
          typename KeyTransform>
void
histogram_sort_inplace(KeyIterator key_begin,
                       RowstartIterator rowstart, // Must support numkeys + 1 elements and be precomputed
                       NumKeys numkeys,
                       Value1Iter values1,
                       Value2Iter values2,
                       KeyTransform key_transform) {

  typedef NumKeys vertices_size_type;
  typedef typename std::iterator_traits<RowstartIterator>::value_type EdgeIndex;

  // 1. Copy m_rowstart (except last element) to get insert positions
  std::vector<EdgeIndex> insert_positions(rowstart, rowstart + numkeys);
  // 2. Swap the sources and targets into place
  for (size_t i = 0; i < rowstart[numkeys]; ++i) {
    BOOST_ASSERT (key_transform(key_begin[i]) < numkeys);
    // While edge i is not in the right bucket:
    while (!(i >= rowstart[key_transform(key_begin[i])] && i < insert_positions[key_transform(key_begin[i])])) {
      // Add a slot in the right bucket
      size_t target_pos = insert_positions[key_transform(key_begin[i])]++;
      BOOST_ASSERT (target_pos < rowstart[key_transform(key_begin[i]) + 1]);
      if (target_pos == i) continue;
      // Swap this edge into place
      using std::swap;
      swap(key_begin[i], key_begin[target_pos]);
      swap(values1[i], values1[target_pos]);
      swap(values2[i], values2[target_pos]);
    }
  }
}

template <typename InputIterator, typename VerticesSize>
void split_into_separate_coords(InputIterator begin, InputIterator end,
                                std::vector<VerticesSize>& firsts,
                                std::vector<VerticesSize>& seconds) {
  firsts.clear();
  seconds.clear();
  size_t reserve_size
    = detail::reserve_count_for_single_pass(begin, end);
  firsts.reserve(reserve_size);
  seconds.reserve(reserve_size);
  for (; begin != end; ++begin) {
    std::pair<VerticesSize, VerticesSize> edge = *begin;
    firsts.push_back(edge.first);
    seconds.push_back(edge.second);
  }
}

template <typename InputIterator, typename VerticesSize, typename SourceFilter>
void split_into_separate_coords_filtered
  (InputIterator begin, InputIterator end,
   std::vector<VerticesSize>& firsts,
   std::vector<VerticesSize>& seconds,
   const SourceFilter& filter) {
  firsts.clear();
  seconds.clear();
  for (; begin != end; ++begin) {
    std::pair<VerticesSize, VerticesSize> edge = *begin;
    if (filter(edge.first)) {
      firsts.push_back(edge.first);
      seconds.push_back(edge.second);
    }
  }
}

template <typename InputIterator, typename PropInputIterator,
          typename VerticesSize, typename PropType, typename SourceFilter>
void split_into_separate_coords_filtered
  (InputIterator begin, InputIterator end,
   PropInputIterator props,
   std::vector<VerticesSize>& firsts,
   std::vector<VerticesSize>& seconds,
   std::vector<PropType>& props_out,
   const SourceFilter& filter) {
  firsts.clear();
  seconds.clear();
  props_out.clear();
  for (; begin != end; ++begin) {
    std::pair<VerticesSize, VerticesSize> edge = *begin;
    if (filter(edge.first)) {
      firsts.push_back(edge.first);
      seconds.push_back(edge.second);
      props_out.push_back(*props);
    }
    ++props;
  }
}

template <typename Pair>
struct project1st {
  typedef typename Pair::first_type result_type;
  const result_type& operator()(const Pair& p) const {return p.first;}
};

template <typename Pair>
struct project2nd {
  typedef typename Pair::second_type result_type;
  const result_type& operator()(const Pair& p) const {return p.second;}
};

    }
  }
}

#endif // BOOST_GRAPH_DETAIL_HISTOGRAM_SORT_HPP
