#ifndef CAFFE2_UTILS_GPU_BITONIC_SORT_H_
#define CAFFE2_UTILS_GPU_BITONIC_SORT_H_

#include "caffe2/utils/math.h"
#include "caffe2/utils/GpuDefs.cuh"

namespace caffe2 {

// Returns true if the given integer type is a power-of-2 (positive only)
// Note(jiayq): windows reported an error per
//     https://github.com/caffe2/caffe2/issues/997
// and as a result will make it a macro.
#ifdef _MSC_VER
#define integerIsPowerOf2(v) ((v) && !((v) & ((v) - 1)))
#else // _MSC_VER
template <typename T>
constexpr bool integerIsPowerOf2(T v) {
  return (v && !(v & (v - 1)));
}
#endif // _MSC_VER

/// The maximum in-block bitonic sort we support
constexpr int kMaxBitonicSortSize = 4096;

template <typename T>
__device__ inline void swapVars(T& t1, T& t2) {
  T tmp = t1;
  t1 = t2;
  t2 = tmp;
}

template <typename Comparator, typename K, typename V>
__device__ inline void bitonicSwap(K& kA, V& vA,
                                   K& kB, V& vB,
                                   bool dir,
                                   const Comparator& comp) {
  bool swap = comp(kA, vA, kB, vB);
  if (swap == dir) {
    swapVars(kA, kB);
    swapVars(vA, vB);
  }
};

template <typename Comparator, typename K, typename V,
          int Power2SortSize,
          int ThreadsPerBlock>
__device__ inline void bitonicSort(K* keys,
                                   V* values,
                                   const Comparator& comp) {
  static_assert(Power2SortSize <= kMaxBitonicSortSize,
                "sort size <= 4096 only supported");
  // Assume the sort is taking place in shared memory
  // static_assert(Power2SortSize * (sizeof(K) + sizeof(V)) < 32768,
  //               "sort data too large (>32768 bytes)");
  static_assert(integerIsPowerOf2(Power2SortSize),
                "sort size must be power of 2");
  static_assert(integerIsPowerOf2(ThreadsPerBlock),
                "threads in block must be power of 2");

  // If what we are sorting is too small, then not all threads
  // participate
  constexpr int numThreadsForSort = Power2SortSize / 2;
  constexpr bool allThreads = numThreadsForSort >= ThreadsPerBlock;

  // If what we are sorting is too large, then threads must loop more
  // than once
  constexpr int loopPerThread =
    allThreads ? numThreadsForSort / ThreadsPerBlock : 1;

#pragma unroll
  for (int size = 2; size < Power2SortSize; size *= 2) {

#pragma unroll
    for (int stride = size / 2; stride > 0; stride /= 2) {

#pragma unroll
      for (int loop = 0; loop < loopPerThread; ++loop) {
        int threadId = loop * ThreadsPerBlock + threadIdx.x;
        bool flag = ((threadId & (size / 2)) != 0);

        int pos = 2 * threadId - (threadId & (stride - 1));

        if (allThreads || (threadId < numThreadsForSort)) {
          bitonicSwap<Comparator, K, V>(
            keys[pos], values[pos],
            keys[pos + stride], values[pos + stride],
            flag, comp);
        }

        __syncthreads();
      }
    }
  }

#pragma unroll
  for (int stride = Power2SortSize / 2; stride > 0; stride /= 2) {

#pragma unroll
    for (int loop = 0; loop < loopPerThread; ++loop) {
      int threadId = loop * ThreadsPerBlock + threadIdx.x;

      int pos = 2 * threadId - (threadId & (stride - 1));

      if (allThreads || (threadId < numThreadsForSort)) {
        bitonicSwap<Comparator, K, V>(
          keys[pos], values[pos],
          keys[pos + stride], values[pos + stride],
          false, comp);
      }

      __syncthreads();
    }
  }
}

template <typename Comparator, typename K, typename V, int Power2SortSize>
__device__ inline void warpBitonicSort(K* keys,
                                       V* values,
                                       const Comparator& comp) {
  // Smaller sorts should use a warp shuffle sort
  static_assert(Power2SortSize > kWarpSize,
                "sort not large enough");
  static_assert(integerIsPowerOf2(Power2SortSize),
                "sort size must be power of 2");
  static_assert(Power2SortSize <= kMaxBitonicSortSize,
                "sort size <= 4096 only supported");

  // If what we are sorting is too large, then lanes must loop more
  // than once
  constexpr int loopPerThread = (Power2SortSize / 2) / kWarpSize;
  int laneId = getLaneId();

#pragma unroll
  for (int size = 2; size < Power2SortSize; size *= 2) {

#pragma unroll
    for (int stride = size / 2; stride > 0; stride /= 2) {

#pragma unroll
      for (int loop = 0; loop < loopPerThread; ++loop) {
        int threadId = loop * kWarpSize + laneId;
        bool flag = ((threadId & (size / 2)) != 0);

        int pos = 2 * threadId - (threadId & (stride - 1));

        bitonicSwap<Comparator, K, V>(
          keys[pos], values[pos],
          keys[pos + stride], values[pos + stride],
          flag, comp);

        __threadfence_block();
      }
    }
  }

#pragma unroll
  for (int stride = Power2SortSize / 2; stride > 0; stride /= 2) {

#pragma unroll
    for (int loop = 0; loop < loopPerThread; ++loop) {
      int threadId = loop * kWarpSize + laneId;

      int pos = 2 * threadId - (threadId & (stride - 1));

      bitonicSwap<Comparator, K, V>(
        keys[pos], values[pos],
        keys[pos + stride], values[pos + stride],
        false, comp);

      __threadfence_block();
    }
  }
}


}  // namespace caffe2

#endif  // CAFFE2_UTILS_GPU_BITONIC_SORT_H_
