#ifndef EXPECTED_H
#define EXPECTED_H

#include <stdexcept>
#include <typeinfo>
#include <utility>

template <typename T>
class Expected {
	union {
		T m_value;
		std::exception_ptr m_error;
	};
	bool m_valid;
	Expected() {} // internal use

public:

    /** Constructor that receives a valid result value */
	Expected(const T& rhs)
		: m_value(rhs), m_valid(true)
	{}

	/** Move constructor for a valid result value */
	Expected(T&& rhs)
		: m_value(std::move(rhs))
		, m_valid(true)
	{}

    /** Copy constructor */
	Expected(const Expected& rhs)
		: m_valid(rhs.m_valid)
	{
		if (m_valid) {
			new (&m_value) T(rhs.m_value);
		}
		else {
			new (&m_error) std::exception_ptr(rhs.m_error);
		}
	}

	/** Move constructor */
	Expected(Expected &&rhs)
		: m_valid(rhs.m_valid)
	{
		if (m_valid) {
			new (&m_value) T(std::move(rhs.m_value));
		}
		else {
			new (&m_error) std::exception_ptr(std::move(rhs.m_error));
		}
	}

	/** destructor */
	~Expected()
	{
		if (m_valid) {
			m_value.~T();
		}
		else {
			using std::exception_ptr;
			m_error.~exception_ptr();
		}
	}

//	void swap(Expected& rhs)
//	{
//		if (m_valid) {
//			if (rhs.m_valid) {
//				using std::swap;
//				swap(m_value, rhs.m_value);
//			}
//			else {
//				auto t = std::move(rhs.m_error);
//				new(&rhs.m_value) T(std::move(m_value));
//				new(&m_error) std::exception_ptr(t);
//				std::swap(m_valid, rhs.m_valid);
//			}
//		}
//		else {
//			if (rhs.m_valid) {
//				rhs.swap(*this);
//			}
//			else {
//				using std::swap;
//				swap(m_error, rhs.m_error);
//				std::swap(m_valid, rhs.m_valid);
//			}
//		}
//	}

	template <class E>
	static Expected<T> fromException(const E& exception)
	{
		if (typeid(exception) != typeid(E)) {
			throw std::invalid_argument("slicing detected");
		}
		return fromException(std::make_exception_ptr(exception));
	}

	static Expected<T> fromException(std::exception_ptr p)
	{
		Expected<T> result;
		result.m_valid = false;
		new (&result.m_error) std::exception_ptr(std::move(p));
		return result;
	}

	static Expected<T> fromException()
	{
		return fromException(std::current_exception());
	}

	bool valid() const
	{
		return m_valid;
	}

	T& get()
	{
		if (!m_valid) {
			std::rethrow_exception(m_error);
		}
		return m_value;
	}

	const T& get() const
	{
		if (!m_valid) {
			std::rethrow_exception(m_error);
		}
		return m_value;
	}

	template <class E>
	bool hasException() const
	{
		try {
			if (!m_valid) {
				std::rethrow_exception(m_error);
			}
		}
		catch (const E& ) {
			return true;
		}
		catch (...) {
		}
		return false;
	}

	template <class F>
	static Expected fromCode(F fun)
	{
		try {
			return Expected(fun());
		}
		catch (...) {
			return fromException();
		}
	}
};


template <>
class Expected<void> {
	std::exception_ptr m_error;
	bool m_valid;

public:

	Expected<void>()
		: m_valid(true)
	{}


	Expected(const Expected& rhs)
	: m_error(rhs.m_error)
	, m_valid(rhs.m_valid)
	{}

	Expected(Expected<void> &&rhs)
	: m_error(std::move(rhs.m_error))
	, m_valid(rhs.m_valid)
	{}

	~Expected()
	{}

//	void swap(Expected& rhs)
//	{
//		if (m_valid) {
//			if (!rhs.m_valid) {
//				m_error = rhs.m_error;
//				std::swap(m_valid, rhs.m_valid);
//			}
//		}
//		else {
//			if (rhs.m_valid) {
//				rhs.swap(*this);
//			}
//			else {
//				using std::swap;
//				swap(m_error, rhs.m_error);
//				std::swap(m_valid, rhs.m_valid);
//			}
//		}
//	}

	template <class E>
	static Expected<void> fromException(const E& exception)
	{
		if (typeid(exception) != typeid(E)) {
			throw std::invalid_argument("slicing detected");
		}
		return fromException(std::make_exception_ptr(exception));
	}

	static Expected<void> fromException(std::exception_ptr p)
	{
		Expected<void> result;
		result.m_valid = false;
		result.m_error = std::exception_ptr(std::move(p));
		return result;
	}

	static Expected<void> fromException()
	{
		return fromException(std::current_exception());
	}

	bool valid() const
	{
		return m_valid;
	}

	void get() const
	{
		if (!m_valid) {
			std::rethrow_exception(m_error);
		}
	}


	template <class E>
	bool hasException() const
	{
		try {
			if (!m_valid) {
				std::rethrow_exception(m_error);
			}
		}
		catch (const E& ) {
			return true;
		}
		catch (...) {
		}
		return false;
	}

	template <class F>
	static Expected fromCode(F fun)
	{
		try {
			fun();
			return Expected<void>();
		}
		catch (...) {
			return fromException();
		}
	}
};



#endif // EXPECTED_H
