/*******************************************************************************
***  This file is part of Dux Rei Nummariae.                                 ***
***                                                                          ***
***  Copyright (C) 2017, 2018, 2019, 2020, 2021                              ***
***  John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me>                ***
***                                                                          ***
***  This program is free software: you can redistribute it and/or modify it ***
***  under the terms of the GNU Affero General Public License as published   ***
***  by the Free Software Foundation, either version 3 of the License, or    ***
***  (at your option) any later version.                                     ***
***                                                                          ***
***  This program 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 Affero General Public License for more details.             ***
***                                                                          ***
***  You should have received a copy of the GNU Affero General Public License***
***  along with this program. If not, see <http://www.gnu.org/licenses/>.    ***
*******************************************************************************/
#ifndef DRN_FOUNDATION_OPTIONAL_HPP_
#define DRN_FOUNDATION_OPTIONAL_HPP_

#include <QObject>
#include <QString>

#include <cassert>
#include <ostream>
#include <utility>


namespace drn
{
namespace foundation
{

/**
 * @brief Represents a type that may or may not contain a value.
 *
 * @tparam ValueType The expected type of the optional value. The optional type must be assignable,
 * able to be copied, moveable, tested for equality, stream insertable, and less-than comparable.
 */
template<typename ValueType>
class Optional final
{
	template<typename ValueStorageType>
	union OptionalStorage
	{
		constexpr OptionalStorage() noexcept;
		~OptionalStorage();

		quint8 dummy_;
		ValueStorageType value_;
	};

	void setValue(ValueType value);
	template<typename ... ConstructorArgs>
	void constructValue(ConstructorArgs&& ... args);

	bool hasValue_;
	OptionalStorage<ValueType> store_;

public:
	/**
	 * @brief Default constructor which creates an un-set optional.
	 */
	constexpr Optional() noexcept;
	/**
	 * @brief Constructor for setting an optional value via a copy.
	 *
	 * @param value The value to set for the optional.
	 */
	Optional(const ValueType& value);
	/**
	 * @brief Copy constructor for setting the optional value.
	 *
	 * @param other The other object to copy.
	 */
	Optional(const Optional& other);
	/**
	 * @brief Move constructor for setting the optional value.
	 *
	 * @param other The other object to move.
	 */
	Optional(Optional&& other) noexcept;
	/**
	 * @brief Constructor for setting an optional value via a move.
	 *
	 * @param value The value to set for the optional.
	 */
	Optional(ValueType&& value) noexcept;
	/**
	 * @brief Destructor that will invoke the stored object's destructor when there is a value.
	 */
	~Optional();
	/**
	 * @brief Assignment move operator for one optional into another.
	 *
	 * @param other The optional on the right-hand-side of the assignment operator to copy.
	 *
	 * @return The values of this object are replaced by the right-hand-side.
	 */
	Optional& operator=(const Optional& other);
	/**
	 * @brief Assignment move operator for one optional into another.
	 *
	 * @param other The optional on the right-hand-side of the assignment operator to move.
	 *
	 * @return The values of this object are replaced by the right-hand-side.
	 */
	Optional& operator=(Optional&& other) noexcept;
	/**
	 * @brief Assignment operator for the value type into the object optional type.
	 *
	 * @param value The value to assign into this object.
	 *
	 * @return The values of this object are replaced by the supplied value.
	 */
	Optional& operator=(const ValueType& value);
	/**
	 * @brief Assignment operator for the value type into the object optional type.
	 *
	 * @param value The value to assign and move into this object.
	 *
	 * @return The values of this object are replaced by the supplied value.
	 */
	Optional& operator=(ValueType&& value) noexcept;
	/**
	 * @brief Constructs the optional value type directly.
	 *
	 * @tparam ConstructorArguments The types for the arguments to the optional value type.
	 *
	 * @param args The values for the arguments in the optional value type constructor.
	 */
	template<typename ... ConstructorArguments>
	void emplace(ConstructorArguments&& ... args);
	/**
	 * @brief Indirection operator to retrieve the value in the optional.
	 *
	 * @return The value stored in the optional object.
	 */
	const ValueType& operator*() const;
	/**
	 * @brief Indirection operator to retrieve the value in the optional.
	 *
	 * @return The value stored in the optional object.
	 */
	ValueType& operator*();
	/**
	 * @brief Arrow operator to retrieve the value in the optional.
	 *
	 * @return The value stored in the optional object.
	 */
	const ValueType* operator->() const;
	/**
	 * @brief Arrow operator to retrieve the value in the optional.
	 *
	 * @return The value stored in the optional object.
	 */
	ValueType* operator->();
	/**
	 * @brief Checks to see if the optional has a value.
	 *
	 * @return true if is set, false if not.
	 */
	bool hasValue() const;
	/**
	 * @brief Resets the optional to a non-set state.
	 */
	void reset();
	/**
	 * @brief Equality operator to compare if two optional objects have the same values.
	 *
	 * @param other The other optional to compare against.
	 *
	 * @return True when both the current instance and the other instance have values and they are
	 * the same, or both are not set, false else-wise.
	 */
	bool operator==(const Optional<ValueType>& other) const;
	/**
	 * @brief Equality operator to compare if this instance is set to a specific value.
	 *
	 * @param value The value to compare against.
	 *
	 * @return True when the current instance has a value and it is the same as the supplied value,
	 * false else-wise.
	 */
	bool operator==(const ValueType& value) const;
	/**
	 * @brief Inequality operator to compare if two optional objects held values are not the same.
	 *
	 * @param other The other optional to compare against.
	 *
	 * @return True when either the current instance or the other instance do not have values or
	 * they are not the same, false else-wise.
	 */
	bool operator!=(const Optional<ValueType>& other) const;
	/**
	 * @brief Inequality operator to compare if this instance is not set to a specific value.
	 *
	 * @param value The value to compare against.
	 *
	 * @return True when either the current instance or the other instance do not have values or
	 * they are not the same, false else-wise.
	 */
	bool operator!=(const ValueType& value) const;
	/**
	 * @brief Less-than operator to compare if two optional objects held values are less-than the
	 * other.
	 *
	 * @param other The other optional to compare against.
	 *
	 * @return True when the current instance has a value and it is less than the other value that
	 * also has a value or the current instance is not set and the other value is set, false
	 * other wise.
	 */
	bool operator<(const Optional<ValueType>& other) const;
	/**
	 * @brief Less-than operator to compare if two optional objects held values are less-than the
	 * other.
	 *
	 * @param value The value to compare against.
	 *
	 * @return True when the current instance has a value and it is less than the other value or,
	 * the current instance is not set false other wise.
	 */
	bool operator<(const ValueType& value) const;
	/**
	 * @brief Provides the stored optional value or an alternative value.
	 *
	 * @param alternative The value to supply when the optional has no value..
	 *
	 * @return The stored value when set, else the alternative value supplied.
	 */
	ValueType valueOr(const ValueType& alternative) const;
	/**
	 * @brief Provides the stored optional value or a default constructed value.
	 *
	 * @return The stored value when set, else the alternative value default.
	 */
	ValueType valueOrDefault() const;
};

template<typename ValueType>
bool operator==(const ValueType& lhs, const Optional<ValueType>& rhs);
template<typename ValueType>
bool operator!=(const ValueType& lhs, const Optional<ValueType>& rhs);
template<typename ValueType>
bool operator<(const ValueType& lhs, const Optional<ValueType>& rhs);
template<typename ValueType>
std::ostream& operator<<(std::ostream& o, const Optional<ValueType>& opt);
template<>
std::ostream& operator<<(std::ostream& o, const Optional<::QString>& opt);

}}

