#pragma once

#include <type_traits>
#include <assert.h>
#include <stdexcept>

struct in_place_t
{
	explicit in_place_t() = default;
};

template<typename BaseType> class optional
{
	using Storage = typename std::aligned_storage<sizeof(BaseType), std::alignment_of<BaseType>::value>::type;

	Storage m_storage;
	bool m_valid = false;

public:
	typedef optional<BaseType> self_type;

	optional() = default;

	~optional()
	{
		reset();
	}

	optional(const optional& src) : m_valid(src.m_valid)
	{
		if (src.m_valid)
		{
			new(&m_storage) BaseType(*src);
		}
	}

	optional(optional&& src) : m_valid(src.m_valid)
	{
		if (src.m_valid)
		{
			new(&m_storage) BaseType(std::move(*src));
		}
	}

	template<typename U = BaseType, typename =
		typename std::enable_if<
			std::is_constructible<BaseType, U&&>::value &&
			!(std::is_same<typename std::decay<U>::type, self_type>::value)
			>::type
		>
	optional(U&& value) : m_valid(true)
	{
		new(&m_storage) BaseType(std::forward<U>(value));
	}

	template<typename... Args, typename = typename std::enable_if<std::is_constructible<BaseType, Args...>::value>::type>
	explicit optional(in_place_t tag, Args&&... args) : m_valid(true)
	{
		new(&m_storage) BaseType(std::forward<Args>(args)...);
	}

	optional& operator = (optional&& src) noexcept
	{
		if (src.m_valid)
		{
			if (m_valid)
			{
				**this = std::move(*src);
			}
			else
			{
				new(&m_storage) BaseType(std::move(*src));
				m_valid = true;
			}
		}
		else if (m_valid)
		{
			reinterpret_cast<BaseType*>(&m_storage)->~BaseType();
			m_valid = false;
		}

		return *this;
	}

	optional& operator = (const optional& src)
	{
		if (src.m_valid)
		{
			if (m_valid)
			{
				**this = *src;
			}
			else
			{
				new(&m_storage) BaseType(*src);
				m_valid = true;
			}
		}
		else if (m_valid)
		{
			reinterpret_cast<BaseType*>(&m_storage)->~BaseType();
			m_valid = false;
		}

		return *this;
	}

	template<typename U = BaseType, typename =
		typename std::enable_if<
		!std::is_same<typename std::decay<U>::type, self_type>::value &&
		std::is_constructible<BaseType, U>::value &&
		std::is_assignable<BaseType&, U>::value
		>::type>
	optional& operator = (U&& value)
	{
		if (m_valid)
		{
			**this = std::forward<U>(value);
		}
		else
		{
			new(&m_storage) BaseType(std::forward<U>(value));
			m_valid = true;
		}

		return *this;
	}

	void reset()
	{
		if (m_valid)
		{
			(**this).BaseType::~BaseType();
			m_valid = false;
		}
	}

	constexpr explicit operator bool() const
	{
		return m_valid;
	}

	constexpr bool has_value() const
	{
		return m_valid;
	}

	constexpr bool valid() const
	{
		return has_value();
	}

	BaseType& operator * () &
	{
		assert(m_valid);
		return *reinterpret_cast<BaseType*>(&m_storage);
	}

	const BaseType& operator * () const &
	{
		assert(m_valid);
		return *reinterpret_cast<const BaseType*>(&m_storage);
	}

	BaseType&& operator * () &&
	{
		assert(m_valid);
		return std::move(*reinterpret_cast<BaseType*>(&m_storage));
	}

	const BaseType&& operator * () const &&
	{
		assert(m_valid);
		return std::move(*reinterpret_cast<BaseType*>(&m_storage));
	}

	BaseType* operator -> ()
	{
		assert(m_valid);
		return reinterpret_cast<BaseType*>(&m_storage);
	}

	const BaseType* operator -> () const
	{
		assert(m_valid);
		return reinterpret_cast<const BaseType*>(&m_storage);
	}

	BaseType& value()
	{
		if (!has_value())
		{
			throw std::logic_error("bad_optional_access");
		}

		return **this;
	}

	const BaseType& value() const
	{
		if (!has_value())
		{
			throw std::logic_error("bad_optional_access");
		}

		return **this;
	}

	template<typename U> BaseType value_or(U&& default_value) &&
	{
		return has_value() ? std::move(**this) : static_cast<BaseType>(std::forward<U>(default_value));
	}

	template<typename U> const BaseType value_or(U&& default_value) const &
	{
		return has_value() ? **this : static_cast<BaseType>(std::forward<U>(default_value));
	}

	template<typename... Args> BaseType& emplace(Args&&... args)
	{
		reset();

		new(&m_storage) BaseType(std::forward<Args>(args)...);
		m_valid = true;

		return **this;
	}
};

template<typename T> optional<typename std::decay<T>::type> make_optional(T&& val)
{
	return optional<typename std::decay<T>::type>(std::forward<T>(val));
}

template<typename T, typename... Args> optional<T> make_optional(Args&&... args)
{
	return optional<T>(in_place_t{}, std::forward<Args>(args)...);
}