/*
 * Copyright 2017 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <limits>
#include <tuple>
#include <type_traits>

// tupleRange<start, n>(tuple): select n elements starting at index start
//    in the given tuple
// tupleRange<start>(tuple): select all elements starting at index start
//    until the end of the given tuple
// tuplePrepend(x, tuple): return a tuple obtained by prepending x to the
//    given tuple.
//
// In Lisp lingo, std::get<0> is car, tupleRange<1> is cdr, and tuplePrepend
// is cons.

namespace folly {

// TemplateSeq<T, ...> is a type parametrized by sizeof...(Xs) values of type
// T. Used to destructure the values into a template parameter pack;
// see the example in TupleSelect, below.
template <class T, T... xs>
struct TemplateSeq {
  template <T x>
  using Prepend = TemplateSeq<T, x, xs...>;
};

// TemplateRange<T, start, n>::type is
// TemplateSeq<T, start+1, start+2, ..., start+n-1>
template <class T, T start, T n, class Enable=void> struct TemplateRange;

template <class T, T start, T n>
struct TemplateRange<
  T, start, n,
  typename std::enable_if<(n > 0)>::type> {
  using type =
    typename TemplateRange<T, start+1, n-1>::type::template Prepend<start>;
};

template <class T, T start, T n>
struct TemplateRange<
  T, start, n,
  typename std::enable_if<(n <= 0)>::type> {
  using type = TemplateSeq<T>;
};

// Similar to TemplateRange, given a tuple T,
// TemplateTupleRange<T, start, n>::type is
// TemplateSeq<size_t, start, start+1, ..., start+k-1>
// where k = min(tuple_size<T>::value - start, n)
// (that is, it's a TemplateSeq of at most n elements, but won't extend
// past the end of the given tuple)
template <class T,
          std::size_t start = 0,
          std::size_t n = std::numeric_limits<std::size_t>::max(),
          std::size_t size =
            std::tuple_size<typename std::remove_reference<T>::type>::value,
          class Enable = typename std::enable_if<(start <= size)>::type>
struct TemplateTupleRange {
  using type = typename TemplateRange<
      std::size_t,
      start,
      (n <= size - start ? n : size - start)>::type;
};

namespace detail {

// Helper class to select a subset of a tuple
template <class S> struct TupleSelect;
template <std::size_t... Ns>
struct TupleSelect<TemplateSeq<std::size_t, Ns...>> {
  template <class T>
  static auto select(T&& v)
  -> decltype(std::make_tuple(std::get<Ns>(std::forward<T>(v))...)) {
    return std::make_tuple(std::get<Ns>(std::forward<T>(v))...);
  }
};

}  // namespace detail

// Return a tuple consisting of the elements at a range of indices.
//
// Use as tupleRange<start, n>(t) to return a tuple of (at most) n
// elements starting at index start in tuple t.
// If only start is specified (tupleRange<start>(t)), returns all elements
// starting at index start until the end of the tuple t.
// Won't compile if start > size of t.
// Will return fewer elements (size - start) if start + n > size of t.
template <
  std::size_t start = 0,
  std::size_t n = std::numeric_limits<std::size_t>::max(),
  class T,
  class Seq = typename TemplateTupleRange<T, start, n>::type>
auto tupleRange(T&& v)
-> decltype(detail::TupleSelect<Seq>::select(std::forward<T>(v))) {
  return detail::TupleSelect<Seq>::select(std::forward<T>(v));
}

// Return a tuple obtained by prepending car to the tuple cdr.
template <class T, class U>
auto tuplePrepend(T&& car, U&& cdr)
-> decltype(std::tuple_cat(std::make_tuple(std::forward<T>(car)),
                           std::forward<U>(cdr))) {
  return std::tuple_cat(std::make_tuple(std::forward<T>(car)),
                        std::forward<U>(cdr));
}

}  // namespaces