template<typename ValueType>
template<typename ValueStorageType>
constexpr drn::foundation::Optional<ValueType>::OptionalStorage<ValueStorageType>::OptionalStorage(
) noexcept :
	dummy_{}
{
	static_assert(
		std::is_same<ValueType, ValueStorageType>::value,
		"The optional value type must be same type as the stored value type."
	);
	new (&this->dummy_) quint8{0};
}

template<typename ValueType>
template<typename ValueStorageType>
drn::foundation::Optional<ValueType>::OptionalStorage<ValueStorageType>::~OptionalStorage()
{}

template<typename ValueType>
void drn::foundation::Optional<ValueType>::setValue(ValueType value)
{
	if (this->hasValue_)
		this->store_.value_ = std::move(value);
	else
		new (&this->store_.value_) ValueType{std::move(value)};
	this->hasValue_ = true;
}

template<typename ValueType>
template<typename ... ConstructorArgs>
void drn::foundation::Optional<ValueType>::constructValue(ConstructorArgs&& ... args)
{
	assert( ! this->hasValue_ && "The value should not already be set");
	new (&this->store_.value_) ValueType{std::forward<ConstructorArgs>(args)...};
	this->hasValue_ = true;
}

template<typename ValueType>
constexpr drn::foundation::Optional<ValueType>::Optional() noexcept :
	hasValue_{false},
	store_{}
{}

template<typename ValueType>
drn::foundation::Optional<ValueType>::Optional(const ValueType& value) :
	Optional{}
{
	this->setValue(value);
}

template<typename ValueType>
drn::foundation::Optional<ValueType>::Optional(const Optional& other) :
	Optional{}
{
	if (other.hasValue_)
		this->setValue(other.store_.value_);
}

template<typename ValueType>
drn::foundation::Optional<ValueType>::Optional(ValueType&& value) noexcept :
	Optional{}
{
	this->setValue(std::move(value));
}

template<typename ValueType>
drn::foundation::Optional<ValueType>::Optional(Optional&& other) noexcept:
	Optional{}
{
	if (other.hasValue_)
		this->setValue(std::move(other.store_.value_));
	other.reset();
}

template<typename ValueType>
drn::foundation::Optional<ValueType>::~Optional()
{
	this->reset();
}

