/**************************************************************************//**
 *
 * \file rosa/support/squashed_int.hpp
 *
 * \author David Juhasz (david.juhasz@tuwien.ac.at)
 *
 * \date 2017
 *
 * \brief Facilities for squashing integer types into standard equivalents.
 *
 * \note This implementation is partially based on the `squashed_int`
 *       implementation of CAF.
 * \todo Check license.
 *
 ******************************************************************************/

#ifndef ROSA_SUPPORT_SQUASHED_INT_HPP
#define ROSA_SUPPORT_SQUASHED_INT_HPP

#include "rosa/support/type_list.hpp"
#include "rosa/support/type_pair.hpp"

namespace rosa {

/// Compile-time list of integer types.
///
/// \note This list is used to select a proper type as `rosa::type_nr_t`, always
/// make sure that `rosa::type_nr_t` remains correct whenever changing the list.
using IntegerTypesBySize = TypeList< // bytes
    none_t,                          // 0
    TypePair<int8_t, uint8_t>,       // 1
    TypePair<int16_t, uint16_t>,     // 2
    none_t,                          // 3
    TypePair<int32_t, uint32_t>,     // 4
    none_t,                          // 5
    none_t,                          // 6
    none_t,                          // 7
    TypePair<int64_t, uint64_t>      // 8
    >;

/// Squashes integer types into `[u]int_[8|16|32|64]_t` equivalents.
///
/// The squashed type for a type `T` can be obtained as \code
/// typename SquashedInt<T>::Type
/// \endcode
///
/// \tparam T the integer type to squash
///
/// \pre `T` is an integral type:\code
/// std::is_integral<T>::value
/// \endcode
template <typename T> struct SquashedInt {
  STATIC_ASSERT((std::is_integral<T>::value), "squashing a non-integral type");
  using TPair = typename TypeListAt<IntegerTypesBySize, sizeof(T)>::Type;
  using Type =
      typename std::conditional<std::is_signed<T>::value, typename TPair::First,
                                typename TPair::Second>::type;
};

/// Convenience alias for obtaining a squashed integer type.
template <typename T>
using squashed_int_t = typename SquashedInt<T>::Type;

/// \defgroup SquashedType
/// \brief Squashes a type.
///
/// The squashed type for a type `T` can be obtained as \code
/// typename SquashedType<T>::Type
/// \endcode
/// The used type `Type` is squashed with `SquashedInt` if `T` is integral,
/// and remains `T` otherwise.
///@{

/// Definition for the general case, used when `T` is not integral.
///
/// \tparam T the type to squash
/// \tparam IsIntegral Always use the default value!
template <typename T, bool IsIntegral = std::is_integral<T>::value>
struct SquashedType {
  using Type = T;
};

/// Specialization for the case when `T` is integral.
///
/// \tparam T the type to squash
template <typename T>
struct SquashedType<T, true> {
  using Type = squashed_int_t<T>;
};

///@}

/// Convenience alias for obtaining a squashed type.
template <typename T>
using squashed_t = typename SquashedType<T>::Type;

/// \defgroup SquashedTypeList
/// \brief Squashes a `rosa::TypeList` elementwise.
///
/// Replaces all types in a `rosa::TypeList` with their corresponding squashed
/// types by using `rosa::SquashedType`. The squashed `rosa::TypeList`
/// corresponding to `List` can be obtained as \code
/// typename SquashedTypeList<List>::Type
/// \endcode
///@{

/// Declaration of the template.
///
/// \tparam List `TypeList` to squash
template <typename List> struct SquashedTypeList;

// Specialization for the case of `rosa::EmptyTypeList`.
template <> struct SquashedTypeList<EmptyTypeList> {
  using Type = EmptyTypeList;
};

/// Specialization for non-empty `rosa::TypeList`.
template <typename T, typename... Ts>
struct SquashedTypeList<TypeList<T, Ts...>> {
  using Type = typename TypeListPush<
      squashed_t<T>, typename SquashedTypeList<TypeList<Ts...>>::Type>::Type;
};

///@}

} // End namespace rosa

#endif // ROSA_SUPPORT_SQUASHED_INT_HPP

