#pragma once

#include <type_traits>


template <auto ...>
struct Term;


// Single item: field (member or method) of _T.
template <typename _T, typename _R, _R(_T::*_field)>
struct Term<_field> {
  using Operand = std::decay_t<_T>;
  using Result = std::conditional_t<
    std::is_function_v<_R>,
    std::invoke_result_t<decltype(_field), _T>,
    _R const &
  >;

  constexpr
  Result operator()(Operand const &operand) const {
    if constexpr (std::is_function_v<_R>) {
      return (operand.*_field)();
    } else {
      return operand.*_field;
    }
  }
};


// Single item: function of _T.
template <typename _T, typename _R, _R(*_function)(_T &)>
struct Term<_function> {
  using Operand = std::decay_t<_T>;
  using Result = _R;

  constexpr
  Result operator()(Operand const &operand) const {
    return _function(operand);
  }

  constexpr
  Result operator()(Operand &operand) const {
    return _function(operand);
  }
};


// Multiple items: recursively forward the result of the first term to the remaining ones.
template <auto _head, auto ..._tail>
struct Term<_head, _tail...> {
  using Operand = std::decay_t<typename Term<_head>::Operand>;
  using Result = typename Term<_tail...>::Result;

  constexpr
  Result operator()(Operand const &operand) const {
    return Term<_tail...>{}(Term<_head>{}(operand));
  }

  constexpr
  Result operator()(Operand &operand) const {
    return Term<_tail...>{}(Term<_head>{}(operand));
  }
};


//
// Equality compare
//

template <typename _L, typename _R>
struct Equal {
 static_assert(std::is_same_v<typename _L::Operand, typename _R::Operand>);

  using Operand = std::decay_t<typename _L::Operand>;
  using Result = bool;

  constexpr
  Result operator()(Operand const &operand) const {
    return _L{}(operand) == _R{}(operand);
  }

  constexpr
  Result operator()(Operand &operand) const {
    return _L{}(operand) == _R{}(operand);
  }
};


template <auto ..._l, auto ..._r>
constexpr
Equal<Term<_l ...>, Term<_r ...>> operator==(Term<_l ...>, Term<_r ...>) {
  return {};
}