/*
 * optional.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 7, 2016
 * Author: Jan Travnicek
 */

#ifndef __OPTIONAL_REF_HPP_
#define __OPTIONAL_REF_HPP_

#include <optional>
#include <memory>

namespace ext {

template < class T >
class optional_ref {
	T * m_value;

public:
	optional_ref ( ) : optional_ref ( std::nullopt ) { }
	optional_ref ( std::nullopt_t ) : m_value ( nullptr ) { }

	optional_ref ( T & val ) : m_value ( std::addressof ( val ) ) {
	}

	template < class U >
	optional_ref ( const optional < U > & other ) : optional_ref ( other.value ( ) ) {
	}

	template < class U >
	optional_ref ( optional < U > && other ) : optional_ref ( std::move ( other ).value ( ) ) {
	}

	optional_ref & operator = ( const optional_ref & ) = delete;
	optional_ref & operator = ( optional_ref && ) = delete;

	optional_ref ( const optional_ref & other ) = default;
	optional_ref ( optional_ref && other ) noexcept = default;

	~ optional_ref ( ) noexcept = default;

	const T * operator-> ( ) const {
		return m_value;
	}

	T * operator-> ( ) {
		return m_value;
	}

	const T & operator * ( ) const & {
		return value ( );
	}

	T & operator * ( ) & {
		return value ( );
	}

	const T && operator * ( ) const && {
		return std::move ( value ( ) );
	}

	T && operator * ( ) && {
		return std::move ( value ( ) );
	}

	explicit operator bool ( ) const noexcept {
		return has_value ( );
	}

	bool has_value ( ) const noexcept {
		return m_value != nullptr;
	}

	T & value ( ) & {
		return * m_value;
	}

	const T & value ( ) const & {
		return * m_value;
	}

	T && value ( ) && {
		return std::move ( * m_value );
	}

	const T && value ( ) const && {
		return std::move ( * m_value );
	}

	const T & value_or ( const T & default_value ) const & {
		if ( has_value ( ) )
			return value ( );
		else
			return default_value;
	}

	T & value_or ( T & default_value ) & {
		if ( has_value ( ) )
			return value ( );
		else
			return default_value;
	}

	T && value_or ( T && default_value ) && {
		if ( has_value ( ) )
			return std::move ( value ( ) );
		else
			return default_value;
	}

	/**
	 * \brief
	 * Compares two optional_ref instances for equvalence.
	 *
	 * \param lhs the first instance to compare
	 * \param rhs the second instance to compare
	 *
	 * \return true if the two compared instance are equal, false othervise
	 */
	friend bool operator== ( const optional_ref < T >& lhs, const optional_ref < T >& rhs ) {
		return lhs.value ( ) == rhs.value ( );
	}

	/**
	 * \brief
	 * Compares two optional_ref instances for non equvalence.
	 *
	 * \param lhs the first instance to compare
	 * \param rhs the second instance to compare
	 *
	 * \return true if the two compared instance are not equal, false othervise
	 */
	friend bool operator!= ( const optional_ref < T >& lhs, const optional_ref < T >& rhs ) {
		return lhs.value ( ) != rhs.value ( );
	}

	/**
	 * \brief
	 * Compares two optional_ref instances by less relation.
	 *
	 * \param lhs the first instance to compare
	 * \param rhs the second instance to compare
	 *
	 * \return true if the first compared instance is less than the other instance, false othervise
	 */
	friend bool operator<  ( const optional_ref < T >& lhs, const optional_ref < T >& rhs ) {
		return lhs.value ( ) < rhs.value ( );
	}

	/**
	 * \brief
	 * Compares two optional_ref instances by less or equal relation.
	 *
	 * \param lhs the first instance to compare
	 * \param rhs the second instance to compare
	 *
	 * \return true if the first compared instance is less than or equal than the other instance, false othervise
	 */
	friend bool operator<= ( const optional_ref < T >& lhs, const optional_ref < T >& rhs ) {
		return lhs.value ( ) <= rhs.value ( );
	}

	/**
	 * \brief
	 * Compares two optional_ref instances by greater relation.
	 *
	 * \param lhs the first instance to compare
	 * \param rhs the second instance to compare
	 *
	 * \return true if the first compared instance is greater than the other instance, false othervise
	 */
	friend bool operator>  ( const optional_ref < T >& lhs, const optional_ref < T >& rhs ) {
		return lhs.value ( ) > rhs.value ( );
	}

	/**
	 * \brief
	 * Compares two optional_ref instances by greater or equal relation.
	 *
	 * \param lhs the first instance to compare
	 * \param rhs the second instance to compare
	 *
	 * \return true if the first compared instance is greater than or equal than the other instance, false othervise
	 */
	friend bool operator>= ( const optional_ref < T >& lhs, const optional_ref < T >& rhs ) {
		return lhs.value ( ) >= rhs.value ( );
	}

};

/**
 * \brief
 * Specialisation of the compare structure implementing the three-way comparison.
 *
 * \tparam T type in the optional
 */
template < typename T >
struct compare < ext::optional_ref < T > > {

	/**
	 * \brief
	 * Implementation of the three-way comparison.
	 *
	 * \param first the left operand of the comparison
	 * \param second the right operand of the comparison
	 *
	 * \return negative value of left < right, positive value if left > right, zero if left == right
	 */
	int operator ( ) ( const ext::optional_ref < T > & first, const ext::optional_ref < T > & second ) const {
		if ( ! first && ! second )
			return 0;
		if ( first && ! second )
			return 1;
		if ( ! first && second )
			return -1;

		static ext::compare < T > comp;
		return comp ( first.value ( ), second.value ( ) );
	}
};

/**
 * \brief
 * Operator to print the optional to the output stream.
 *
 * \param out the output stream
 * \param optional the optional to print
 *
 * \tparam T the type of value inside the optional
 *
 * \return the output stream from the \p out
 */
template< class T >
std::ostream & operator << ( std::ostream & out, const ext::optional_ref < T > & optional ) {
	if ( ! optional )
		return out << "void";
	else
		return out << optional.value ( );
}


/**
 * \brief
 * Overload of to_string function.
 *
 * \param value the optional to be converted to string
 *
 * \tparam T the type of values inside the optional
 *
 * \return string representation
 */
template < class T >
std::string to_string ( const ext::optional_ref < T > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

} /* namespace ext */

#endif /* __OPTIONAL_REF_HPP_ */
