/*
 * functional.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: Apr 1, 2013
 * Author: Jan Travnicek
 */

#ifndef _FUNCTIONAL_HPP_
#define _FUNCTIONAL_HPP_

#include <functional>

#include "type_traits.hpp"
#include "typeindex.h"

namespace ext {

template < class T >
class PolyComp {
	const T & m_inst;

public:
	explicit PolyComp ( const T & inst ) : m_inst ( inst ) {
	}

	template < class R >
	friend bool operator < ( const PolyComp < T > & first, const R & second ) {
		if constexpr ( supports < std::less < > ( T, R ) >::value )
			return first.m_inst < second;
		else
			return std::type_index ( typeid ( T ) ) < std::type_index ( typeid ( R ) );
	}

	template < class R >
	friend bool operator < ( const R & first, const PolyComp < T > & second ) {
		if constexpr ( supports < std::less < > ( R, T ) >::value )
			return first < second.m_inst;
		else
			return std::type_index ( typeid ( R ) ) < std::type_index ( typeid ( T ) );
	}

};

template < class T >
PolyComp < T > poly_comp ( const T & inst ) {
	return PolyComp < T > ( inst );
}

template < class ... Ts >
class SliceComp {
	std::tuple < const Ts & ... > m_data;

	template < class T, size_t ... I >
	static bool compare ( const SliceComp < Ts ... > & first, const T & second, std::index_sequence < I ... > ) {
		return first.m_data < std::tie ( std::get < I > ( second ) ... );
	}

	template < class T, size_t ... I >
	static bool compare ( const T & first, const SliceComp < Ts ... > & second, std::index_sequence < I ... > ) {
		return std::tie ( std::get < I > ( first ) ... ) < second.m_data;
	}

public:
	explicit SliceComp ( const Ts & ... data ) : m_data ( data ... ) {
	}

	template < class T >
	friend bool operator < ( const SliceComp < Ts ... > & first, const T & second ) {
		return compare < T > ( first, second, std::make_index_sequence < sizeof ... ( Ts ) > { } );
	}

	template < class T >
	friend bool operator < ( const T & first, const SliceComp < Ts ... > & second ) {
		return compare < T > ( first, second, std::make_index_sequence < sizeof ... ( Ts ) > { } );
	}

};

template < class ... Ts >
SliceComp < Ts ... > slice_comp ( const Ts & ... inst ) {
	return SliceComp < Ts ... > ( inst ... );
}

/**
 * \brief
 * Class extending the reference wrapper class from the standard library. Original reason is to allow its use with standard stream aggregation class.
 *
 * The class mimics the behavior of the reference wrapper from the standatd library.
 *
 * \tparam T the type of the reference inside the reference wrapper
 */
template < class T >
class reference_wrapper : public std::reference_wrapper < T > {
public:
	/**
	 * Inherit constructors of the standard reference_wrapper.
	 */
	using std::reference_wrapper < T >::reference_wrapper;

	/**
	 * Inherit operator = of the standard reference_wrapper.
	 */
	using std::reference_wrapper < T >::operator =;

#ifndef __clang__

	/**
	 * Copy constructor needed by g++ since it is not inherited.
	 */
	reference_wrapper ( const reference_wrapper & other ) = default;

	/**
	 * Copy operator = needed by g++ since it is not inherited.
	 */
	reference_wrapper & operator = ( const reference_wrapper & other ) = default;

	/**
	 * Overload to allow wrapping object in assignment.
	 */
	reference_wrapper & operator = ( T & object ) {
		return * this = reference_wrapper < T > ( object );
	}
#endif

	/**
	 * Inherit the accessor of the internal reference.
	 */
	using std::reference_wrapper < T >::operator T &;

	/**
	 * \brief
	 * Specialisation of less than operator for reference_wrapper vector.
	 *
	 * \param other the second compared value
	 *
	 * \return true if the this is smaller than the other, false othervise
	 */
	bool operator < ( const reference_wrapper < T > & other ) const {
		return this->get ( ) < other.get ( );
	}

	/**
	 * \brief
	 * Specialisation of more than operator for reference_wrapper vector.
	 *
	 * \param other the second compared value
	 *
	 * \return true if the this is bigger than the other, false othervise
	 */
	bool operator > ( const reference_wrapper < T > & other ) const {
		return other < * this;
	}

	/**
	 * \brief
	 * Specialisation of less than or equal operator for reference_wrapper vector.
	 *
	 * \param other the second compared value
	 *
	 * \return true if the this is smaller than or equal to the other, false othervise
	 */
	bool operator <= ( const reference_wrapper < T > & other ) const {
		return ! ( * this > other );
	}

	/**
	 * \brief
	 * Specialisation of more than or equal operator for reference_wrapper vector.
	 *
	 * \param other the second compared value
	 *
	 * \return true if the this is bigger than or equal to the other, false othervise
	 */
	bool operator >= ( const reference_wrapper < T > & other ) const {
		return ! ( * this < other );
	}

	/**
	 * \brief
	 * Specialisation of equals operator for reference_wrapper vector.
	 *
	 * \param other the second compared value
	 *
	 * \return true if the this is equal to the other, false othervise
	 */
	bool operator == ( const reference_wrapper < T > & other ) const {
		return this->get ( ) == other.get ( );
	}

	/**
	 * \brief
	 * Specialisation of not equals operator for reference_wrapper vector.
	 *
	 * \param other the second compared value
	 *
	 * \return true if the this is not equal to the other, false othervise
	 */
	bool operator != ( const reference_wrapper < T > & other ) const {
		return ! ( * this == other );
	}
};

} /* namespace ext */

#endif /* _FUNCTIONAL_HPP_ */
