#ifndef TILE_BASE_FUTURE_TYPES_H
#define TILE_BASE_FUTURE_TYPES_H

#pragma once

#include "tile/base/internal/meta.h"
#include <type_traits>

namespace tile {
namespace future {
template<typename... Ts>
struct Types {};

// Get type at the specify location
template<typename T, std::size_t I>
struct types_at;

template<typename T, typename... Ts>
struct types_at<Types<T, Ts...>, 0> {
    using type = T;
};

template<std::size_t I, typename T, typename... Ts>
struct types_at<Types<T, Ts...>, I> : types_at<Types<Ts...>, I - 1> {};

template<typename T, std::size_t I>
using types_at_t = typename types_at<T, I>::type;
static_assert(std::is_same<types_at_t<Types<int, char, void>, 1>, char>::value, "Compiler support error: types_at_t");

template<typename... Ts>
struct types_cat;

template<>
struct types_cat<> {
    using type = Types<>;
};

template<typename... Ts>
struct types_cat<Types<Ts...>> {
    using type = Types<Ts...>;
};

template<typename... Ts, typename... Us, typename... Vs>
struct types_cat<Types<Ts...>, Types<Us...>, Vs...> : types_cat<Types<Ts..., Us...>, Vs...> {};

template<typename... Ts>
using types_cat_t = typename types_cat<Ts...>::type;

template<typename T, typename U>
struct types_contains;

template<typename U>
struct types_contains<Types<>, U> : std::false_type {};

template<typename T, typename U>
struct types_contains<Types<T>, U> : std::is_same<T, U> {};

template<typename T, typename U, typename... Ts>
struct types_contains<Types<T, Ts...>, U>
    : std::conditional<std::is_same<T, U>::value, std::true_type, types_contains<Types<Ts...>, U>>::type {};

static_assert(types_contains<Types<int, char>, char>::value, "");
static_assert(!types_contains<Types<int, char>, char *>::value, "");

// Erase all occurrance of give type from Types<...>
template<typename T, typename U>
struct types_erase;

template<typename U>
struct types_erase<Types<>, U> {
    using type = Types<>;
};

template<typename T, typename U, typename... Ts>
struct types_erase<Types<T, Ts...>, U>
    : types_cat<internal::conditional_t<!std::is_same<T, U>::value, Types<T>, Types<>>,
                typename types_erase<Types<Ts...>, U>::type> {};

template<typename T, typename U>
using types_erase_t = typename types_erase<T, U>::type;

static_assert(std::is_same<types_erase_t<Types<int, void, char>, void>, Types<int, char>>::value, "");
static_assert(std::is_same<types_erase_t<Types<>, void>, Types<>>::value, "");
static_assert(std::is_same<types_erase_t<Types<int, char>, void>, Types<int, char>>::value, "");

}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_TYPES_H
