#pragma once

#include <type_traits>
#include "utility"
#include "cstddef"

namespace std
{
#if __cplusplus >= 201103L
template <typename... Types>
class tuple;

template <>
class tuple<>
{
public:
  tuple() = default;
  void swap(tuple<> &)
  {
  }
};

template <typename Head, typename... Tail>
class tuple<Head, Tail...>
{
public:
  Head head;
  tuple<Tail...> tail;

  constexpr tuple() = default;

  constexpr tuple(const Head &head, const Tail &...tail)
    : head(head), tail(tail...)
  {
  }

  constexpr tuple(const tuple &other) : head(other.head), tail(other.tail)
  {
  }

  template <
    typename UHead,
    typename... UTail,
    typename = std::enable_if_t<
      std::is_convertible<UHead, Head>::value &&
      (... && std::is_convertible<UTail, Tail>::value)>>
  constexpr tuple(const tuple<UHead, UTail...> &other)
    : head(other.head), tail(other.tail)
  {
  }

  tuple &operator=(const tuple &other)
  {
    head = other.head;
    tail = other.tail;
    return *this;
  }

  void swap(tuple &other)
  {
    Head temp = this->head;
    this->head = other.head;
    other.head = temp;
    this->tail.swap(other.tail);
  }
};

template <std::size_t I, typename Tuple>
struct tuple_element;

template <typename Head, typename... Tail>
struct tuple_element<0, tuple<Head, Tail...>>
{
  using type = Head;
};

template <std::size_t I, typename Head, typename... Tail>
struct tuple_element<I, tuple<Head, Tail...>>
{
  using type = typename tuple_element<I - 1, tuple<Tail...>>::type;
};

template <typename Tuple>
struct tuple_size;

template <typename... Types>
struct tuple_size<tuple<Types...>>
  : std::integral_constant<std::size_t, sizeof...(Types)>
{
};

template <std::size_t I, typename... Types>
constexpr typename tuple_element<I, tuple<Types...>>::type &
get(tuple<Types...> &t) noexcept
{
  if constexpr (I == 0)
    return t.head;
  else
    return get<I - 1>(t.tail);
}

template <std::size_t I, typename... Types>
constexpr const typename tuple_element<I, tuple<Types...>>::type &
get(const tuple<Types...> &t) noexcept
{
  if constexpr (I == 0)
    return t.head;
  else
    return get<I - 1>(t.tail);
}

// operator==
template <typename... T1, typename... T2>
constexpr bool operator==(const tuple<T1...> &lhs, const tuple<T2...> &rhs)
{
  return (std::get<0>(lhs) == std::get<0>(rhs)) && (lhs.tail == rhs.tail);
}

template <>
constexpr bool operator==(const tuple<> &, const tuple<> &)
{
  return true;
}

// operator!=
template <typename... T1, typename... T2>
constexpr bool operator!=(const tuple<T1...> &lhs, const tuple<T2...> &rhs)
{
  return !(lhs == rhs);
}

// operator<
template <typename... T1, typename... T2>
constexpr bool operator<(const tuple<T1...> &lhs, const tuple<T2...> &rhs)
{
  if (std::get<0>(lhs) < std::get<0>(rhs))
    return true;
  if (std::get<0>(lhs) > std::get<0>(rhs))
    return false;
  return lhs.tail < rhs.tail;
}

template <>
constexpr bool operator<(const tuple<> &, const tuple<> &)
{
  return false;
}

// operator<=
template <typename... T1, typename... T2>
constexpr bool operator<=(const tuple<T1...> &lhs, const tuple<T2...> &rhs)
{
  return !(rhs < lhs);
}

// operator>
template <typename... T1, typename... T2>
constexpr bool operator>(const tuple<T1...> &lhs, const tuple<T2...> &rhs)
{
  return rhs < lhs;
}

// operator>=
template <typename... T1, typename... T2>
constexpr bool operator>=(const tuple<T1...> &lhs, const tuple<T2...> &rhs)
{
  return !(lhs < rhs);
}

// Minimal reference_wrapper needed by unwrap_refwrapper below.
// The full implementation lives in <functional>; this stub is sufficient
// for make_tuple to strip reference_wrapper<T> → T& at compile time.
template <class T>
class reference_wrapper
{
  T *ptr_;

public:
  using type = T;
  reference_wrapper(T &ref) noexcept : ptr_(&ref)
  {
  }
  operator T &() const noexcept
  {
    return *ptr_;
  }
  T &get() const noexcept
  {
    return *ptr_;
  }
};

template <class T>
struct unwrap_refwrapper
{
  using type = T;
};

template <class T>
struct unwrap_refwrapper<std::reference_wrapper<T>>
{
  using type = T &;
};

template <class T>
using unwrap_decay_t =
  typename unwrap_refwrapper<typename std::decay<T>::type>::type;

template <class... Types>
constexpr // since C++14
  tuple<unwrap_decay_t<Types>...>
  make_tuple(Types &&...args)
{
  return tuple<unwrap_decay_t<Types>...>(std::forward<Types>(args)...);
}

#endif

} // namespace std
