/*
 * type_traits.hpp
 *
 * This file is part of Algorithms library toolkit.
 * Copyright (C) 2017 Jan Travnicek (jan.travnicek@fit.cvut.cz)

 * Algorithms library toolkit is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Algorithms library toolkit is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Algorithms library toolkit.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created on: Feb 28, 2014
 * Author: Jan Travnicek
 */

#ifndef __TYPE_TRAITS_HPP_
#define __TYPE_TRAITS_HPP_

#include <type_traits>
#include <utility>
#include <cstdlib>

namespace ext {

	/**
	 * \brief
	 * Type trait to determine existence of clone method. A boolean field namd value is set to true if provided class (possibly cv-qualified and via reference) has clone method.
	 *
	 * \tparam the type to examine
	 */
	template<class T>
	struct has_clone {
	private:
		template < class U >
		static std::true_type test ( U * data, typename std::enable_if < std::is_pointer_v < decltype ( data->clone ( ) ) > >::type * = nullptr );
		static std::false_type test ( ... );
	public:
		/**
		 * \brief
		 * True if the type decayed type T has clone method.
		 */
		static const bool value = decltype ( has_clone::test ( std::declval < std::decay_t < T > * > ( ) ) )::value;
	};


	/**
	 * \brief
	 * Positive supports test implementation. The test is designed to detect call availability on callable F with parameters Ts ...
	 *
	 * \tparam F the type of callable
	 * \tparam Ts ... call parameter types
	 *
	 * \returns std::true_type
	 */
	template < class F, class ... Ts, typename = decltype ( std::declval < F > ( ) ( std::declval < Ts > ( ) ... ) ) >
	std::true_type supports_test ( const F &, const Ts & ... );

	/**
	 * \brief
	 * Negative supports test implementation. The test is designed to be callback when the positive test failed.
	 *
	 * \returns std::false_type
	 */
	std::false_type supports_test ( ... );

	/**
	 * Base implementation of the call test support.
	 */
	template < class > struct supports;

	/**
	 * Specialisation of the call test support for F type be a callable.
	 *
	 * \tparam F the type of callable
	 * \tparam Ts ... call parameter types
	 *
	 * If the call with parameter types Ts ... is possible on callable F, the class provides a value of type true, othervise a false value filed is provided.
	 */
	template < class F, class ... Ts > struct supports < F ( Ts ... ) > : decltype ( supports_test ( std::declval < F > ( ), std::declval < Ts > ( ) ... ) ) { };

// ----------------------------------------------------------------------------------------------------

	/**
	 * \brief
	 * Trait to test whether type T is in a types pack. The trait provides field value set to true if the type T is in pack of types Ts ..., false othervise.
	 *
	 * \tparam T the type to look for
	 * \tparam Ts ... the types pack to look in
	 */
	template < typename T, typename ... Ts >
	struct is_in;

	/**
	 * \brief
	 * Trait to test whether type T is in a types pack. The trait provides field value set to true if the type T is in pack of types Ts ..., false othervise.
	 *
	 * Specialisation for empty pack, in which case the field is set to false.
	 *
	 * \tparam T the type to look for
	 */
	template < typename T >
	struct is_in < T > : std::false_type { };

	/**
	 * \brief
	 * Trait to test whether type T is in a types pack. The trait provides field value set to true if the type T is in pack of types Ts ..., false othervise.
	 *
	 * Specialisation for non-empty pack where the first type in the pack is different from the tested type, in which case the field is set to result of the recursive test on shorter pack.
	 *
	 * \tparam T the type to look for
	 */
	template < typename T, typename U, typename ... Ts >
	struct is_in < T, U, Ts ... > : is_in < T, Ts ... > { };

	/**
	 * \brief
	 * Trait to test whether type T is in a types pack. The trait provides field value set to true if the type T is in pack of types Ts ..., false othervise.
	 *
	 * Specialisation for non-empty pack where the first type in the pack is the same as the tested type, in which case the field is set to true.
	 *
	 * \tparam T the type to look for
	 */
	template < typename T, typename ... Ts >
	struct is_in < T, T, Ts ... > : std::true_type { };

// ----------------------------------------------------------------------------------------------------

	template < bool value, typename type = int >
	using require = typename std::enable_if < value, type >::type;

// ----------------------------------------------------------------------------------------------------

	template < bool value>
	using boolean = typename std::conditional < value, std::true_type, std::false_type >::type;

	template < class ... Ts >
	struct casional;

	template < >
	struct casional < > {
		typedef void type;
	};

	template < class T >
	struct casional < T > {
		typedef T type;
	};

	template < class R, class ... Ts >
	struct casional < std::true_type, R, Ts ... > : public casional < R > {
	};

	template < class R, class ... Ts >
	struct casional < std::false_type, R, Ts ... > : public casional < Ts ... > {
	};

} /* namespace ext */

#endif /* __TYPE_TRAITS_HPP_ */
