/**
 * 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/>.
 *
 * https://gist.github.com/tibordp/6909880
 *
 * Created on: May 16, 2019
 * Created: Jan Travnicek
 */

#pragma once

#include <optional>
#include <sstream>

namespace ext {

template < typename T >
class optional : public std::optional < T > {
public:
	/**
	 * Inherit constructors of the standard optional
	 */
	using std::optional< T >::optional; // NOLINT(modernize-use-equals-default)

	/**
	 * Inherit operator = of the standard optional
	 */
	using std::optional< T >::operator =;
#ifndef __clang__

	/**
	 * Default constructor needed by g++ since it is not inherited
	 */
	optional ( ) = default;

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

	/**
	 * Move constructor needed by g++ since it is not inherited
	 */
	optional ( optional && other ) = default;

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

	/**
	 * Move operator = needed by g++ since it is not inherited
	 */
	optional & operator = ( const optional & other ) = default;
#endif

	// Comparisons between optional values.
	template < typename U >
	constexpr bool operator == ( const optional < U > & rhs ) const{
		const optional < T > & lhs = * this;
		return static_cast < bool > ( lhs ) == static_cast < bool > ( rhs ) && ( ! lhs || * lhs == * rhs);
	}

	template < std::three_way_comparable_with < T > U >
	constexpr std::compare_three_way_result_t < T, U > operator <=> ( const optional < U > & y ) const {
		const optional < T > & x = * this;
		return x && y ? *x <=> *y : static_cast < bool > ( x ) <=> static_cast < bool > ( y );
	}

	// Comparisons with nullopt.
	constexpr bool operator==( std::nullopt_t ) const noexcept {
		const optional < T > & lhs = * this;
		return ! lhs;
	}

	constexpr std::strong_ordering operator <=> ( std::nullopt_t ) const noexcept {
		const std::optional < T > & x = * this;
		return x <=> std::nullopt;
	}

	// Comparisons with value type.
	template < typename U >
	constexpr bool operator == ( const U & rhs ) const {
		const optional < T > & lhs = * this;
		return lhs && *lhs == rhs;
	}

	template < typename U >
	constexpr std::compare_three_way_result_t < T, U > operator <=> ( const U & y ) const {
		const optional < T > & x = * this;
		return static_cast < bool > ( x ) ? *x <=> y : std::strong_ordering::less;
	}

};

/**
 * \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 < 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 < T > & value ) {
	std::stringstream ss;
	ss << value;
	return ss.str();
}

} /* namespace ext */