template<typename ValueType>
drn::foundation::Optional<ValueType>& drn::foundation::Optional<ValueType>::operator=(
	const Optional& other
)
{
	if (this != &other)
	{
		if (this->hasValue_ && other.hasValue_)
			this->store_.value_ = other.store_.value_;
		else if (other.hasValue_)
			this->setValue(other.store_.value_);
		else
			this->reset();
		this->hasValue_ = other.hasValue_;
	}
	return *this;
}

template<typename ValueType>
drn::foundation::Optional<ValueType>& drn::foundation::Optional<ValueType>::operator=(
	Optional&& other
) noexcept
{
	if (this != &other)
	{
		if (this->hasValue_ && other.hasValue_)
			this->store_.value_ = std::move(other.store_.value_);
		else if (other.hasValue_)
			this->setValue(std::move(other.store_.value_));
		else
			this->reset();
		this->hasValue_ = other.hasValue_;
	}
	return *this;
}

template<typename ValueType>
drn::foundation::Optional<ValueType>& drn::foundation::Optional<ValueType>::operator=(
	const ValueType& value
)
{
	*this = Optional{value};
	return *this;
}

template<typename ValueType>
drn::foundation::Optional<ValueType>& drn::foundation::Optional<ValueType>::operator=(
	ValueType&& value
) noexcept
{
	*this = Optional{std::move(value)};
	return *this;
}

template<typename ValueType>
template<typename ... ConstructorArguments>
void drn::foundation::Optional<ValueType>::emplace(ConstructorArguments&& ... args)
{
	this->reset();
	this->constructValue(std::forward<ConstructorArguments>(args)...);
}

template<typename ValueType>
const ValueType& drn::foundation::Optional<ValueType>::operator*() const
{
	assert(this->hasValue_ && "Should not access a value when it's not set.");
	return this->store_.value_;
}

template<typename ValueType>
ValueType& drn::foundation::Optional<ValueType>::operator*()
{
	assert(this->hasValue_ && "Should not access a value when it's not set.");
	return this->store_.value_;
}

template<typename ValueType>
const ValueType* drn::foundation::Optional<ValueType>::operator->() const
{
	assert(this->hasValue_ && "Should not access a value when it's not set.");
	return &this->store_.value_;
}

template<typename ValueType>
ValueType* drn::foundation::Optional<ValueType>::operator->()
{
	assert(this->hasValue_ && "Should not access a value when it's not set.");
	return &this->store_.value_;
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::hasValue() const
{
	return this->hasValue_;
}

template<typename ValueType>
void drn::foundation::Optional<ValueType>::reset()
{
	if (this->hasValue_)
		this->store_.value_.~ValueType();
	this->hasValue_ = false;
	this->store_.dummy_ = 0;
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::operator==(const Optional<ValueType>& other) const
{
	return ( ! this->hasValue_ && ! other.hasValue_)
		|| (
			this->hasValue_ && other.hasValue_
			&& this->store_.value_ == other.store_.value_
		);
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::operator==(const ValueType& value) const
{
	return *this == Optional{value};
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::operator!=(const Optional<ValueType>& other) const
{
	return ! (*this == other);
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::operator!=(const ValueType& value) const
{
	return *this != Optional{value};
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::operator<(const Optional<ValueType>& other) const
{
	if (this->hasValue_ && other.hasValue_)
		return this->store_.value_ < other.store_.value_;

	if (this->hasValue_)
		return false;
	return other.hasValue_;
}

template<typename ValueType>
bool drn::foundation::Optional<ValueType>::operator<(const ValueType& value) const
{
	return *this < Optional{value};
}

template<typename ValueType>
ValueType drn::foundation::Optional<ValueType>::valueOr(const ValueType& alternative) const
{
	if (this->hasValue_)
		return this->store_.value_;
	return alternative;
}

template<typename ValueType>
ValueType drn::foundation::Optional<ValueType>::valueOrDefault() const
{
	return this->valueOr(ValueType{});
}

template<typename ValueType>
bool drn::foundation::operator==(const ValueType& lhs, const Optional<ValueType>& rhs)
{
	return rhs == lhs;
}

template<typename ValueType>
bool drn::foundation::operator!=(const ValueType& lhs, const Optional<ValueType>& rhs)
{
	return rhs != lhs;
}

template<typename ValueType>
bool drn::foundation::operator<(const ValueType& lhs, const Optional<ValueType>& rhs)
{
	return Optional<ValueType>{lhs} < rhs;
}

template<typename ValueType>
std::ostream& drn::foundation::operator<<(
	std::ostream& o,
	const drn::foundation::Optional<ValueType>& opt
)
{
	if (opt.hasValue())
		return o << *opt;
	return o << QObject::tr("<optional-value-not-set>").toStdString();
}

template<>
inline std::ostream& drn::foundation::operator<<(
	std::ostream& o,
	const drn::foundation::Optional<::QString>& opt
)
{
	return o << opt.valueOr(QObject::tr("<optional-value-not-set>")).toStdString();
}

#endif
