#ifndef META_UTILS_H
#define META_UTILS_H

#include <tuple>

#define CHECK_TEST_RES(pred) MetaUtils::check(pred, __PRETTY_FUNCTION__)

/// @todo Merge with Traits.h
namespace MetaUtils {


  template <class Func, std::size_t... Is>
  constexpr void staticForImpl(Func &&f,
                               std::index_sequence<Is...>) {
    // Use C++17 fold expressions to apply Func f
    // to all Is... in sequence
    //
    // Won't work on empty tuple (Binning is not intended to work with 0 dimensions).
    //
    // std::integral_constant used to encode value into type, otherwise it's not
    // possible to use Is inside functor as consant expression
    // (std::get<Is> etc. won't work)

    ((void)f(std::integral_constant<std::size_t, Is>()), ...);
  }

  /// @brief Used to apply functor on tuple. Calls lambda with integral constant,
  /// which can be used to query tuple elements via std::get<I>(), and thus iterate
  /// over tuple.
  ///
  /// To use, create lambda which captures every necessary variable by referenrece (or value)
  /// and has (auto I) parameter. Then use I inside lambda to call templated functions.
  template <size_t N, class Func>
  constexpr void staticFor(Func&& f) {
    staticForImpl(std::forward<Func>(f),
                  std::make_index_sequence<N>{});
  }


  /// @brief Logical conjuction implementation.
  /// @note Source: https://en.cppreference.com/w/cpp/types/conjunction#Possible_implementation
  template<class...> struct conjunction : std::true_type { };
  template<class B1> struct conjunction<B1> : B1 { };
  template<class B1, class... Bn>
  struct conjunction<B1, Bn...>
      : std::conditional_t<bool(B1::value), conjunction<Bn...>, B1> {};

  struct nonesuch {
    ~nonesuch() = delete;
    nonesuch(nonesuch const&) = delete;
    void operator=(nonesuch const&) = delete;
  };

  namespace operatorTraits {
    template<class T>
    using addition_assignment_t = decltype(std::declval<T&>() += std::declval<const T&>());
  }

  /// @brief Traits detection. Check's whether expression is valid for type T.
  /// @note Source: https://en.cppreference.com/w/cpp/experimental/is_detected#Possible_implementation
  namespace detail {
    template <class Default, class AlwaysVoid,
              template<class...> class Op, class... Args>
    struct detector {
      using value_t = std::false_type;
      using type = Default;
    };

    template <class Default, template<class...> class Op, class... Args>
    struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
      using value_t = std::true_type;
      using type = Op<Args...>;
    };

  } // namespace detail

  /// @note Anonymous namespace to limit visibility to this file
  namespace {
    template <typename, template <typename...> class>
    struct is_instance_impl : public std::false_type {};

    template <template <typename...> class U, typename...Ts>
    struct is_instance_impl<U<Ts...>, U> : public std::true_type {};
  }

  /// @brief Detects if type T is an instance of template U
  template <typename T, template <typename ...> class U>
  using is_instance = is_instance_impl<std::decay_t<T>, U>;

  template <template<class...> class Op, class... Args>
  using is_detected = typename detail::detector<nonesuch, void, Op, Args...>::value_t;

  template <template<class...> class Op, class... Args>
  using detected_t = typename detail::detector<nonesuch, void, Op, Args...>::type;

  template <class Default, template<class...> class Op, class... Args>
  using detected_or = detail::detector<Default, void, Op, Args...>;

  template <class Expected, template<class...> class Op, class... Args>
  using is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;

  template< template<class...> class Op, class... Args >
  constexpr bool is_detected_v = is_detected<Op, Args...>::value;

  /// @brief checks whether if T satisfies user defined Concept
  ///
  /// @note Concept check should appear inside body of type's member function
  /// or outside of type, since otherwise type is considered incomplete.
  template <typename T, template <class U> class Concept>
  constexpr bool checkConcept() {
    return Concept<T>::checkResult::value;
  }

  inline int check(bool pred, const char* funcName) {
    if (pred == false)
      std::clog << funcName << " FAILED\n";
    return (int)(pred ? EXIT_SUCCESS : EXIT_FAILURE);
  }


  namespace {

    template <size_t, size_t>
    struct _tuple {
      template <typename T>
      static auto remove (const T& t) {
        return std::make_tuple(t);
      }
    };

    template <size_t N>
    struct _tuple<N, N> {
      template <typename T>
      static std::tuple<> remove (const T&) {
        return {};
      }
    };

    template<size_t N, typename T, size_t... Is>
    auto _removeTupleElement(const T& tup, std::index_sequence<Is...>) {
      return std::tuple_cat( _tuple<Is, N>::remove(std::get<Is>(tup))...);
    }

  }

  template<size_t I, typename... Ts>
  auto removeTupleElement(const std::tuple<Ts...>& tup) {
    return _removeTupleElement<I>(tup, std::make_index_sequence<sizeof...(Ts)>{});
  }


}

#endif
