/*******************************************************************************
 *
 * File:     type_list.hpp
 *
 * Contents: Facilities for types representing lists of types.
 *
 * Copyright 2017
 *
 * Author: David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * This implementation is partially based on the type_list implementation of
 * CAF.
 * TODO: Check license.
 *
 ******************************************************************************/

#ifndef ROSA_SUPPORT_TYPE_LIST_HPP
#define ROSA_SUPPORT_TYPE_LIST_HPP

#include "rosa/support/debug.hpp"
#include "rosa/support/types.hpp"

#include <type_traits>

namespace rosa {

// A list of types.
template <typename... Ts> struct TypeList {
  constexpr TypeList() {
    // nop
  }
};

// Denotes the empty list.
using EmptyTypeList = TypeList<>;

// Gets element at index Pos of List.
template <size_t Pos, typename... Ts> struct TypeListAtImpl;

template <size_t Pos, typename T, typename... Ts>
struct TypeListAtImpl<Pos, T, Ts...> {
  using Type = typename TypeListAtImpl<Pos - 1, Ts...>::Type;
};

template <typename T, typename... Ts> struct TypeListAtImpl<0, T, Ts...> {
  using Type = T;
};

template <size_t Pos> struct TypeListAtImpl<Pos> { using Type = none_t; };

template <typename List, size_t Pos> struct TypeListAt;

template <size_t Pos, typename... Ts> struct TypeListAt<TypeList<Ts...>, Pos> {
  using Type = typename TypeListAtImpl<Pos, Ts...>::Type;
};

// Finds the first element of type What beginning at index Pos.
template <size_t Pos, typename X, typename... Ts> struct TypeListIndexOfImpl;

template <size_t Pos, typename X> struct TypeListIndexOfImpl<Pos, X> {
  static constexpr int Value = -1;
};

template <size_t Pos, typename X, typename... Ts>
struct TypeListIndexOfImpl<Pos, X, X, Ts...> {
  static constexpr int Value = Pos;
};

template <size_t Pos, typename X, typename T, typename... Ts>
struct TypeListIndexOfImpl<Pos, X, T, Ts...> {
  static constexpr int Value = TypeListIndexOfImpl<Pos + 1, X, Ts...>::Value;
};

template <typename List, typename T> struct TypeListIndexOf;

template <typename... Ts, typename T>
struct TypeListIndexOf<TypeList<Ts...>, T> {
  static constexpr int Value = TypeListIndexOfImpl<0, T, Ts...>::Value;
};

// Gets the first element of List.
template <typename List> struct TypeListHead;

template <> struct TypeListHead<EmptyTypeList> { using Type = none_t; };

template <typename T, typename... Ts>
struct TypeListHead<TypeList<T, Ts...>> {
  using Type = T;
};

// Gets the tail of List.
template <typename List> struct TypeListTail;

template <> struct TypeListTail<TypeList<>> { using Type = EmptyTypeList; };

template <typename T, typename... Ts>
struct TypeListTail<TypeList<T, Ts...>> {
  using Type = TypeList<Ts...>;
};

// Extends a TypeList with a type, in the front or in the back depending on the
// order of the template arguments.
template <typename List, typename T>
struct TypeListPush;

template <typename... Ts, typename T>
struct TypeListPush<TypeList<Ts...>, T> {
  using Type = TypeList<Ts...,T>;
};

template <typename T, typename... Ts>
struct TypeListPush<T, TypeList<Ts...>> {
  using Type = TypeList<T, Ts...>;
};

// Drops the first N element of List.
template <size_t N, typename List> struct TypeListDrop;

template <size_t N> struct TypeListDrop<N, EmptyTypeList> {
  using Type = EmptyTypeList;
};

template <size_t N, typename T, typename... Ts>
struct TypeListDrop<N, TypeList<T, Ts...>> {
  using Type = typename std::conditional<
      N == 0, TypeList<Ts...>,
      typename TypeListDrop<N - 1, TypeList<Ts...>>::Type>::type;
};

// Gets the number of template parameters of List.
template <typename List>
struct TypeListSize;

template <typename... Ts> struct TypeListSize<TypeList<Ts...>> {
  static constexpr size_t Value = sizeof...(Ts);
};

template <typename... Ts> constexpr size_t TypeListSize<TypeList<Ts...>>::Value;

// Tests whether a list is empty.
template <typename List> struct TypeListEmpty {
  static constexpr bool Value = std::is_same<EmptyTypeList, List>::value;
};

// Tells if List contains type T.
template <typename List, typename T> struct TypeListContains;

template <typename... Ts, typename T>
struct TypeListContains<TypeList<Ts...>, T> {
  static constexpr bool Value =
      std::conditional<TypeListIndexOf<TypeList<Ts...>, T>::Value == -1,
                       std::false_type, std::true_type>::type::value;
};

// Tells if ListA is a subset of ListB
template <typename ListA, typename ListB, bool Fwd = true>
struct TypeListSubsetOf;

template <typename List>
struct TypeListSubsetOf<EmptyTypeList, List, true> {
  static constexpr bool Value = true;
};

template <typename ListA, typename ListB>
struct TypeListSubsetOf<ListA, ListB, false> {
  static constexpr bool Value = false;
};

template <typename T, typename... Ts, typename List>
struct TypeListSubsetOf<TypeList<T, Ts...>, List>
    : TypeListSubsetOf<TypeList<Ts...>, List,
                       TypeListContains<List, T>::Value> {};

// Finds the first element satisfying Pred

template <template <typename> class Pred, typename... Ts>
struct TypeListFindImpl;

template <template <typename> class Pred> struct TypeListFindImpl<Pred> {
  using Type = none_t;
};

template <template <typename> class Pred, typename T, typename... Ts>
struct TypeListFindImpl<Pred, T, Ts...> {
  using Type = typename std::conditional<
      Pred<T>::Value, T, typename TypeListFindImpl<Pred, Ts...>::Type>::type;
};

template <typename List, template <typename> class Pred>
struct TypeListFind;

template <typename... Ts, template <typename> class Pred>
struct TypeListFind<TypeList<Ts...>, Pred> {
  using Type = typename TypeListFindImpl<Pred, Ts...>::Type;
};

} // End namespace rosa

#endif // ROSA_SUPPORT_TYPE_LIST_HPP

