#pragma once

#include <alib/tuple>
#include <alib/array>
#include <alib/typeinfo>

#include <abstraction/OperationAbstraction.hpp>
#include <abstraction/ValueHolderInterface.hpp>

namespace abstraction {

namespace detail {

template < class ... ParamTypes, class F, class Tuple, std::size_t... I >
constexpr decltype ( auto ) apply_impl ( F && f, Tuple && t, std::index_sequence < I ... > ) {
	return std::invoke ( std::forward < F > ( f ), abstraction::retrieveValue < ParamTypes > ( std::get < I > ( std::forward < Tuple > ( t ) ) ) ... );
}

}  // namespace detail

template < class ... ParamTypes, class F, class Tuple >
constexpr decltype ( auto ) apply ( F && f, Tuple && t ) {
	return detail::apply_impl < ParamTypes ... > ( std::forward < F > ( f ), std::forward < Tuple > ( t ), std::make_index_sequence < sizeof ... ( ParamTypes ) > { } );
}

template < class ... Params >
static ext::type_index paramType ( unsigned index ) {
	ext::type_index res ( typeid ( void ) );

	auto lambda = [ & ] ( auto I ) {
		res = ext::type_index ( typeid ( std::tuple_element_t < decltype ( I )::value, std::tuple < Params ... > > ) );
	};

	ext::constexpr_switch < sizeof ... ( Params ) > ( index, lambda );

	return res;
}

template < class ... Params >
abstraction::TypeQualifiers::TypeQualifierSet paramTypeQualifiers ( unsigned index ) {
	abstraction::TypeQualifiers::TypeQualifierSet res = abstraction::TypeQualifiers::TypeQualifierSet::NONE;

	auto lambda = [ & ] ( auto I ) {
		res = abstraction::TypeQualifiers::typeQualifiers < std::tuple_element_t < decltype ( I )::value, std::tuple < Params ... > > > ( );
	};

	ext::constexpr_switch < sizeof ... ( Params ) > ( index, lambda );

	return res;
}

} /* namespace abstraction */

